var tileUtils = {
	earth_radius: 6378137,
	min_lat: -85.05112878,
	max_lat: 85.05112878,
	min_lng: -180,
	max_lng: 180,

	Clip: function(n, minValue, maxValue) {
		return Math.min(Math.max(n, minValue), maxValue);
	},


	getMapSize: function(levelOfDetail, tileSize) {
        if(!tileSize){tileSize = getTileSize()}
		return Math.abs(tileSize << levelOfDetail);
	},

	GroundResolution: function(latitude, levelOfDetail, tileSize) {
		var latitude = this.Clip(latitude, this.min_lat, this.max_lat);
		return Math.cos(latitude * Math.PI / 180) * 2 * Math.PI * this.earth_radius / this.getMapSize(levelOfDetail, tileSize);
	},

	MapScale: function(latitude, levelOfDetail, screenDpi, tileSize) {

		return this.GroundResolution(latitude, levelOfDetail, tileSize) * screenDpi / 0.0254;
	},

	LatLongToPixelXY: function(latitude, longitude, levelOfDetail, tileSize, dontClip) {
        if(!dontClip){
            longitude = this.Clip(longitude, this.min_lng, this.max_lng);
        }
        latitude = this.Clip(latitude, this.min_lat, this.max_lat);
		var x = (longitude + 180) / 360;
		var sinLatitude = Math.sin(latitude * Math.PI / 180);
		var y = 0.5 - Math.log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * Math.PI);

		var mapSize = this.getMapSize(levelOfDetail, tileSize);
        var pixelX = x * mapSize + 0.5;
        var pixelY = y * mapSize + 0.5;
        if(!dontClip){
            pixelX = this.Clip(pixelX, 0, mapSize - 1);
        }
		pixelY = this.Clip(pixelY, 0, mapSize - 1);
		return [pixelX, pixelY];
	},

	LongLatToTileXY: function(lng, lat, level, tileSize) {
		var pixel = this.LatLongToPixelXY(lat, lng, level, tileSize);
		//alert(pixel[0] + ", " + pixel[1]);
		var tiles = this.PixelXYToTileXY(pixel[0], pixel[1], tileSize);
		//alert(tiles[0] + ", " + tiles[1]);
		return tiles;
	},

	PixelXYToLatLong: function(pixelX, pixelY, levelOfDetail, clipDegrees, tileSize) {
		if (clipDegrees == undefined) {
			clipDegrees = true
		}
        
		var mapSize = this.getMapSize(levelOfDetail, tileSize);
        
		if (clipDegrees == false) {
			var pixelXClipped = pixelX - (mapSize - 1) * (Math.ceil(pixelX / (mapSize - 1)) - 1)
			var x = (this.Clip(pixelXClipped, 0, mapSize - 1) / mapSize) - 0.5;
			var y = 0.5 - (this.Clip(pixelY, 0, mapSize - 1) / mapSize);
			var latitude = (90 - 360 * Math.atan(Math.exp(-y * 2 * Math.PI)) / Math.PI);
			var longitude = (360 * x) + (360 * (Math.ceil(pixelX / (mapSize - 1)) - 1));
		} else {
			var x = (this.Clip(pixelX, 0, mapSize - 1) / mapSize) - 0.5;
			var y = 0.5 - (this.Clip(pixelY, 0, mapSize - 1) / mapSize);
			var latitude = 90 - 360 * Math.atan(Math.exp(-y * 2 * Math.PI)) / Math.PI;
			var longitude = 360 * x;
		}
		return [latitude, longitude];
	},

	PixelXYToTileXY: function(pixelX, pixelY, tileSize) {
		if(!tileSize){tileSize = getTileSize()}
		var tileX = pixelX / tileSize;
		var tileY = pixelY / tileSize;
		return [tileX, tileY];
	},

	TileXYToPixelXY: function(tileX, tileY, tileSize) {
		if(!tileSize){tileSize = tileSize}
		var pixelX = tileX * tileSize;
		var pixelY = tileY * tileSize;
		return [pixelX, pixelY];
	},

	// Funktion um aus Quadkey die X, Y, Z-Pixelkoordinaten zu berechnen    
	QuadKeyToTileXY: function(quadKey) {
		var tileX = 0;
		var tileY = 0;
		var levelOfDetail = quadKey.length;

		for (var i = levelOfDetail; i > 0; i--) {
			var mask = 1 << (i - 1);
			switch (quadKey[levelOfDetail - i]) {
				case '0':
					break;

				case '1':
					tileX |= mask;
					break;

				case '2':
					tileY |= mask;
					break;

				case '3':
					tileX |= mask;
					tileY |= mask;
					break;

				default:
					writeInfoLine(String("Invalid QuadKey digit sequence: " + String(quadKey)));
			}
		}
		return [tileX, tileY, levelOfDetail];
	},


	tileXYZToQuadKey: function(x, y, z)

	{
		var quadKey = ''

		for (var i = z; i > 0; i--) {

			var digit = 0
			var mask = 1 << (i - 1)

			if ((x & mask) != 0) {
				digit += 1;
			};

			if ((y & mask) != 0) {
				digit += 2;
			};

			quadKey = quadKey + digit;
		};
		return quadKey
	},

};

