/**
 * @requires OpenLayers/Layer/Grid.js
 * @requires OpenLayers/Tile/Image.js
 */

/**
 * Class: OpenLayers.Layer.WMTS
 * 
 * Inherits from:
 *  - <OpenLayers.Layer.Grid>
 */
OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
    
    /**
     * APIProperty: isBaseLayer
     * {Boolean}
     */
    isBaseLayer: true,

    /**
     * APIProperty: requestEncoding
     * {String} default is "REST", it can also be "KVP"
     */
    requestEncoding: "REST",
    
    /**
     * APIProperty: defaultParams
     * {Object} default parameters for WMTS GetCapabilities/GetTile requests
     */
    defaultParams: {
		service: "WMTS",
		version: "1.0.0",
		request: "GetTile",
		//layer: "0",
		//style: "0",
		//<dimension1>: "",
		//<dimension2>: "",
		//...
		//<dimensionn>: "",
		//tileMatrixSet: "",
		format: "image/jpeg"
	},
    
	/**
	 * APIProperty: dimensions
	 * {<Array>} parameters in this.params that should be considered as a WMTS dimension parameter
	 * 
	 */
	dimensions: [],
	
    /**
     * APIProperty: tileOrigin
     * {<OpenLayers.Pixel>}
     */
    tileOrigin: null,
    
    /**
     * APIProperty: tileFullExtent
     * {<OpenLayers.Bounds>}
     */
    tileFullExtent: null,

	/**
     * APIProperty: tileFormatSuffix
     * {String}
     */
    tileFormatSuffix: "jpg",

    /**
     * Constructor: OpenLayers.Layer.WMTS
     * 
     * Parameters:
     * name - {String}
     * url - {String}
     * params - {Object} WMTS request parameters
     * options - {Object} extra options to tag onto the layer
     */
    initialize: function(name, url, params, options) {
		var newArguments = [];        
        params = OpenLayers.Util.upperCaseObject(params); //uppercase params
        newArguments.push(name, url, params, options);
        
        OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
        OpenLayers.Util.applyDefaults(
        	this.params, 
            OpenLayers.Util.upperCaseObject(this.defaultParams)
        );
        
        // there must be a 'tileFullExtent'
        if(this.tileFullExtent === null) {
        	OpenLayers.Console.error("...tileFullExtent missing or empty...");
     		throw "...tileFullExtent missing or empty...";	
    	}     	
    	// there must be a 'tileOrigin'
    	if(this.tileOrigin === null) {
    		OpenLayers.Console.error("...tileOrigin missing or empty...");
     		throw "...tileOrigin missing or empty...";	
    	}
	},    

    /**
     * APIMethod:destroy
     */
    destroy: function() {
        // for now, nothing special to do here. 
        OpenLayers.Layer.Grid.prototype.destroy.apply(this, arguments);  
    },

    
    /**
     * APIMethod: clone
     * 
     * Parameters:
     * obj - {Object}
     * 
     * Returns:
     * {<OpenLayers.Layer.WMTS>} An exact clone of this <OpenLayers.Layer.WMTS>
     */
    clone: function (obj) {        
        if (obj == null) {
            obj = new OpenLayers.Layer.WMTS(this.name, this.url, this.options);
        }
        //get all additions from superclasses
        obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
        // copy/set any non-init, non-simple values here
        return obj;
    },    
    
    /**
     * Method: getURL
     * 
     * Parameters:
     * bounds - {<OpenLayers.Bounds>}
     * 
     * Returns:
     * {String} A string with the layer's url and parameters and also the 
     *          passed-in bounds and appropriate tile size specified as 
     *          parameters
     */
    getURL: function (bounds) {
        bounds = this.adjustBounds(bounds);
        
        var res = this.map.getResolution();        
        var path = null;
        var url = null;
        
        if(this.tileFullExtent.intersectsBounds(bounds)) {
        	var scale = this.map.getZoom(); 
        	var col = null;
	        if(this.tileOrigin.lon <= bounds.left) {
	        	col = Math.round((bounds.left - this.tileOrigin.lon) / (res * this.tileSize.w));
	        } else if(this.tileOrigin.lon >= bounds.right) {
	        	col = Math.round((this.tileOrigin.lon - bounds.right) / (res * this.tileSize.w));
	        } else {
	        	return "../../img/transparent.png";
	        	OpenLayers.Console.warn("...invalid tileOrigin...");
     			//throw "...invalid tileOrigin...";	
	        }                
	        var row = null;
	        if(this.tileOrigin.lat >= bounds.top) {
	        	row = Math.round((this.tileOrigin.lat - bounds.top) / (res * this.tileSize.h));
	        } else if(this.tileOrigin.lat <= bounds.bottom) {
	        	row = Math.round((bounds.bottom - this.tileOrigin.lat) / (res * this.tileSize.h));
	        } else {
	        	return "../../img/transparent.png";
	        	OpenLayers.Console.warn("...invalid tileOrigin...");
     			//throw "...invalid tileOrigin...";
	        }        
	        
	        if(this.requestEncoding === "REST") {
	        	// include 'version', 'layer' and 'style' in tile resource url
		        path = this.params['VERSION'] + "/" + this.params['LAYER'] + "/" + this.params['STYLE'] + "/";
		        // include 'dimension' if there is any
		        for(var i=0; i<this.dimensions.length; i++) {
		        	if(this.params[this.dimensions[i]]) {
		        		path = path + this.params[this.dimensions[i]] + "/";
		        	}
		        }
		        // include 'tileMatrixSet'
		        path = path + this.params['TILEMATRIXSET'] + "/";	        
		        // include 'scale'
		        path = path + scale + "/";
		        // include 'tileRow', 'tileColumn' and tile image suffix
		        path = path + row + "/" + col + "." + this.tileFormatSuffix;
		        
		        url = this.url;
		        if (url instanceof Array) {
		            url = this.selectUrl(path, url);
		        }
		        return url + path;
		    } else if (this.requestEncoding === "KVP") {
	            // TileMatrix parameter added
	            var tilematrixValue = Math.round(scale);
	            tilematrixValue = (tilematrixValue > 9 ? "L" + tilematrixValue : "L0" + tilematrixValue);
	        	var newParams = {
	        	    'TILEMATRIX': tilematrixValue,
	        	    'SCALE': scale,
	        		'TILEROW': row,
	        		'TILECOL': col
                };
	        	return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this, [newParams]);
	        } else { 
	        	OpenLayers.Console.error("...unsupported WMTS request encoding " + this.requestEncoding + "...");
     			throw "...unsupported WMTS request encoding " + this.requestEncoding + "...";
	        }
	    } else {
	    	// area outside of tiles' full extent
	    	return "../../img/transparent.png";	    	
	    }        
    },
    
    /**
     * APIMethod: mergeNewParams
     * Catch changeParams and uppercase the new params to be merged in
     *     before calling changeParams on the super class.
     * 
     *     Once params have been changed, the tiles will be reloaded with
     *     the new parameters.
     * 
     * Parameters:
     * newParams - {Object} Hashtable of new params to use
     */
    mergeNewParams:function(newParams) {
        if(this.requestEncoding === "KVP") {
	    	var upperParams = OpenLayers.Util.upperCaseObject(newParams);
	        var newArguments = [upperParams];
	        return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this, 
	                                                             newArguments);
        }
    },

    /**
     * Method: addTile
     * addTile creates a tile, initializes it, and adds it to the layer div. 
     * 
     * Parameters:
     * bounds - {<OpenLayers.Bounds>}
     * position - {<OpenLayers.Pixel>}
     * 
     * Returns:
     * {<OpenLayers.Tile.Image>} The added OpenLayers.Tile.Image
     */
    addTile:function(bounds,position) {
        return new OpenLayers.Tile.Image(this, position, bounds, 
                                         null, this.tileSize);
    },

    /** 
     * APIMethod: setMap
     * When the layer is added to a map, then we can fetch our origin 
     *    (if we don't have one.) 
     * 
     * Parameters:
     * map - {<OpenLayers.Map>}
     */
    setMap: function(map) {
        OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments);
        if(!this.tileOrigin) { 
            this.tileOrigin = new OpenLayers.LonLat(this.map.maxExtent.left,
                                                this.map.maxExtent.bottom);
        }   
        if(!this.tileFullExtent) { 
            this.tileFullExtent = this.map.maxExtent;
        }                             
    },

    CLASS_NAME: "OpenLayers.Layer.WMTS"
});

