/*jslint white: false */
/*jslint forin: true */
/*global OpenLayers Drupal $ document jQuery window */

/**
 * @file
 * This file holds the main javascript API for OpenLayers. It is
 * responsable for loading and displaying the map.
 *
 * @ingroup openlayers
 */

/**
 * This is a workaround for a bug involving IE and VML support.
 * See the Drupal Book page describing this problem:
 * http://drupal.org/node/613002
 */

document.namespaces;

(function($) {

Drupal.settings.openlayers = {};
Drupal.settings.openlayers.maps = {};

/**
 * Minimal OpenLayers map bootstrap.
 * All additional operations occur in additional Drupal behaviors.
 */
Drupal.behaviors.openlayers = {
  'attach': function(context, settings) {
    if (typeof(Drupal.settings.openlayers) === 'object' &&
        Drupal.settings.openlayers.maps &&
        !$(context).data('openlayers')) {
      $('.openlayers-map:not(.openlayers-processed)').each(function() {
        // By setting the stop_render variable to TRUE, this will
        // halt the render process.  If set, one could remove this setting
        // then call Drupal.attachBehaviors again to get it started
        var map_id = $(this).attr('id');
        if (Drupal.settings.openlayers.maps[map_id] && Drupal.settings.openlayers.maps[map_id].stop_render != true) {
          var map = Drupal.settings.openlayers.maps[map_id];
          $(this).addClass('openlayers-processed');

          // Use try..catch for error handling.
          try {
            // Set OpenLayers language based on document language,
            // rather than browser language
            OpenLayers.Lang.setCode($('html').attr('lang'));

            $(this)
              // @TODO: move this into markup in theme function, doing this dynamically is a waste.
              .css('width', map.width)
              .css('height', map.height);

            var options = {};
            // This is necessary because the input JSON cannot contain objects
            options.projection = new OpenLayers.Projection('EPSG:' + map.projection);
            options.displayProjection = new OpenLayers.Projection('EPSG:' + map.displayProjection);

            // TODO: work around this scary code
            if (map.projection === '900913') {
              options.maxExtent = new OpenLayers.Bounds(
                -20037508.34, -20037508.34, 20037508.34, 20037508.34);
            }
            if (map.projection === '4326') {
              options.maxExtent = new OpenLayers.Bounds(-180, -90, 180, 90);
            }

            options.maxResolution = 1.40625;
            options.controls = [];

            // Change image, CSS, and proxy paths if specified
            if (map.image_path) {
              OpenLayers.ImgPath = Drupal.openlayers.relatePath(map.image_path,
                Drupal.settings.basePath);
            }
            if (map.css_path) {
              options.theme = Drupal.openlayers.relatePath(map.css_path,
                Drupal.settings.basePath);
            }
            if (map.proxy_host) {
              OpenLayers.ProxyHost = Drupal.openlayers.relatePath(map.proxy_host,
                Drupal.settings.basePath);
            }

            // Initialize openlayers map
            var openlayers = new OpenLayers.Map(map.id, options);

            // Run the layer addition first
            Drupal.openlayers.addLayers(map, openlayers);

            // Attach data to map DOM object
            $(this).data('openlayers', {'map': map, 'openlayers': openlayers});

            // Finally, attach behaviors
            Drupal.attachBehaviors(this);

            if ($.browser.msie) {
              Drupal.openlayers.redrawVectors();
            }
          }
          catch (e) {
            if (typeof console != 'undefined') {
              console.log(e);
            }
            else {
              $(this).text('Error during map rendering: ' + e);
            }
          }
        }
      });
    }
  }
};

/**
 * Collection of helper methods.
 */
Drupal.openlayers = {
  // Determine path based on format.
  'relatePath': function(path, basePath) {
    // Check for a full URL or an absolute path.
    if (path.indexOf('://') >= 0 || path.indexOf('/') == 0) {
      return path;
    }
    else {
      return basePath + path;
    }
  },
  /*
   * Redraw Vectors.
   * This is necessary because various version of IE cannot draw vectors on
   * $(document).ready()
   */
  'redrawVectors': function() {
    $(window).load(
      function() {
        var map;
        for (map in Drupal.settings.openlayers.maps) {
          $.each($('#' + map).data('openlayers')
            .openlayers.getLayersByClass('OpenLayers.Layer.Vector'),
            function(i, layer) {
              layer.redraw();
            }
          );
        }
      }
    );
  },
  /**
   * Add layers to the map
   *
   * @param map Drupal settings object for the map.
   * @param openlayers OpenLayers Map Object.
   */
  'addLayers': function(map, openlayers) {

    var sorted = [];
    for (var name in map.layers) {
      sorted.push({'name': name, 'weight': map.layers[name].weight });
    }
    sorted.sort(function(a, b) {
      var x = a.weight, y = b.weight;
      return ((x < y) ? -1 : ((x > y) ? 1 : 0));
    });

    for (var i = 0; i < sorted.length; ++i) {
      var layer,
        name = sorted[i].name,
        options = map.layers[name];

      // Add reference to our layer ID
      options.drupalID = name;
      // Ensure that the layer handler is available
      if (options.layer_handler !== undefined &&
        Drupal.openlayers.layer[options.layer_handler] !== undefined) {
        var layer = Drupal.openlayers.layer[options.layer_handler](map.layers[name].title, map, options);

        layer.visibility = !!(!map.layer_activated || map.layer_activated[name]);

        if (layer.isBaseLayer === false) {
          layer.displayInLayerSwitcher = (!map.layer_switcher || map.layer_switcher[name]);
        }

        if (map.center.wrapdateline === '1') {
          // TODO: move into layer specific settings
          layer.wrapDateLine = true;
        }

        openlayers.addLayer(layer);
      }
    }

    openlayers.setBaseLayer(openlayers.getLayersBy('drupalID', map.default_layer)[0]);

    // Zoom & center
    if (map.center.initial) {
      var center = OpenLayers.LonLat.fromString(map.center.initial.centerpoint).transform(
            new OpenLayers.Projection('EPSG:4326'),
            new OpenLayers.Projection('EPSG:' + map.projection));
      var zoom = parseInt(map.center.initial.zoom, 10);
      openlayers.setCenter(center, zoom, false, false);
    }

    // Set the restricted extent if wanted.
    // Prevents the map from being panned outside of a specfic bounding box.
    if (typeof map.center.restrict !== 'undefined' && map.center.restrict.restrictextent) {
      openlayers.restrictedExtent = OpenLayers.Bounds.fromString(
          map.center.restrict.restrictedExtent);
    }
  },
  /**
   * Abstraction of OpenLayer's feature adding syntax to work with Drupal output.
   * Ideally this should be rolled into the PHP code, because we don't want to manually
   * parse WKT
   */
  'addFeatures': function(map, layer, features) {
    var newFeatures = [];

    // Go through features
    for (var key in features) {
      var feature = features[key];
      var newFeatureObject = this.objectFromFeature(feature);

      // If we have successfully extracted geometry add additional
      // properties and queue it for addition to the layer
      if (newFeatureObject) {
        var newFeatureSet = [];

        // Check to see if it is a new feature, or an array of new features.
        if (typeof(newFeatureObject[0]) === 'undefined') {
          newFeatureSet[0] = newFeatureObject;
        }
        else {
          newFeatureSet = newFeatureObject;
        }

        // Go through new features
        for (var i in newFeatureSet) {
          var newFeature = newFeatureSet[i];

          // Transform the geometry if the 'projection' property is different from the map projection
          if (feature.projection) {
            if (feature.projection !== map.projection) {
              var featureProjection = new OpenLayers.Projection('EPSG:' + feature.projection);
              var mapProjection = new OpenLayers.Projection('EPSG:' + map.projection);
              newFeature.geometry.transform(featureProjection, mapProjection);
            }
          }

          // Add attribute data
          if (feature.attributes) {
            // Attributes belong to features, not single component geometries
            // of them. But we're creating a geometry for each component for
            // better performance and clustering support. Let's call these
            // "pseudofeatures".
            //
            // In order to identify the real feature each geometry belongs to
            // we then add a 'fid' parameter to the "pseudofeature".
            // NOTE: 'drupalFID' is only unique within a single layer.
            newFeature.attributes = feature.attributes;
            newFeature.data = feature.attributes;
            newFeature.drupalFID = key;
          }

          // Add style information
          if (feature.style) {
            newFeature.style = jQuery.extend({},
                OpenLayers.Feature.Vector.style['default'],
                feature.style);
          }

          // Push new features
          newFeatures.push(newFeature);
        }
      }
    }

    // Add new features if there are any
    if (newFeatures.length !== 0) {
      layer.addFeatures(newFeatures);
    }
  },
  'getStyleMap': function(map, layername) {
    if (map.styles) {
      var stylesAdded = {};
      // Grab and map base styles.
      for (var style in map.styles) {
        stylesAdded[style] = new OpenLayers.Style(map.styles[style]);
      }
      // Implement layer-specific styles.
      if (map.layer_styles !== undefined && map.layer_styles[layername]) {
        var style = map.layer_styles[layername];
        stylesAdded['default'] = new OpenLayers.Style(map.styles[style]);
      }
      return new OpenLayers.StyleMap(stylesAdded);
    }
    else {
      return new OpenLayers.StyleMap({
        'default': new OpenLayers.Style({
          pointRadius: 5,
          fillColor: '#ffcc66',
          strokeColor: '#ff9933',
          strokeWidth: 4,
          fillOpacity: 0.5
        }),
        'select': new OpenLayers.Style({
          fillColor: '#66ccff',
          strokeColor: '#3399ff'
        })
      });
    }
  },
  'objectFromFeature': function(feature) {
    var wktFormat = new OpenLayers.Format.WKT();
    // Extract geometry either from wkt property or lon/lat properties
    if (feature.wkt) {
      return wktFormat.read(feature.wkt);
    }
    else if (feature.lon) {
      return wktFormat.read('POINT(' + feature.lon + ' ' + feature.lat + ')');
    }
  }
};

Drupal.openlayers.layer = {};
})(jQuery);
;
if (Drupal.openlayers === undefined) {
  Drupal.openlayers = {};
}
Drupal.openlayers.layer = Drupal.openlayers.layer || {};