var tileFinder =

	{
		__init__: function(tileUtils, tileTemplate)

		{
			this.tileUtils = tileUtils;
			this.tileTemplate = tileTemplate;
		},


		getZoomLevel: function(ScreenRes, deltaAngle, outputDetailLevel, dontClipToMax, tileSize, maxZL, globalMaxZL)

		{
            
            if(!tileSize){tileSize = getTileSize()}
            if(!maxZL){maxZL = getMaxZL()}
            if(!globalMaxZL){globalMaxZL = getGlobalMaxZL()}
            
			if (dontClipToMax == undefined) {
				dontClipToMax = false;
			}
            
			var zl = Math.round(((Math.log(180 / (deltaAngle / (outputDetailLevel*1.2 * ScreenRes / tileSize)))) / Math.log(2)) + 1);

			if (dontClipToMax == false) {
				zl = tileUtils.Clip(zl, 1, maxZL);
			} else {
				zl = tileUtils.Clip(zl, 1, globalMaxZL);
			}
			return zl
		},

		getLatLongRectByPixelXY: function(X1, Y1, X2, Y2, level, tileSize) {
			var coord = [];

			var NW = tileUtils.PixelXYToLatLong(X1, Y1, level, false, tileSize);
			var SE = tileUtils.PixelXYToLatLong(X2, Y2, level, false, tileSize);

			coord = [NW[1], SE[0], SE[1], NW[0]];

			return coord;
		},



		getTileQuadsByLatLng: function(tempScreenViewPoints, outputDetailLevel, screenRes, tileSize, maxZL, globalMaxZL, fileNameAppendix, fileExtension)

		{
            if(!fileNameAppendix){fileNameAppendix = getFileNameAppendix()};
            if(!fileExtension){fileExtension = getFileExtension()};
            
			var xminInput = tempScreenViewPoints[0];
			var ymin = tempScreenViewPoints[1];
			var xmaxInput = tempScreenViewPoints[2];
			var ymax = tempScreenViewPoints[3];
			var stepVal = 360;
			var fullRotsMin = Math.ceil((xminInput + (stepVal / 2)) / stepVal) - 1;
			var fullRotDegrees = stepVal * (fullRotsMin) - (stepVal / 2);
			var xmin = xminInput - fullRotDegrees - (stepVal / 2);
			var fullRotsMax = Math.ceil((xmaxInput + (stepVal / 2)) / stepVal) - 1;
			var fullRotDegrees = stepVal * (fullRotsMax) - (stepVal / 2);
			var xmax = xmaxInput - fullRotDegrees - (stepVal / 2);
			//writeInfoLine(String(fullRotsMin)+"/"+String(fullRotsMax));
            
			var level = tileFinder.getZoomLevel(screenRes, (xmaxInput - xminInput), outputDetailLevel, undefined, tileSize, maxZL, globalMaxZL);
			var tileMin = tileUtils.LongLatToTileXY(xmin, ymax, level, tileSize);
			var tileMax = tileUtils.LongLatToTileXY(xmax, ymin, level, tileSize);
			var tileUrls = [];
			tileMax[0] = Math.ceil(tileMax[0]);
			tileMin[1] = Math.floor(tileMin[1]);
			if (fullRotsMin == fullRotsMax) {
				for (var y = tileMax[1]; y >= tileMin[1]; y = y - 1) {
					for (var x = tileMin[0]; x <= tileMax[0]; x = x + 1) {
						tileUrls.push([this.createTileUrl(x, y, level).toString(), fullRotsMin]);
					}
				}
			} else {
				for (var fR = fullRotsMin; fR <= fullRotsMax; fR++) {
					var tileBorderMax = Math.ceil(tileUtils.LongLatToTileXY(180, ymin, level, tileSize)[0]);
					var tileBorderMin = Math.floor(tileUtils.LongLatToTileXY(-180, ymin, level, tileSize)[0]);
					if (fR == fullRotsMin) {;
						for (var y = tileMax[1]; y >= tileMin[1]; y = y - 1) {
							for (var x = Math.floor(tileMin[0]); x <= tileBorderMax; x = x + 1) {
								tileUrls.push([this.createTileUrl(x, y, level).toString(), fR]);
							}
						}
					}
					if (fR > fullRotsMin && fR < fullRotsMax) {
						for (var y = tileMax[1]; y >= tileMin[1]; y = y - 1) {
							for (var x = tileBorderMin; x <= tileBorderMax; x = x + 1) {
								tileUrls.push([this.createTileUrl(x, y, level).toString(), fR]);
							}
						}
					}
					if (fR == fullRotsMax) {
						for (var y = tileMax[1]; y >= tileMin[1]; y = y - 1) {
							for (var x = tileBorderMin; x <= tileMax[0]; x = x + 1) {
								tileUrls.push([this.createTileUrl(x, y, level).toString(), fR]);
							}
						}
					}
				}
			}
            
			return tileUrls.map(function(quadAndFullRot){
                return {fileNameAppendix : fileNameAppendix, quadcode: quadAndFullRot[0], fullRots: quadAndFullRot[1], fileExtension : fileExtension}
            })
		},

		createTileUrl: function(x, y, z) {
			return tileUtils.tileXYZToQuadKey(x, y, z);
		},
        
        getHigherZoomleverQuads: function(quadcode){
            return [quadcode + "0",quadcode + "1",quadcode + "2",quadcode + "3"];
        }
	};