/* Copyright 2008 Google.
 * All rights reserved.
 *
 * Description: Wrapper objects for using the Google
                Maps API with Google Earth Enterprise
 * Version: 1.01
 */

var _fusion_map_server = "";
if (typeof _mFusionMapServer != "undefined" && _mFusionMapServer) {
  _fusion_map_server = _mFusionMapServer + '/';
}

var MAX_ZOOM_LEVEL = 23;

function GFusionMap(container, serverDefs, opts) {
  if (typeof opts != "object") {
    opts = {};
  }
  this.map_layers = [];
  this.map_layers_index = {};  // An index from layer id's to the index of the
                               // map layers array.
  InitLatLngProjectionPrototypes();
  InitGeeMapsButtonPrototypes();
  this.createFusionMapType(serverDefs);  // Call before initializeLayers.
  if (!serverDefs.useGoogleLayers && this.fusion_map_type) {
    opts.mapTypes = [ this.fusion_map_type ];
  }
  GMap2.call(this, container, opts);
  if (serverDefs.useGoogleLayers && this.fusion_map_type) {
    this.addMapType(this.fusion_map_type);
    // Default to maps layer for this case.
    serverDefs.layers[0].initialState = false;  
  }
  this.initializeLayers(serverDefs);
}

(function() {
  function C() {}
  C.prototype = GMap2.prototype;
  GFusionMap.prototype = new C;
})();

GFusionMap.prototype.initializeLayers = function(serverDefs) {
  
  var layerDefs = serverDefs.layers;
  var serverUrl = serverDefs.serverUrl;
  var projection = serverDefs.projection;
  if (layerDefs == undefined || layerDefs.length == 0) {
    alert("Error: No Layers are defined for this URL.");
    return;
  }
  this.allowUsageLogging = function() { return false; }

  // Create tile layers for the remaining vector layers
  var index = 0;
  for (var i = 0; i < layerDefs.length; ++i, ++index) {
    var zIndex = i;
    var map_layer = new GFusionMapLayer(serverUrl, layerDefs[i], zIndex);
    this.map_layers.push(map_layer);
    this.map_layers_index[layerDefs[i].id] = index;
  }
}

/**
 * Create a GMapType for the GEE base imagery layer if it exists.
 * @param {Object} 
 *          serverDefs are the GEE server definitions of URL, layers and 
 *          projection.
 * @return {GMapType} the constructed map type.
 */
GFusionMap.prototype.createFusionMapType = function(serverDefs) {
  var layerDefs = serverDefs.layers;
  var serverUrl = serverDefs.serverUrl;
  var projection = serverDefs.projection;
  this.fusion_base_layer = null;
  // Create a TileLayer from the base imagery layer if available
  if (layerDefs && layerDefs.length > 0 && 
      layerDefs[0].requestType == "ImageryMaps") {
    this.fusion_base_layer = new GFusionTileLayer(serverUrl, layerDefs[0]);
    this.fusion_map_type = new GMapType([this.fusion_base_layer], 
          projection == "mercator" ? 
              new GMercatorProjection(MAX_ZOOM_LEVEL) :
              new LatLngProjection(MAX_ZOOM_LEVEL),
          layerDefs[0].label);    
  }
  return null;
}

GFusionMap.prototype.showInitialFusionLayers = function() {
  for (var i = 0; i < this.map_layers.length; ++i) {
    var layer = this.map_layers[i];
    if (layer.initial_state) {
      this.showFusionLayer(layer.id);
    }
  }
}

GFusionMap.prototype.getFusionLayerCount = function() {
  return this.map_layers.length;
}

GFusionMap.prototype.isFusionLayerVisible = function(layerId) {
  var i = this.map_layers_index[layerId];
  return this.map_layers[i].enabled;
}

GFusionMap.prototype.showFusionLayer = function(layerId) {
  var i = this.map_layers_index[layerId];
  this.addOverlay(this.map_layers[i].overlay);
  this.map_layers[i].enabled = true;
}

GFusionMap.prototype.hideFusionLayer = function(layerId) {
  var i = this.map_layers_index[layerId];
  this.removeOverlay(this.map_layers[i].overlay);
  this.map_layers[i].enabled = false;
}

GFusionMap.prototype.getFusionLayerName = function(layerId) {
  var i = this.map_layers_index[layerId];
  return this.map_layers[i].txt;
}

GFusionMap.prototype.getFusionLayerIcon = function(layerId) {
  var i = this.map_layers_index[layerId];
  return this.map_layers[i].img;
}