OpenLayers.Layer.MapBox = OpenLayers.Class(OpenLayers.Layer.TMS, {
    /*
     * Do not remove the MapBox or OpenLayers attribution from this code,
     * doing so is in violation of the terms of both licenses.
     */
    initialize: function (name, options) {
        var newArguments, // Arguments which will be automatically
                          // sent to the Layer.TMS constructor
            urls; // Multiple server URLs with the same contents 
                  // but distributed for performance
        mapbox_logo = "<a class='mapbox-branding' href='http://mapbox.com'></a> | <a href='http://mapbox.com/tos'>Terms of Service</a>";
        options.isBaseLayer = options.baselayer;
        options = OpenLayers.Util.extend({
            attribution: mapbox_logo,
            maxExtent: new OpenLayers.Bounds(
              -20037508, -20037508, 20037508, 20037508),
            maxResolution: 156543.0339,
            units: "m",
            type: "png",
            projection: "EPSG:900913",
            isBaseLayer: true,
            numZoomLevels: 19,
            displayOutsideMaxExtent: false,
            wrapDateLine: true,
            buffer: 0
        }, options);
        urls = (options.urls) ? options.urls : [
            "http://a.tiles.mapbox.com/mapbox/",
            "http://b.tiles.mapbox.com/mapbox/",
            "http://c.tiles.mapbox.com/mapbox/",
            "http://c.tiles.mapbox.com/mapbox/"
        ];
        if (options.osm) {
          options.attribution = "<a class='mapbox-branding' href='http://mapbox.com'></a> | <a href='http://mapbox.com/tos'>Terms of Service</a> | Data CCBYSA OSM"
        }
        newArguments = [name, urls, options];
        OpenLayers.Layer.TMS.prototype.initialize.apply(this, newArguments);
    },
    CLASS_NAME: "OpenLayers.Layer.MapBox"
});

