/*
 * L.TileLayer is used for standard xyz-numbered tile layers.
 */

L.TileLayer = L.Class.extend({
	includes: L.Mixin.Events,
	
	options: {
		minZoom: 0,
		maxZoom: 18,
		tileSize: 256,
		subdomains: 'abc',
		errorTileUrl: '',
		attribution: '',
		opacity: 1,
		scheme: 'xyz',
    noWrap: false,
		
		unloadInvisibleTiles: L.Browser.mobileWebkit,
		updateWhenIdle: L.Browser.mobileWebkit
	},
	
	initialize: function(url, options) {
		L.Util.setOptions(this, options);
		
		this._url = url;
		
		if (typeof this.options.subdomains == 'string') {
			this.options.subdomains = this.options.subdomains.split('');
		}
	},
	
	onAdd: function(map) {
		this._map = map;
		
		// create a container div for tiles
		this._initContainer();
		
		// create an image to clone for tiles
		this._createTileProto();
		
		// set up events
		map.on('viewreset', this._reset, this);
		
		if (this.options.updateWhenIdle) {
			map.on('moveend', this._update, this);
		} else {
			this._limitedUpdate = L.Util.limitExecByInterval(this._update, 100, this);
			map.on('move', this._limitedUpdate, this);
		}
		
		this._reset();
		this._update();
	},
	
	onRemove: function(map) {
		this._map.getPanes().tilePane.removeChild(this._container);
		this._container = null;
		
		this._map.off('viewreset', this._reset, this);
		
		if (this.options.updateWhenIdle) {
			this._map.off('moveend', this._update, this);
		} else {
			this._map.off('move', this._limitedUpdate, this);
		}
	},
	
	getAttribution: function() {
		return this.options.attribution;
	},
	
	setOpacity: function(opacity) {
		this.options.opacity = opacity;
		
		this._setOpacity(opacity);
		
		// stupid webkit hack to force redrawing of tiles
		if (L.Browser.webkit) {
			for (i in this._tiles) {
				this._tiles[i].style.webkitTransform += ' translate(0,0)';
			}
		}
	},
	
	_setOpacity: function(opacity) {
		if (opacity < 1) {
			L.DomUtil.setOpacity(this._container, opacity);
		}
	},
	
	_initContainer: function() {
		var tilePane = this._map.getPanes().tilePane;
		
		if (!this._container || tilePane.empty) {
			this._container = L.DomUtil.create('div', 'leaflet-layer', tilePane);
			
			this._setOpacity(this.options.opacity);
		}
	},
	
	_reset: function() {
		this._tiles = {};
		this._initContainer();
		this._container.innerHTML = '';
	},
	
	_update: function() {
		var bounds = this._map.getPixelBounds(),
			tileSize = this.options.tileSize;
		
		var nwTilePoint = new L.Point(
				Math.floor(bounds.min.x / tileSize),
				Math.floor(bounds.min.y / tileSize)),
			seTilePoint = new L.Point(
				Math.floor(bounds.max.x / tileSize),
				Math.floor(bounds.max.y / tileSize)),
			tileBounds = new L.Bounds(nwTilePoint, seTilePoint);
		
		this._addTilesFromCenterOut(tileBounds);
		
		if (this.options.unloadInvisibleTiles) {
			this._removeOtherTiles(tileBounds);
		}
	},
	
	_addTilesFromCenterOut: function(bounds) {
		var queue = [],
			center = bounds.getCenter();
		
		for (var j = bounds.min.y; j <= bounds.max.y; j++) {
			for (var i = bounds.min.x; i <= bounds.max.x; i++) {				
				if ((i + ':' + j) in this._tiles) { continue; }
				queue.push(new L.Point(i, j));
			}
		}
		
		// load tiles in order of their distance to center
		queue.sort(function(a, b) {
			return a.distanceTo(center) - b.distanceTo(center);
		});
		
		this._tilesToLoad = queue.length;
		for (var k = 0, len = this._tilesToLoad; k < len; k++) {
			this._addTile(queue[k]);
		}
	},
	
	_removeOtherTiles: function(bounds) {
		var kArr, x, y, key;
		
		for (key in this._tiles) {
			if (this._tiles.hasOwnProperty(key)) {
				kArr = key.split(':');
				x = parseInt(kArr[0], 10);
				y = parseInt(kArr[1], 10);
				
				// remove tile if it's out of bounds
				if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
					this._tiles[key].src = '';
					if (this._tiles[key].parentNode == this._container) {
						this._container.removeChild(this._tiles[key]);
					}
					delete this._tiles[key];
				}
			}
		}		
	},
	
	_addTile: function(tilePoint) {
		var tilePos = this._getTilePos(tilePoint),
			zoom = this._map.getZoom(),
			key = tilePoint.x + ':' + tilePoint.y;
			
		// wrap tile coordinates
		var tileLimit = (1 << zoom);
		if (!this.options.noWrap) {
      tilePoint.x = ((tilePoint.x % tileLimit) + tileLimit) % tileLimit;
    }
		if (tilePoint.y < 0 || tilePoint.y >= tileLimit) { return; }
		
		// create tile
		var tile = this._createTile();
		L.DomUtil.setPosition(tile, tilePos);
		
		this._tiles[key] = tile;
        
		if (this.options.scheme == 'tms') {
			tilePoint.y = tileLimit - tilePoint.y - 1;
		}

		this._loadTile(tile, tilePoint, zoom);
		
		this._container.appendChild(tile);
	},
	
	_getTilePos: function(tilePoint) {
		var origin = this._map.getPixelOrigin(),
			tileSize = this.options.tileSize;
		
		return tilePoint.multiplyBy(tileSize).subtract(origin);
	},
	
	// image-specific code (override to implement e.g. Canvas or SVG tile layer)
	
	getTileUrl: function(tilePoint, zoom) {
		var subdomains = this.options.subdomains,
			s = this.options.subdomains[(tilePoint.x + tilePoint.y) % subdomains.length];

		return this._url
				.replace('{s}', s)
				.replace('{z}', zoom)
				.replace('{x}', tilePoint.x)
				.replace('{y}', tilePoint.y);
	},
	
	_createTileProto: function() {
		this._tileImg = L.DomUtil.create('img', 'leaflet-tile');
		this._tileImg.galleryimg = 'no';
		
		var tileSize = this.options.tileSize;
		this._tileImg.style.width = tileSize + 'px';
		this._tileImg.style.height = tileSize + 'px';
	},
	
	_createTile: function() {
		var tile = this._tileImg.cloneNode(false);
		tile.onselectstart = tile.onmousemove = L.Util.falseFn;
		return tile;
	},
	
	_loadTile: function(tile, tilePoint, zoom) {
		tile._layer = this;
		tile.onload = this._tileOnLoad;
		tile.onerror = this._tileOnError;
		tile.src = this.getTileUrl(tilePoint, zoom);
	},
	
	_tileOnLoad: function(e) {
		var layer = this._layer;
		
		this.className += ' leaflet-tile-loaded'; 

		layer.fire('tileload', {tile: this, url: this.src});
		
		layer._tilesToLoad--;
		if (!layer._tilesToLoad) {
			layer.fire('load');
		}
	},
	
	_tileOnError: function(e) {
		var layer = this._layer;
		
		layer.fire('tileerror', {tile: this, url: this.src});
		
		var newUrl = layer.options.errorTileUrl;
		if (newUrl) {
			this.src = newUrl;
		}
	}
});