/**
 * Adjusts the opacity of the specified layer by a step amount.
 */
GFusionMap.prototype.adjustFusionLayerOpacity = function(layerId, step) {
  var i = this.map_layers_index[layerId];
  var layer = this.map_layers[i];
  var tileLayer = layer.overlay.getTileLayer();
  var opacity = tileLayer.getOpacity();
  opacity = opacity + step;
  if (opacity > 1.0) opacity = 1.0;
  if (opacity < 0.0) opacity = 0.0;
  tileLayer.opacity = opacity;
  // Must force the layer to update the display...this is ugly.
  if (layer.enabled) {
    this.hideFusionLayer(layerId);
    this.showFusionLayer(layerId);
  }
}

/******************************************************************************
 * GFusionMapLayer
 *  Container class to hold the GTileLayerOverlay and status for a Fusion layer
 ******************************************************************************/
function GFusionMapLayer(serverUrl, layer_def, order) {
  var opts = {};
  if (typeof order == "number") {
    opts.zPriority = order;
  }
  this.overlay = new GTileLayerOverlay(
      new GFusionTileLayer(serverUrl, layer_def), opts);
  this.initial_state = layer_def.initialState;
  this.enabled = layer_def.initialState;
  this.txt = layer_def.label;
  this.img = geeLayerIconUrl(serverUrl, layer_def);
  this.id = layer_def.id;
}

/******************************************************************************
 * Fusion tile layers
 ******************************************************************************/
function GFusionTileLayer(serverUrl, layer_def) {
  this.fetch_func = geeLayerRequestCallback(serverUrl, layer_def);
  this.min_zoom_level = 1;  // zoomRange not currently returned by GEE.
  this.zoom_range = MAX_ZOOM_LEVEL;  // zoomRange not currently returned by GEE.
  this.opacity = layer_def.opacity;
  this.is_png = layer_def.isPng;
}

GFusionTileLayer.prototype = new GTileLayer(new GCopyrightCollection(""),
                                            1, MAX_ZOOM_LEVEL);

GFusionTileLayer.prototype.getTileUrl = function(addr, level) {
    return this.fetch_func(addr, level);
}

GFusionTileLayer.prototype.isPng = function() {
  return this.is_png;
}

GFusionTileLayer.prototype.getOpacity = function() {
  return this.opacity;
}

GFusionTileLayer.prototype.minResolution = function() {
  return this.min_zoom_level;
}

GFusionTileLayer.prototype.maxResolution = function() {
  return this.zoom_range;
}

GFusionTileLayer.prototype.getCopyright = function(bounds, zoom) {
  return "";
}

/**
 * Return the URL for a layer icon (a specific request to the GEE Server).
 * @param {serverUrl}
 *          serverUrl the URL of the GEE Server.
 * @param {Array}
 *          layer the layer object.
 * @return {string} the kml feature object.
 */
function geeLayerIconUrl(serverUrl, layer) {
  return serverUrl + "/query?request=Icon&icon_path=" + layer.icon;
}

/**
 * Return a callback for map tile requests to the GEE server.
 * @param {serverUrl}
 *          serverUrl the URL of the GEE Server.
 * @param {Array}
 *          layer the layer object.
 * @return {closure} the callback function.
 */
function geeLayerRequestCallback(serverUrl, layer) {
  return function(addr, level) {
    return serverUrl + "/query?request=" + layer.requestType +
      "&level=" + level +
      "&row=" + addr.y +
      "&col=" + addr.x +
      "&channel=" + layer.id +
      "&version=" + layer.version;
  };
}

/******************************************************************************
 * geeMapsButton
 * A geeMapsButton is a GControl that displays textual button inside the Map.
 ******************************************************************************/

/**
 * Create the geeMapsButton.
 * @param {string}
 *          label  the text label of the button.
 * @param {function(GMap2)}
 *          callback  the callback for the button action.
 * @param {GControlAnchor}
 *          anchor  the anchor for positioning the button.
 * @param {number}
 *          horizontalPadding  the integer value for the horizontal padding.
 * @param {number}
 *          verticalPadding  the integer value for the vertical padding.
 * @constructor
 */
function geeMapsButton(label, callback, anchor, 
                       horizontalPadding, verticalPadding) {
  this.fusion_label = label;
  this.fusion_callback = callback;
  this.fusion_anchor = anchor;
  this.fusion_padding = new GSize(horizontalPadding, verticalPadding);
}