/**
 * For OpenLayers 0.x and 2.x
 */
Drupal.openlayers.layer.MapBox = function (name, map, options) {
  var styleMap = Drupal.openlayers.getStyleMap(map, options.name);
  if (options.maxExtent !== undefined) {
    options.maxExtent = new OpenLayers.Bounds.fromArray(options.maxExtent);
  }
  if (options.type === undefined){
    options.type = "png";
  }
  options.projection = new OpenLayers.Projection('EPSG:'+options.projection);
  var layer = new OpenLayers.Layer.MapBox(name, options);
  layer.styleMap = styleMap;
  return layer;
}
;

/**
 * @file
 * JS Implementation of OpenLayers behavior.
 */

/**
 * Attribution Behavior
 */
(function($) {
Drupal.behaviors.openlayers_behavior_attribution = {
  'attach': function(context, settings) {
    var data = $(context).data('openlayers');
    if (data && data.map.behaviors['openlayers_behavior_attribution']) {
      // Add control
      var control = new OpenLayers.Control.Attribution();
      data.openlayers.addControl(control);
      control.activate();
    }
  }
};
})(jQuery);
;

/**
 * Global variables to help with scope
 *
 * TODO: Move this to a better place, like the map data().
 */
Drupal.openlayers = Drupal.openlayers || {};
Drupal.openlayers.popup = Drupal.openlayers.popup || {};
Drupal.openlayers.popup.popupSelect = Drupal.openlayers.popup.popupSelect || {};
Drupal.openlayers.popup.selectedFeature = Drupal.openlayers.popup.selectedFeature || {};


