/**
 * @requires OpenLayers/Layer/Grid.js
 */

/**
 * Class: OpenLayers.Layer.Seznam
 * 
 * Inherits from:
 *  - <OpenLayers.Layer.Grid>
 */
OpenLayers.Layer.Seznam = OpenLayers.Class(OpenLayers.Layer.Grid,
                          OpenLayers.Layer.FixedZoomLevels, {


    /** Aktualni cislo serveru Seznamu, ze ktereho se stahuji dlazdice.
     * {Integer} 1
     */         
    CURRENTSRVINDEX: 1,
    
    
    /** Pocet serveru Seznamu, ze kterych se stahuji dlazdice.
     * {Integer} 4
     */         
    SRVNUM: 4,

    /** 
     * Constant: MIN_ZOOM_LEVEL
     * {Integer} 3
     */
    MIN_ZOOM_LEVEL: 3,
    
    /** 
     * Constant: MAX_ZOOM_LEVEL
     * {Integer} 16
     */
    MAX_ZOOM_LEVEL: 16,


    RESOLUTIONS: [
        1,    // nepouzito
        1,    // nepouzito
        1,    // nepouzito
        131072,
        65536,
        32768,
        16384,
        8192,
        4096,
        2048,
        1024,
        512,
        256,
        128,
        64,
        32,
        16 
    ],


    maxResolution : 983040,
    minResolution : 16,

    maxExtent : new OpenLayers.Bounds(0, 0, 251658240, 251658240),

    /** 
     * APIProperty: isBaseLayer
     */    
    isBaseLayer: true,

    /**
     * APIProperty: units
     * {?}
     */    
    units: null,
    
    mapTheme: "base",

    moveTo:function(bounds, zoomChanged, dragging) {
        OpenLayers.Layer.Grid.prototype.moveTo.apply(this, arguments);
        var res = this.getResolution();
        var rr = res;
    },

          
    /**
     * Constructor: OpenLayers.Layer.Seznam
     * 
     * Parameters:
     * name - {String}
     * options - {Object} Additional options for the layer. Any of the 
     *     APIProperties listed on this layer, and any layer types it
     *     extends, can be overridden through the options parameter. 
     */
    initialize: function(name, theme, options) {
        OpenLayers.Layer.Grid.prototype.initialize.apply(this, arguments);
        this.addOptions(options);
        this.projection = new OpenLayers.Projection.Seznam();
        if (theme == null) {
          this.mapTheme = "base";
        } else {
          this.mapTheme = theme;
        }
    },

    /**
     * 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) {
      var resolution = this.getResolution();
      var zm = this.getZoomForResolution(resolution) + 3;

      var xHex = convertDec2Hex(bounds.left);
      var yHex = convertDec2Hex(bounds.bottom);

      var url = "http://m" + this.CURRENTSRVINDEX + ".mapserver.mapy.cz/" + this.mapTheme + "/" + zm + "_" + xHex + "_" + yHex;
      this.CURRENTSRVINDEX++;
      if (this.CURRENTSRVINDEX>this.SRVNUM) {
        this.CURRENTSRVINDEX = 1;
        }
      
      return url;
    },

    /**
     * Method: addTile
     * 
     * Parameters:
     * bounds - {<OpenLayers.Bounds>}
     * position - {<OpenLayers.Pixel>}
     * 
     * Returns:
     * {<OpenLayers.Tile.Image>}
     */    
    addTile:function(bounds,position) {
        var url = this.getURL(bounds);
        return new OpenLayers.Tile.Image(this, position, bounds, 
                                             url, this.tileSize);
    },


    /**
     * APIMethod: clone
     * 
     * Parameters: 
     * obj - {Object}
     * 
     * Returns:
     * {<OpenLayers.Layer.Kamap>} An exact clone of this OpenLayers.Layer.KaMap
     */
    clone: function (obj) {
        
        if (obj == null) {
            obj = new OpenLayers.Layer.Seznam(this.name,
                                            this.mapTheme,
                                            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
        if (this.tileSize != null) {
            obj.tileSize = this.tileSize.clone();
        }
        
        // we do not want to copy reference to grid, so we make a new array
        obj.grid = [];

        return obj;
    },    
    

    CLASS_NAME: "OpenLayers.Layer.Seznam"
});



/**
 * Class: OpenLayers.Projection.Seznam
 * 
 * Inherits from:
 *  - <OpenLayers.Projection>
 */
OpenLayers.Projection.Seznam = OpenLayers.Class(OpenLayers.Projection,
                           {

    CLASS_NAME: "OpenLayers.Layer.Seznam",
    projCode: "Mapy.cz",
    
    initialize: function(projCode, options) {
        OpenLayers.Util.extend(this, options);
        this.projCode = "Mapy.cz";
        if (window.Proj4js) {
            this.proj = new Proj4js.Proj(projCode);
        }

        OpenLayers.Projection.addTransform("EPSG:4326", "Mapy.cz", this.WGSToMapyCZ);
        OpenLayers.Projection.addTransform("Mapy.cz", "EPSG:4326", this.MapyCZToWGS);
        
    },
    
    MapyCZToWGS: function(point) {
      var wgs = PPToWGS(point.x, point.y);
      point.x = wgs.degLon;
      point.y = wgs.degLat;
    },

    WGSToMapyCZ: function(point) {
      var pp = WGSToPP(point.y, point.x);
      point.x = pp.x;
      point.y = pp.y;
    }


});


  function WGSToPP(la, lo) {
    var rad = wgsDegToRad(la, lo);
    var utmEE = wgsToUTM(rad.la, rad.lo);
    var pp = utmEEToPP(utmEE.east, utmEE.north);
    return pp;
  }
  
  function PPToWGS(x, y) {
    var utmEE = ppToUTMEE(x, y);
    var out = utmToWGS(utmEE.east, utmEE.north, 33);
    return out;
  }
  
  utmToWGS = function(east, north, zone) {
    var pi = Math.PI;
    var units = 1;
    if (typeof zone != "undefined") {
        var zone = zone;
    } else {
        var zone = 33;
    }
    var k = parseFloat(0.9996);
    var a = parseFloat(6378137);
    var FL = 1 / 298.257223563;
    var f = parseFloat(FL);
    var b = a * (1 - f);
    var e2 = (a * a - b * b) / (a * a);
    var e = Math.sqrt(e2);
    var ei2 = (a * a - b * b) / (b * b);
    var ei = Math.sqrt(ei2);
    var n = (a - b) / (a + b);
    var G = a * (1 - n) * (1 - n * n) * (1 + (9 / 4) * n * n + (255 / 64) * Math.pow(n, 4)) * (pi / 180);
    var north = (north - 0) * units;
    var east = (east - 500000) * units;
    var m = north / k;
    var sigma = (m * pi) / (180 * G);
    var footlat = sigma + ((3 * n / 2) - (27 * Math.pow(n, 3) / 32)) * Math.sin(2 * sigma) + ((21 * n * n / 16) - (55 * Math.pow(n, 4) / 32)) * Math.sin(4 * sigma) + (151 * Math.pow(n, 3) / 96) * Math.sin(6 * sigma) + (1097 * Math.pow(n, 4) / 512) * Math.sin(8 * sigma);
    var rho = a * (1 - e2) / Math.pow(1 - (e2 * Math.sin(footlat) * Math.sin(footlat)), (3 / 2));
    var nu = a / Math.sqrt(1 - (e2 * Math.sin(footlat) * Math.sin(footlat)));
    var psi = nu / rho;
    var t = Math.tan(footlat);
    var x = east / (k * nu);
    var laterm1 = (t / (k * rho)) * (east * x / 2);
    var laterm2 = (t / (k * rho)) * (east * Math.pow(x, 3) / 24) * ( - 4 * psi * psi + 9 * psi * (1 - t * t) + 12 * t * t);
    var laterm3 = (t / (k * rho)) * (east * Math.pow(x, 5) / 720) * (8 * Math.pow(psi, 4) * (11 - 24 * t * t) - 12 * Math.pow(psi, 3) * (21 - 71 * t * t) + 15 * psi * psi * (15 - 98 * t * t + 15 * Math.pow(t, 4)) + 180 * psi * (5 * t * t - 3 * Math.pow(t, 4)) + 360 * Math.pow(t, 4));
    var laterm4 = (t / (k * rho)) * (east * Math.pow(x, 7) / 40320) * (1385 + 3633 * t * t + 4095 * Math.pow(t, 4) + 1575 * Math.pow(t, 6));
    var latrad = footlat - laterm1 + laterm2 - laterm3 + laterm4;
    var lat = rad2deg(latrad);
    var seclat = 1 / Math.cos(footlat);
    var loterm1 = x * seclat;
    var loterm2 = (Math.pow(x, 3) / 6) * seclat * (psi + 2 * t * t);
    var loterm3 = (Math.pow(x, 5) / 120) * seclat * ( - 4 * Math.pow(psi, 3) * (1 - 6 * t * t) + psi * psi * (9 - 68 * t * t) + 72 * psi * t * t + 24 * Math.pow(t, 4));
    var loterm4 = (Math.pow(x, 7) / 5040) * seclat * (61 + 662 * t * t + 1320 * Math.pow(t, 4) + 720 * Math.pow(t, 6));
    var w = loterm1 - loterm2 + loterm3 - loterm4;
    var longrad = deg2rad(getLCM(zone)) + w;
    var lon = rad2deg(longrad);
    return {
        "degLat": lat,
        "degLon": lon,
        "radLat": latrad,
        "radLon": longrad
    };
};


  function getLCM(zone) {
    if ((zone < 1) || (zone > 60)) {
        throw new Error("coordCalculator:UTM Zone number is not between 1 and 60.");
    } else {
        return ((zone * 6) - 183);
    }
  };


  function ppToUTMEE(x, y) {
    var UTMSIZE = 3;
    var north = (y) * Math.pow(2, -5) + 1300000;
    var east = (x) * Math.pow(2, -5) + ( - 3700000);
    east = roundoff(east, UTMSIZE);
    north = roundoff(north, UTMSIZE);
    return {
        "east": east,
        "north": north,
        "zone": 33
    };
};

  function wgsDegToRad(la, lo) {
    var loRad = deg2rad(lo);
    var laRad = deg2rad(la);

    return {
        "la": laRad,
        "lo": loRad,
        "set": true
    };

  }

  function deg2rad(x) {
    var x = x * Math.PI / 180;
    return x;
  };
  
  function rad2deg(x) {
    var x = x * 180 / Math.PI;
    return x;
  }
 

  function utmEEToPP(east, north) {
    x = (Math.round(east) - ( - 3700000)) * Math.pow(2, 5);
    y = (Math.round(north) - (1300000)) * Math.pow(2, 5);
    return {
        "x": x,
        "y": y
    };
  };      
  
  function roundoff(x, y) {
    var x = parseFloat(x);
    var y = parseFloat(y);
    x = Math.round(x * Math.pow(10, y)) / Math.pow(10, y);
    return x;
  };
  
  function wgsToUTM(la, lo) {
    var pi = Math.PI;
    var units = 1;
    var distsize = 3;
    var latrad = la;
    var lonrad = lo;
    var latddd = rad2deg(la);
    var londdd = rad2deg(lo);
    var UTMSIZE = 3;
    if (arguments[2]) {
        var zone = Math.round((londdd + 183) / 6);
    } else {
        var zone = 33;
    }
    var k = parseFloat(0.9996);
    var a = parseFloat(6378137);
    var FL = 1 / 298.257223563;
    var f = parseFloat(FL);
    var b = a * (1 - f);
    var e2 = (a * a - b * b) / (a * a);
    var e = Math.sqrt(e2);
    var ei2 = (a * a - b * b) / (b * b);
    var ei = Math.sqrt(ei2);
    var n = (a - b) / (a + b);
    var G = a * (1 - n) * (1 - n * n) * (1 + (9 / 4) * n * n + (255 / 64) * Math.pow(n, 4)) * (pi / 180);
    var w = londdd - parseFloat(zone * 6 - 183);
    w = deg2rad(w);
    var t = Math.tan(latrad);
    var rho = a * (1 - e2) / Math.pow(1 - (e2 * Math.sin(latrad) * Math.sin(latrad)), (3 / 2));
    var nu = a / Math.sqrt(1 - (e2 * Math.sin(latrad) * Math.sin(latrad)));
    var psi = nu / rho;
    var coslat = Math.cos(latrad);
    var sinlat = Math.sin(latrad);
    var A0 = 1 - (e2 / 4) - (3 * e2 * e2 / 64) - (5 * Math.pow(e2, 3) / 256);
    var A2 = (3 / 8) * (e2 + (e2 * e2 / 4) + (15 * Math.pow(e2, 3) / 128));
    var A4 = (15 / 256) * (e2 * e2 + (3 * Math.pow(e2, 3) / 4));
    var A6 = 35 * Math.pow(e2, 3) / 3072;
    var m = a * ((A0 * latrad) - (A2 * Math.sin(2 * latrad)) + (A4 * Math.sin(4 * latrad)) - (A6 * Math.sin(6 * latrad)));
    var eterm1 = (w * w / 6) * coslat * coslat * (psi - t * t);
    var eterm2 = (Math.pow(w, 4) / 120) * Math.pow(coslat, 4) * (4 * Math.pow(psi, 3) * (1 - 6 * t * t) + psi * psi * (1 + 8 * t * t) - psi * 2 * t * t + Math.pow(t, 4));
    var eterm3 = (Math.pow(w, 6) / 5040) * Math.pow(coslat, 6) * (61 - 479 * t * t + 179 * Math.pow(t, 4) - Math.pow(t, 6));
    var dE = k * nu * w * coslat * (1 + eterm1 + eterm2 + eterm3);
    var east = parseFloat(500000) + (dE / units);
    east = roundoff(east, UTMSIZE);
    var nterm1 = (w * w / 2) * nu * sinlat * coslat;
    var nterm2 = (Math.pow(w, 4) / 24) * nu * sinlat * Math.pow(coslat, 3) * (4 * psi * psi + psi - t * t);
    var nterm3 = (Math.pow(w, 6) / 720) * nu * sinlat * Math.pow(coslat, 5) * (8 * Math.pow(psi, 4) * (11 - 24 * t * t) - 28 * Math.pow(psi, 3) * (1 - 6 * t * t) + psi * psi * (1 - 32 * t * t) - psi * 2 * t * t + Math.pow(t, 4));
    var nterm4 = (Math.pow(w, 8) / 40320) * nu * sinlat * Math.pow(coslat, 7) * (1385 - 3111 * t * t + 543 * Math.pow(t, 4) - Math.pow(t, 6));
    var dN = k * (m + nterm1 + nterm2 + nterm3 + nterm4);
    var north = (parseFloat(0) + (dN / units));
    north = roundoff(north, UTMSIZE);
    return {
        "zone": zone,
        "east": east,
        "north": north
        };
    };      


function convertDec2Hex(d) {
  var hD="0123456789abcdef";
  var h = hD.substr(d&15,1);
  while(d>15) {d>>=4;h=hD.substr(d&15,1)+h;}
  return h;
  }