/**
 * Initialize geeMapsButton after all javascript has been loaded.
 */
function InitGeeMapsButtonPrototypes() {
  
  /**
   * To "subclass" the GControl, we set the prototype object to
   * an instance of the GControl object.
   */
  geeMapsButton.prototype = new GControl();
  
  /**
   * Creates a one DIV for each of the buttons and places them in a container
   * DIV which is returned as our control element. We add the control to
   * to the map container and return the element for the map class to
   * position properly.
   */
  geeMapsButton.prototype.initialize = function(map) {
    var container = document.createElement("div");
  
    var buttonDiv = document.createElement("div");
    this.setButtonStyle_(buttonDiv);
    container.appendChild(buttonDiv);
    buttonDiv.appendChild(document.createTextNode(this.fusion_label));
    GEvent.addDomListener(buttonDiv, "click", this.fusion_callback);
  
    map.getContainer().appendChild(container);
    return container;
  }
  
  /**
   * By default, the control will appear in the top left corner of the
   * map with 7 pixels of padding.
   */
  geeMapsButton.prototype.getDefaultPosition = function() {
    return new GControlPosition(this.fusion_anchor, this.fusion_padding);
  }
  
  /** 
   * Sets the proper CSS for the given button element.
   */
  geeMapsButton.prototype.setButtonStyle_ = function(button) {
    button.style.textDecoration = "none";
    button.style.color = "#0000cc";
    button.style.backgroundColor = "white";
    button.style.font = "11px Arial";
    button.style.border = "1px solid black";
    button.style.padding = "2px";
    button.style.marginBottom = "3px";
    button.style.textAlign = "center";
    button.style.cursor = "pointer";
  }
}

/******************************************************************************
 * LatLngProjection
 ******************************************************************************/

/**
 * The LatLng projection used by Fusion.
 * We calculate all the constants we need to use at every zoom level up front
 * to make our conversions more efficient.
 */
function LatLngProjection(zoom_level) {
  var me = this;
  me.pixelsPerLonDegree_ = [];
  me.pixelsPerLonRadian_ = [];
  me.pixelOrigo_ = [];
  me.pixelRange_ = [];

  // Our first zoom level is a single tile
  var pixels = 256;
  for (var z = 0; z <= zoom_level; z++) {
    var origin = pixels / 2;
    me.pixelsPerLonDegree_.push(pixels / 360);
    me.pixelsPerLonRadian_.push(pixels / (2 * Math.PI));
    me.pixelOrigo_.push(new GPoint(origin, origin));
    me.pixelRange_.push(pixels);
    pixels *= 2;
  }
}

LatLngProjection.prototype = 0;

// Initialize LatLngProjection after all javascript has been loaded.
function InitLatLngProjectionPrototypes() {
  LatLngProjection.prototype = new GProjection();

  LatLngProjection.prototype.fromLatLngToPixel = function(latLng, zoom) {
    var me = this;
    var o = me.pixelOrigo_[zoom];
    var x = Math.round(o.x + latLng.lng() * me.pixelsPerLonDegree_[zoom]);
    var y = Math.round(o.y + (latLng.lat() - 360) *  me.pixelsPerLonDegree_[zoom]);
    return new GPoint(x, -y);
  }

  LatLngProjection.prototype.fromPixelToLatLng = function(pixel, zoom,
                                                            opt_nowrap) {
    var me = this;
    var o = me.pixelOrigo_[zoom];
    var lng = (pixel.x - o.x) / me.pixelsPerLonDegree_[zoom];
    var lat = ((pixel.y - o.y) / me.pixelsPerLonDegree_[zoom]);
    return new GLatLng(-lat, lng, opt_nowrap);
  }

  LatLngProjection.prototype.tileCheckRange = function(tile, zoom, tilesize) {
    var range = this.pixelRange_[zoom];
    if (tile.y < 0 || tile.y * tilesize >= range) {
      return false;
    }

    // The map repeats itself in the horizontal direction, so tiles are
    // repeated as well.  Compute the "canonical" tile coordinate.
    if (tile.x < 0 || tile.x * tilesize >= range) {
      var tilerange = Math.floor(range / tilesize);
      tile.x = tile.x % tilerange;
      if (tile.x < 0) {
        tile.x += tilerange;
      }
    }

    return true;
  }

  LatLngProjection.prototype.getWrapWidth = function(zoom) {
    return this.pixelRange_[zoom];
  }
}