(function($) {
/**
 * Javascript Drupal Theming function for inside of Popups
 *
 * To override
 *
 * @param feature
 *  OpenLayers feature object.
 * @return
 *  Formatted HTML.
 */
Drupal.theme.prototype.openlayersPopup = function(feature) {
  var output = '';
  
  if (feature.attributes.name) {
    output += '<div class="openlayers-popup openlayers-tooltip-name">' + feature.attributes.name + '</div>';
  }
  
  if (feature.attributes.description) {
    output += '<div class="openlayers-popup openlayers-tooltip-description">' + feature.attributes.description + '</div>';
  }
  
  return output;
}

/**
 * OpenLayers Popup Behavior
 */
Drupal.behaviors.openlayers_behavior_popup =  {
  attach: function(context) {
  var layers, data = $(context).data('openlayers');
  if (data && data.map.behaviors['openlayers_behavior_popup']) {
    var map = data.openlayers;
    var options = data.map.behaviors['openlayers_behavior_popup'];
    var layers = [];

    // For backwards compatiability, if layers is not
    // defined, then include all vector layers
    if (typeof options.layers == 'undefined' || options.layers.length == 0) {
      layers = map.getLayersByClass('OpenLayers.Layer.Vector');
    }
    else {
      for (var i in options.layers) {
        var selectedLayer = map.getLayersBy('drupalID', options.layers[i]);
        if (typeof selectedLayer[0] != 'undefined') {
          layers.push(selectedLayer[0]);
        }
      }
    }

    popupSelect = new OpenLayers.Control.SelectFeature(layers,
      {
        onSelect: function(feature) {
          // Create FramedCloud popup.
          popup = new OpenLayers.Popup.FramedCloud(
            'popup',
            feature.geometry.getBounds().getCenterLonLat(),
            null,
            Drupal.theme('openlayersPopup', feature),
            null,
            true,
            function(evt) {
              Drupal.openlayers.popup.popupSelect.unselect(
                Drupal.openlayers.popup.selectedFeature
              );
            }
          );

          // Assign popup to feature and map.
          feature.popup = popup;
          feature.layer.map.addPopup(popup);
          Drupal.openlayers.popup.selectedFeature = feature;
        },
        onUnselect: function(feature) {
          // Remove popup if feature is unselected.
          feature.layer.map.removePopup(feature.popup);
          feature.popup.destroy();
          feature.popup = null;
        }
      }
    );

    map.addControl(popupSelect);
    popupSelect.activate();
    Drupal.openlayers.popup.popupSelect = popupSelect;
  }
  }
}
})(jQuery);
;

/**
 * Javascript Drupal Theming function for inside of Tooltips
 *
 * To override
 *
 * @param feature
 *  OpenLayers feature object.
 * @return
 *  Formatted HTML.
 */
Drupal.theme.prototype.openlayersTooltip = function(feature) {
  var output = '';
  
  if (feature.attributes.name) {
    output += '<div class="openlayers-popup openlayers-tooltip-name">' + feature.attributes.name + '</div>';
  }
  
  if (feature.attributes.description) {
    output += '<div class="openlayers-popup openlayers-tooltip-description">' + feature.attributes.description + '</div>';
  }
  
  return output;
};

(function($) {
/**
 * OpenLayers Tooltip Behavior
 */
Drupal.behaviors.openlayers_behavior_tooltip = {
  'attach': function(context, settings) {
    var layers, data = $(context).data('openlayers');
    if (data && data.map.behaviors['openlayers_behavior_tooltip']) {
      var map = data.openlayers;
      var options = data.map.behaviors['openlayers_behavior_tooltip'];
      var layers = [];

      // For backwards compatiability, if layers is not
      // defined, then include all vector layers
      if (typeof options.layers == 'undefined' || options.layers.length == 0) {
        layers = map.getLayersByClass('OpenLayers.Layer.Vector');
      }
      else {
        for (var i in options.layers) {
          var selectedLayer = map.getLayersBy('drupalID', options.layers[i]);
          if (typeof selectedLayer[0] != 'undefined') {
            layers.push(selectedLayer[0]);
          }
        }
      }

      // Define feature select events for selected layers.
      popupSelect = new OpenLayers.Control.SelectFeature(layers,
        {
          hover: true,
          clickout: false,
          multiple: false,
          onSelect: function(feature) {
            // Create FramedCloud popup for tooltip.
            var output = Drupal.theme('openlayersTooltip', feature);
            if (typeof output != 'undefined') {
              popup = new OpenLayers.Popup.FramedCloud(
                'tooltip',
                feature.geometry.getBounds().getCenterLonLat(),
                null,
                output,
                null,
                true
              );
              feature.popup = popup;
              feature.layer.map.addPopup(popup);
            }
          },
          onUnselect: function(feature) {
            // Remove popup.
            if (typeof feature.popup != 'undefined') {
              feature.layer.map.removePopup(feature.popup);
              feature.popup.destroy();
              feature.popup = null;
            }
          }
        }
      );

      // Actiate the popups
      map.addControl(popupSelect);
      popupSelect.activate();
    }
  }
};
})(jQuery);
;

/**
 * @file
 * JS Implementation of OpenLayers behavior.
 */

/**
 * Global variables to help with scope
 *
 * TODO: Move this to a better place, like the map data().
 */
Drupal.openlayers = Drupal.openlayers || {};
Drupal.openlayers.fullscreen = Drupal.openlayers.fullscreen || {};

(function($) {
/**
 * Attribution Behavior
 */
Drupal.behaviors.openlayers_behavior_fullscreen = {
  'attach': function(context, settings) {
    var fullscreenPanel;
    var data = $(context).data('openlayers');
    if (data && data.map.behaviors['openlayers_behavior_fullscreen']) {
      var opts = data.map.behaviors['openlayers_behavior_fullscreen'];

      // Create new panel control and add.
      fullscreenPanel = new OpenLayers.Control.Panel(
        {
          displayClass: 'openlayers_behavior_fullscreen_button_panel'
        }
      );
      data.openlayers.addControl(fullscreenPanel);

      // Create toggleing control and cutton.
      var toggler = OpenLayers.Function.bind(
        Drupal.openlayers.fullscreen.fullscreenToggle, data);
      var button = new OpenLayers.Control.Button({
        displayClass: 'openlayers_behavior_fullscreen_button',
        title: Drupal.t('Fullscreen'),
        trigger: toggler
      });
      fullscreenPanel.addControls([button]);

      // Make fullscreen by default if activited.
      if (opts['activated'] == true) {
        toggler();
      }
    }
  }
};

/**
 * Toggling function for FullScreen control.
 */
Drupal.openlayers.fullscreen.fullscreenToggle = function() {
  var map = this.openlayers;
  var $map = $(this.openlayers.div);
  var extent = map.getExtent();

  $map.parent().toggleClass('openlayers_map_fullscreen');
  $map.toggleClass('openlayers_map_fullscreen');
  $map.data('openlayers').openlayers.updateSize();
  $map.data('openlayers').openlayers.zoomToExtent(extent, true);
}
})(jQuery);
;

/**
 * @file
 * JS Implementation of OpenLayers behavior.
 */

/**
 * Keyboard Defaults Behavior
 */
(function($) {
Drupal.behaviors.openlayers_behavior_keyboarddefaults = function(context) {
  var data = $(context).data('openlayers');
  if (data && data.map.behaviors['openlayers_behavior_keyboarddefaults']) {
    // Add control
    var control = new OpenLayers.Control.KeyboardDefaults();
    data.openlayers.addControl(control);
    control.activate();
  }
}
})(jQuery);
;

/**
 * @file
 * JS Implementation of OpenLayers behavior.
 */

/**
 * Layer Switcher Behavior
 */
(function($) {
  Drupal.behaviors.openlayers_behavior_layerswitcher = {
    'attach': function(context, settings) {
      var data = $(context).data('openlayers');
      if (data && data.map.behaviors['openlayers_behavior_layerswitcher']) {
        // Add control
        var control = new OpenLayers.Control.LayerSwitcher({
          'ascending': !!data.map.behaviors['openlayers_behavior_layerswitcher'].ascending
        });
        data.openlayers.addControl(control);
        control.activate();
      }
    }
  };
})(jQuery);
;

/**
 * @file
 * JS Implementation of OpenLayers behavior.
 */

/**
 * Navigation Behavior
 */
(function($) {
Drupal.behaviors.openlayers_behavior_navigation = {
  'attach': function(context, settings) {
    var data = $(context).data('openlayers');
    if (data && data.map.behaviors['openlayers_behavior_navigation']) {
      // Add control
      var options = {
        'zoomWheelEnabled': data.map.behaviors['openlayers_behavior_navigation'].zoomWheelEnabled,
        'documentDrag': Boolean(data.map.behaviors['openlayers_behavior_navigation'].documentDrag),
      };
      var control = new OpenLayers.Control.Navigation(options);
      data.openlayers.addControl(control);
      control.activate();
    }
  }
};
})(jQuery);
;

/**
 * @file
 * JS Implementation of OpenLayers behavior.
 */

/**
 * Pan Zoom Bar Behavior
 */
(function($) {
  Drupal.behaviors.openlayers_behavior_panzoom = {
    'attach': function(context, settings) {
    var data = $(context).data('openlayers');
    if (data && data.map.behaviors['openlayers_behavior_panzoom']) {
      // Add control
      var control = new OpenLayers.Control.PanZoom();
      data.openlayers.addControl(control);
      control.activate();
    }
  }
}
})(jQuery);
;

/**
 * @file
 * OpenLayers Behavior implementation for clustering.
 */

(function ($) {
  
/**
 * OpenLayers Cluster Behavior
 */
Drupal.behaviors.openlayers_cluster = {
  attach: function(context) {
    var data = $(context).data('openlayers');
    if (data && data.map.behaviors.openlayers_behavior_cluster) {
      var options = data.map.behaviors.openlayers_behavior_cluster;
      var map = data.openlayers;
      var distance = parseInt(options.distance, 10);
      var threshold = parseInt(options.threshold, 10);
      var layers = [];
      for (var i in options.clusterlayer) {
        var selectedLayer = map.getLayersBy('drupalID', options.clusterlayer[i]);
        if (typeof selectedLayer[0] != 'undefined') {
          layers.push(selectedLayer[0]);
        }
      }

      // Go through chosen layers
      for (var i in layers) {
        var layer = layers[i];
        // Ensure vector layer
        if (layer.CLASS_NAME == 'OpenLayers.Layer.Vector') {
          var cluster = new OpenLayers.Strategy.Cluster(options);
          layer.addOptions({ 'strategies': [cluster] });
          cluster.setLayer(layer);
          cluster.features = layer.features.slice();
          cluster.activate();
          cluster.cluster();
        }
      }
    }
  }
};

/*
 * Override of callback used by 'popup' behaviour to support clusters
 */
Drupal.theme.openlayersPopup = function(feature) {
  if (feature.cluster)
  {
    var output = '';
    var visited = []; // to keep track of already-visited items
    for (var i = 0; i < feature.cluster.length; i++) {
      var pf = feature.cluster[i]; // pseudo-feature
      if (typeof pf.drupalFID != 'undefined') {
        var mapwide_id = feature.layer.drupalID + pf.drupalFID;
        if (mapwide_id in visited) continue;
        visited[mapwide_id] = true;
      }
      output += '<div class="openlayers-popup openlayers-popup-feature">' +
        Drupal.theme.prototype.openlayersPopup(pf) + '</div>';
    }
    return output;
  }
  else
  {
    return Drupal.theme.prototype.openlayersPopup(feature);
  }
};

})(jQuery);
;

