/*
 * File: CIESIN.ol
 */

/*
 *  Create namespace object if it doesn't exist
 */
if (typeof CIESIN == "undefined" || !CIESIN) {
  var CIESIN = {};
  CIESIN.ol = {};
} else if (!CIESIN.ol) {
  CIESIN.ol = {};
}

CIESIN.ol.jQueryVer = '1.4';
CIESIN.ol.jQueryUIVer = '1.8';
CIESIN.ol.openLayersVer = '2.9.1';
CIESIN.ol.svrName = 'sedac.ciesin.columbia.edu';

// for local development, manually set to alpha
if(CIESIN.ol.svrName.match(/^<.*>$/)){
  CIESIN.ol.svrName = 'alpha.ciesin.columbia.edu';
}
/*
 * TODO: DECIDE HOW TO DEAL W/ ALL THESE DEPENDENCIES ACROSS THE BOARD
 */
CIESIN.ol.jQueryUrl = 'http://ajax.googleapis.com/ajax/libs/jquery/' + CIESIN.ol.jQueryVer + '/jquery.min.js';
CIESIN.ol.jQueryUiUrl = 'http://ajax.googleapis.com/ajax/libs/jqueryui/' + CIESIN.ol.jQueryUIVer + '/jquery-ui.min.js';
CIESIN.ol.layersUrl = 'http://' + CIESIN.ol.svrName + '/gInc/scripts/layers.js';
CIESIN.ol.oLayersUrl = 'http://' + CIESIN.ol.svrName + '/gInc/scripts/OpenLayers-' + CIESIN.ol.openLayersVer + '/OpenLayers.js';
CIESIN.ol.gistCssUrl = 'http://' + CIESIN.ol.svrName + '/gInc/scripts/gist_ol.css';

// XXX: need to detect problems when loading a script
CIESIN.ol.loadScripts = function(){
  if(typeof jQuery.ui === 'undefined'){
    $.getScript(CIESIN.ol.jQueryUiUrl, function(){
      $.getScript(CIESIN.ol.layersUrl, function(){
        $.getScript(CIESIN.ol.oLayersUrl, CIESIN.ol.loadGistOl);
      });
    });
  }else if(jQuery.ui.version.indexOf('1.8') !== 0){
    CIESIN.error('gist_ol.js depends on jQueryUI v'+CIESIN.ol.jQueryUIVer);
  }else{
    $.getScript(CIESIN.ol.layersUrl, function(){
      $.getScript(CIESIN.ol.oLayersUrl, CIESIN.ol.loadGistOl);
    });
  }
};

if(typeof jQuery === 'undefined'){
  /*
   * Load jQuery, and when done load all other scripts
   */
  CIESIN.ol.loadJq = document.createElement('script');
  CIESIN.ol.loadJq.type = 'text/javascript';
  CIESIN.ol.loadJq.language = 'Javascript';
  CIESIN.ol.loadJq.src = CIESIN.ol.jQueryUrl;

  document.getElementsByTagName('head')[0].appendChild(CIESIN.ol.loadJq);

  if (CIESIN.ol.loadJq.readyState) { //IE
    CIESIN.ol.loadJq.onreadystatechange = function(){
      if (CIESIN.ol.loadJq.readyState == 'loaded' || CIESIN.ol.loadJq.readyState == 'complete') {
        CIESIN.ol.loadJq.onreadystatechange = null;
        CIESIN.ol.loadScripts();
      }
    };
  } else { //Others
    CIESIN.ol.loadJq.onload = function(){
      CIESIN.ol.loadScripts();
    };
  }
}else if($().jquery.indexOf('1.4') !== 0){
  CIESIN.error('gist_ol.js depends on jQuery v'+CIESIN.ol.jQueryVer);
}else{
  CIESIN.ol.loadScripts();
}

CIESIN.ol.loadGistOl = function(){
  /*
   * Load css customizations
   */
  $('<link>',{
    rel: 'stylesheet',
    type: 'text/css',
    media: 'all',
    href: CIESIN.ol.gistCssUrl
  }).appendTo('head');

  OpenLayers.ImgPath = 'http://' + CIESIN.ol.svrName + '/gInc/scripts/OpenLayers-' + CIESIN.ol.openLayersVer + '/img/';

  /*
   * Function: createMap
   *
   * Creates a map with the default controls and zoom level we like.
   * By default the map width will be 100% of containing element, and its height 0.5 of that.
   * Width and height should be overwritten via css.
   *
   * Parameters:
   *    mapId - id of element containing the map
   *    opts - hash of options to override defaults.
   *              An array of layers (CIESIN or OpenLayers WMS), can be passed directly to create & populate a map in one line, see example below.
   *    defaults - boolean, set to false if you don't want default options set.
   *
   * Returns:
   *    OpenLayers.Map object
   *
   * Example:
   *    > var map = CIESIN.ol.createMap('map',{layers:[CIESIN.layers.GPWv3['1990PopDensity']]});
   *
   */
  CIESIN.ol.createMap = function(elemId, opts, defaults){
    var map; // contains openlayers map object
    var mapId = 'ol_mapDiv'; // id passed to create the layer
    var mapContainer = $('#' + elemId);
    var mapDiv = $("<div id='" + mapId + "'>").css('backgroundColor', '#aed0f1').prependTo(mapContainer);
    var panZoomDiv = $("<div id='ol_panZoom'>").appendTo(mapDiv);
    var options = {};

    /*
     * if user didn't set a height, set this default height
     */
    if (mapDiv.height() === 0) {
      mapDiv.height(Math.floor(mapDiv.width() * 0.5));
    }

    /*
     * if body.ol_fullscreen, set proper css rules
     */
    if($('body').hasClass('ol_fullscreen')){
      $('body').css({
        margin: 0,
        padding:0
      });

      $('html, body, #' + elemId + ', #'+ mapId).css({
        width: '100%',
        height: '100%'
      });
    }

    /*
     * default options are ignored if 'defaults' are set to false
     */
    if (typeof defaults === "undefined" || defaults === true) {
      options = {
        controls: [
          new OpenLayers.Control.Navigation(), //{ zoomWheelEnabled: false }
          new OpenLayers.Control.PanZoomBar({ div: document.getElementById('ol_panZoom'), zoomWorldIcon: true }),
          new OpenLayers.Control.LayerSwitcher({ ascending: true }),
          new OpenLayers.Control.KeyboardDefaults()
        ],
        resolutions: [ // defined in GeoWebCache
          2.00167551711953,
          1.0008377585597652,
          0.5004188792798824,
          0.33361258618658834,
          0.16680629309329414,
          0.08340314654664707,
          0.03336125861865884,
          0.016680629309329415,
          0.008340314654664708
        ],
        projection: new OpenLayers.Projection("EPSG:4326")
      };
    }

    /*
     *  Override any defaults with what was passed in
     */
    if (opts && typeof opts == 'object') {
      for (var opt in opts) {
        if (opts.hasOwnProperty(opt)) {
          options[opt] = opts[opt];
        }
      }
    }

    /*
     *  Create map with new set of options
     */
    map = new OpenLayers.Map(mapId, options);

    /*
     *  If user doesn't need to tweak any layers, then they can pass in the CIESIN layers
     *  directly here and generate a map in a single line
     */
    if (options.layers) {
      var olLayers = [];
      for (var layer in options.layers) {
        if (options.layers.hasOwnProperty(layer)) {
          if (options.layers[layer].CLASS_NAME.match(/^OpenLayers\.Layer\./)) {
            olLayers.push(options.layers[layer]);
          }else{
            olLayers.push(CIESIN.ol.createLayer(options.layers[layer]));
          }
        }
      }
      map.addLayers(olLayers);
    }

    map.zoomToMaxExtent();
    /*
     * XXX: use maxExtent, but cannot do this now, since geowebcache
     * will return error when using bounds (-180,-60,180,85)
     */
    map.setCenter(new OpenLayers.LonLat(0,15));

    return map;
  };

  /*
   * Function: createLayer
   *
   * Creates an OpenLayers.Layer.WMS object from a predefined layer in the
   * CIESIN library. A reference to the CIESIN layer will be added as CIESIN_REF
   *
   * Parameters:
   *   layer - CIESIN layer to use
   *   params - any GetMap query parameters to include (the layer name will be included
   *            but if you want multiple WMS layers you will need to specify them here).
   *            If 'transparent' is set to true (boolean), it will automatically set all
   *            the proper parameters (format, quality, etc) for you. See example below.
   *   opts - any OpenLayers options to include
   *   defaults - boolean, set to false if you don't want default options set.
   *
   * Returns:
   *    OpenLayers.Layer.WMS object
   *
   * Example:
   *
   * > var layer = CIESIN.ol.createLayer(CIESIN.layers.GPWv3['1990PopDensity']);
   *
   * > //the following line will properly create a transparent layer
   * > var layer = CIESIN.ol.createLayer(CIESIN.layers.GlobalVectors['nationalbounds'], {transparent: true});
   *
   */
  CIESIN.ol.createLayer = function(layer, params, opts, defaults){
    var olayer = null; // contains openlayers layer object
    var queryParams;
    var options;
    var olayerTitle; // title to be set for layer
    var olayerUrl; // url to be set for layer

    // XXX: we need to handle layers/styles that are comma separated
    queryParams = {
      layers: layer.name,
      format: 'image/png',
      transparent: true
    };

    // these default options are ignore if 'defaults' are set to false
    options = {};
    if (typeof defaults === "undefined" || defaults === true) {
      options = {
        isBaseLayer: true // XXX: test this later /w cached layers --> transitionEffect: "resize"
      };
    }

    /*
     *  Override any param defaults with what was passed in
     */
    if (params && typeof params == 'object') {
      for (var param in params) {
        if (params.hasOwnProperty(param)) {
          queryParams[param] = params[param];
        }
        /* XXX: THIS IS IONIC SPECIFIC, AND PERHAPS NO LONGER NEEDED
        if (param == 'transparent' && params[param] === true) {
          queryParams.transparent = 'TRUE';
          queryParams.format = 'image/png';
          queryParams.quality = '35';
        }
        */
      }
    }

    /*
     *  Override any option defaults with what was passed in
     */
    if (opts && typeof opts == 'object') {
      for (var opt in opts) {
        if (opts.hasOwnProperty(opt)) {
          options[opt] = opts[opt];
        }
      }
    }

    /*
     * in GWC all layers are 1:1 relationship with *style* names
     * so instead of workspace:layer its layer:style
     * TODO: this section needs work. if multiple styles are passed, need to treat that (is that even possible?)
     */
    olayerTitle = layer.title;
    olayerUrl = layer.url;
    if(layer.cached_url){
      olayerUrl = layer.cached_url;
      //remember, name matches *style* name, without workspace
      queryParams.layers = layer.name.split(':')[1]+":default";
      if(typeof queryParams.styles !== "undefined"){
        var style = layer.style[queryParams.styles];
        olayerTitle = style.title;
        if(style.cached_url){
          olayerUrl = style.cached_url;
        }
        queryParams.layers = style.name;
      }
    }

    olayer = new OpenLayers.Layer.WMS(olayerTitle, olayerUrl, queryParams, options);
    layer.olayersID = olayer.id; // XXX: not sure if this is needed anymore... id seems to
    // XXX: ask Frank about changing this
    olayer.CIESIN_REF = layer;

    return olayer;
  };

  /*
   * Function: createCiesinDataLayers
   *
   * Creates a layer, or for multiple layers an array of layers either at the collection level,
   * dataset level, or individual layer level. It's pretty sexy ;)
   *
   * Parameters:
   *  collection - collection HUID
   *  dataset - dataset HUID
   *  layer - layer name
   *
   * Example:
   * > // just ONE layer
   * > var popdens = ol.createCiesinDataLayers('gpw-v3', 'gpw-v3-population-density', 'gpw-v3-population-density_1990');
   *
   * > // ALL the layers in a dataset
   * > var popdens = ol.createCiesinDataLayers('gpw-v3', 'gpw-v3-population-density');
   *
   * > // ALL the layers in a collection
   * > var popdens = ol.createCiesinDataLayers('gpw-v3');
   *
   */
  CIESIN.ol.createCiesinDataLayers = function(collection, dataset, layer){
    // TODO: this returns an array, mixing/matching with single layers won't work.
    // TODO: this should accept params and options as well, perhaps merge into createLayer
    var layerArray = [];
    if(collection && dataset && layer){
      layerArray.push(CIESIN.ol.createLayer(CIESIN.layers[collection].datasets[dataset].layers[layer]));
    }else if(collection && dataset && !layer){
      for (var layer in CIESIN.layers[collection].datasets[dataset].layers){
        if(CIESIN.layers[collection].datasets[dataset].layers.hasOwnProperty(layer)){
          layerArray.push(CIESIN.ol.createLayer(CIESIN.layers[collection].datasets[dataset].layers[layer]));
        }
      }
    }else if(collection && !dataset && !layer){
      for (var dataset in CIESIN.layers[collection].datasets){
        if (CIESIN.layers[collection].datasets.hasOwnProperty(dataset)) {
          for (var layer in CIESIN.layers[collection].datasets[dataset].layers){
            if (CIESIN.layers[collection].datasets[dataset].layers.hasOwnProperty(layer)) {
              layerArray.push(CIESIN.ol.createLayer(CIESIN.layers[collection].datasets[dataset].layers[layer]));
            }
          }
        }
      }
    }

    return layerArray;
  };

  /*
   * Function: layerSwitcher
   *
   * Adds a custom layer switcher, and removes the default one. Currently this will work
   * with Overlays and Base Layers as defined by the map.
   *
   * Current features include: legend support, style support, and better design.
   * Additional features (some almost ready) include: sorting of layers,
   * add/remove layers, set opacity on layers, get abstract, label support, creating
   * custom groups of layers, etc.
   *
   * Parameters:
   *   map - CIESIN map object to build the layer list from.
   *   opts - Hash of options. Currently working options are: width and visible
   *
   *
   * Example:
   * > //creates layer switcher with defaults (18em wide, and initially hidden)
   * > CIESIN.ol.layerSwitcher(map);
   *
   * > //creates layer switcher that is 300px wide, and renders it visible (shown)
   * > CIESIN.ol.layerSwitcher(map, { width: '300px', visible:true });
   *
   */
  CIESIN.ol.layerSwitcher = function (map, opts){
    //remove default layer switcher
    map.removeControl(map.getControlsByClass('OpenLayers.Control.LayerSwitcher')[0]);

    var lyrControlPanel; //parent element, will contain all elements related to layers
    var lyrControlPanelTab; // tab to show/hide layer switcher
    var lyrSwitcherContainer; // contains the actual list of layers, scrollable if needed
    var lyrSwitcher; // layer switcher element

    var addLyrs; // button to add layers
    var lyrSwitcherUl; // top level list containing layers and layer groups

    // array of layers contained in the map
    var lyrSwitcherViewableLayers;
    var overlays;
    var baseLayers;

    /*
     * if element does not exist, create it. if it does, then we just need
     * to update it.
     */
    if(!$('#ol_lyrSwitcher').length){
      lyrSwitcher = $('<div>', {id: 'ol_lyrSwitcher'});
      if(!$("#ol_lyrControlPanel").length){
        lyrControlPanel = $('<div>', {id: 'ol_lyrControlPanel'});

        /*
         * do not propagate events that will trigger map actions
         *
         * XXX: probably no longer needed now that panel is no longer
         * a child of the map element... keep for now
         */
        lyrControlPanel.mousedown(function(e){ e.stopPropagation(); });
        lyrControlPanel.dblclick(function(e){ e.stopPropagation(); });
        lyrControlPanel.disableSelection();

        lyrSwitcherContainer = $("<div id='ol_lyrSwitcherContainer'>").append(lyrSwitcher);
        lyrSwitcherContainer.css({
          maxHeight: $(map.div).height() + 'px',
          display: 'none'
        });

        // ensure layer switcher panel resizes when window resizes, if fullscreen
        $(window).resize(function(){
          lyrSwitcherContainer.css('max-height', $(map.div).height()+'px');
        });

        /*
         * hide/show layerSwitcher
         */
        lyrControlPanelTab = $('<span>',{
          id: 'ol_lyrControlPanelTab',
          'class': 'tabShow',
          click: function(e){
            e.preventDefault();
            $(this).toggleClass('tabShow');
            if(lyrSwitcherContainer.is(':visible')){
              lyrSwitcherContainer.hide();
            }else{
              lyrSwitcherContainer.show();
            }
          }
        });


        lyrControlPanel.append(lyrSwitcherContainer).append(lyrControlPanelTab);
        $(map.div).prepend(lyrControlPanel); // XXX: use global variable
      }

    }else{
      lyrSwitcher = $('#ol_lyrSwitcher');
      lyrSwitcher.empty();
    }

    /* these options will also need to be accessed by CIESIN.ol.layerSwitcherGroup
     * XXX: look into different approach?
     */
    CIESIN.ol.lyrSwitcherOpts = {
      editable: false, // can add/remove/edit layers
      sortable: false, // can order layers by dragging/dropping
      visible: false, // should it be show or hidden
      transparent: false, // should it be slightly transparent
      width: '18em', // its best if we set a width, or panel could possibly be too wide
      /*
       * XXX: determine if we set this in html via style, or hash of properties
       */
      printVisibleLayers: false // prints to element with id of 'ol_printVisibleLayers'
    };

    /*
     *  Override any option defaults with what was passed in
     */
    if (opts && typeof opts == 'object') {
      for (var opt in opts) {
        if (opts.hasOwnProperty(opt)) {
          CIESIN.ol.lyrSwitcherOpts[opt] = opts[opt];
        }
      }
    }

    if(CIESIN.ol.lyrSwitcherOpts.printVisibleLayers && $('#ol_printVisibleLayers').length){
      CIESIN.ol.printVisibleLayers(map);
    }

    if(CIESIN.ol.lyrSwitcherOpts.editable){
      addLyrs = $('<input>',{
        type: 'button',
        value: 'Add Layers',
        click: function(){
          CIESIN.ol.lolPanel(map);
        }
      }).prependTo(lyrSwitcher);
    }

    if (CIESIN.ol.lyrSwitcherOpts.visible) {
      lyrControlPanelTab.click(); // just trigger click event to open
    }

    if (typeof CIESIN.ol.lyrSwitcherOpts.width === 'number' ||
      (typeof CIESIN.ol.lyrSwitcherOpts.width === 'string' && CIESIN.ol.lyrSwitcherOpts.width.match(/^([0-9]+)(px|em|pt)$/))) {
      lyrSwitcherContainer.css('width', CIESIN.ol.lyrSwitcherOpts.width);
    } else {
      CIESIN.error("gistLayerSwitcher(): 'width' must be a number or a string of a proper css value");
    }

    if(CIESIN.ol.lyrSwitcherOpts.transparent && $.support.opacity){
      lyrSwitcherContainer.css('backgroundColor', 'rgba(255,255,255,0.95)');
    }

    lyrSwitcherUl = $('<ul>', {id: 'ol_lyrList'}).appendTo(lyrSwitcher);

    /* must reverse array of overlays, since they are visible from the bottom up
     * no need to reverse baselayers, better if left as-is.
     */
    overlays = map.getLayersBy('isBaseLayer', false).reverse();
    baseLayers = map.getLayersBy('isBaseLayer', true);

    /*
     * remove layers from array that should not be displayed in layer switcher
     */
    var rmCountOverlays = 0;
    $(overlays).each(function(i, lyr){
      if(!lyr.displayInLayerSwitcher){
        overlays.splice(i-rmCountOverlays,1);
        rmCountOverlays++;
      }
    });
    var rmCountBaseLayers = 0;
    $(baseLayers).each(function(i, lyr){
      if(!lyr.displayInLayerSwitcher){
        baseLayers.splice(i-rmCountBaseLayers,1);
        rmCountBaseLayers++;
      }
    });

    if(overlays.length >= 1){
      CIESIN.ol.layerSwitcherGroup(map, overlays,{
        type: 'checkbox',
        name: 'Overlays',
        acceptLol: true
      });
    }

    if(baseLayers.length >= 1){
      CIESIN.ol.layerSwitcherGroup(map, baseLayers, {
        name: 'Base Layers'
      });
    }

    map.updateSize(); // XXX: what is this for again?
  };

  /*
   * Function: layerSwitcherGroup
   *
   * Creates a single group of layers for the layer switcher. Each group can be of two types;
   * checkbox or radio. The names can be customizable. Currently this method cannot be used
   * to create custom groups, and is only being used by layerSwitcher to create the default
   * groups.
   *
   * Parameters:
   *   map - CIESIN map object
   *   lyrArray - An array of OpenLayers layer objects that makes up the group.
   *   opts - Hash of options. Current options are: type, name, acceptLol
   *
   * Returns:
   *    (Currently nothing, later will return DOM objects)
   *
   * Example:
   * > //by default the group is set to type 'radio', this will creates the base layers group
   * > var layer = CIESIN.ol.layerSwitcherGroup(map, baseLayersArray, { name: 'Base Layers' });
   *
   * > //this will create a group for the Overlays
   * > var layer = CIESIN.ol.layerSwitcherGroup(map, overlaysArray, { name: 'Overlays', type: 'checkbox' });
   *
   */
  CIESIN.ol.layerSwitcherGroup = function(map, lyrArray, opts){
    // XXX: add logic to make sure custom layer switcher exists, and fail gracefully
    var lyrSwitcherUl = $('#ol_lyrList');
    var lgndDiv,lgndImg;
    var options;

    options = {
      type: 'radio',
      name: 'Layer Group'
    };

    /*
     *  Override any option defaults with what was passed in
     */
    if (opts && typeof opts == 'object') {
      for (var opt in opts) {
        if (opts.hasOwnProperty(opt)) {
          options[opt] = opts[opt];
        }
      }
    }

    if(lyrArray.length){
      var groupUl = $('<ul>');
      if(typeof options.acceptLol != 'undefined' && options.acceptLol === true){
        groupUl.addClass('acceptLol');
      }

      $(lyrArray).each(function(i, lyr){
        var tableElem = $('<table><tr><td class="ol_lyrVis"></td><td class="ol_lyrInf"></td></tr></table>');
        var ol_lyrVis = tableElem.find('.ol_lyrVis');
        var ol_lyrInf = tableElem.find('.ol_lyrInf');

        var visTglName = (options.type == 'radio') ? 'lyrGroup'+lyrSwitcherUl.children().length : 'vis_toggle';

        /*
         * layer name
         * XXX: add 'for' attribute later
         */
        var ol_lyrName = $('<label>',{
          'class': 'ol_lyrName'
        }).text(lyr.name);
        ol_lyrInf.append(ol_lyrName);

        /*
         * visibility toggle
         */
        var visTgl = $('<input>', {
          type: options.type,
          name: visTglName,
          value: lyr.id,
          click: function(e){
            e.stopPropagation();
            if(lyr.getVisibility()){
              if (options.type == 'checkbox') {
                lyr.setVisibility(false);
                ol_lyrName.toggleClass('disabled');
              }
            }else{
              ol_lyrName.toggleClass('disabled');
              if (options.type == 'checkbox') {
                lyr.setVisibility(true);
              }else{
                /*
                 *  set the new base layer, do not toggle visibility (for base layers)
                 */
                map.setBaseLayer(lyr);
                // add 'disabled' class to all others in group
                $("input[name='"+visTglName+"']:not(:checked)").each(function(){
                  var other = $(this).parent().parent().find('.ol_lyrName');
                  if(!other.hasClass('disabled')){
                    other.addClass('disabled');
                  }
                });
              }
            }

            if(CIESIN.ol.lyrSwitcherOpts.printVisibleLayers && $('#ol_printVisibleLayers').length){
              CIESIN.ol.printVisibleLayers(map);
            }
          }
        }).appendTo(ol_lyrVis);

        /*
         * Populate the list item for this layer with all the controls
         */
        var liElem = $('<li>', {
          'class': 'ol_lyrItem',
          click: function(e){
            e.stopPropagation();
            if(visTgl.is(':radio')){
              if(visTgl.not(':checked')){
                visTgl.attr('checked', 'checked');
                visTgl.click();
              }
            }else if(visTgl.is(':checkbox')){
              visTgl.click();
            }
          }
        }).appendTo(groupUl);

        tableElem.appendTo(liElem);

        // this div will contain items that should show/hide depending on lyr visibility
        var ol_lyrDtls = $("<div class='ol_lyrDtls'>").appendTo(ol_lyrInf);

        /*
         * initial states for visible/hidden layers
         * and toggle showing/hiding details
         */
        if (lyr.getVisibility()) {
          visTgl.attr('checked', 'checked');
        } else {
          ol_lyrName.addClass('disabled');
          ol_lyrDtls.hide();
        }

        lyr.events.register("visibilitychanged", lyr, function(){
          if (this.getVisibility()) {
            ol_lyrDtls.slideDown('fast');
          } else {
            ol_lyrDtls.slideUp('fast');
          }
        });

        // legend image, and multiple style support only if they are CIESIN layers.
        if(typeof lyr.CIESIN_REF !== 'undefined'){
          /*
           * legend image
           * TODO: remove this forceLegend idea.
           */
          if(typeof lyr.CIESIN_REF.legend !== 'undefined' && lyr.CIESIN_REF.legend.url !== null){
            if(typeof lyr.CIESIN_REF.forceLegend === 'undefined'){
              /*
               * XXX: MAKE THIS A FUNCTION THAT RETURNS URL
               */
              var lgndReq = lyr.CIESIN_REF.legend.url.split('?')[0]+"?request=GetLegendGraphic&format=image%2Fpng";
              var lgndReqStyle = (lyr.params.STYLES == "") ? "" : "&style="+lyr.params.STYLES;
              var lgndReqLyr = "&layer="+lyr.CIESIN_REF.name.split(":")[1];
              var lgndReqSize = "&width=15&height=15";
              var lgndReqOptions = "&legend_options=border:false;mx:0.05;my:0.02;dx:0.2;dy:0.07;fontSize:11;bandInfo:false;";
              var lgndUrl = lgndReq + lgndReqLyr + lgndReqStyle + lgndReqSize + lgndReqOptions;
            }else{
              /*
               * XXX: this is currently a hack. there are a few issues with geoserver legend support
               * and until we patch and fix these, we'll need to be able to force custom legends.
               */
              var lgndUrl = 'http://' + CIESIN.ol.svrName + '/gInc/scripts/gist_legend_fixes/' + lyr.CIESIN_REF.forceLegend;
            }
            lgndDiv = $("<div class='ol_lgndDiv'>").appendTo(ol_lyrDtls);
            lgndLoadImg = $("<img>", {
              'class': 'ol_lgndLoadImg',
              src: 'http://' + CIESIN.ol.svrName + '/gInc/scripts/OpenLayers-' + CIESIN.ol.openLayersVer + '/img/loading_16.gif'
            }).appendTo(lgndDiv);
            lgndImg = $("<img class='ol_lgndImg' src='"+lgndUrl+"' />").appendTo(lgndDiv).hide();
            lgndImg.load(function(){
              // TODO: make this a function to use w/ select change event below
              $(this).parent().find('.ol_lgndLoadImg').hide();
              $(this).show();
            });
          }

          /*
           * list available styles, if layers has more than one
           * XXX: see if we can do this without iterating through object twice
           */
          var hasMultipleStyles = false;
          if(lyr.params.STYLES == ""){
            var num = 0;
            for (var style in lyr.CIESIN_REF.style){
              if(lyr.CIESIN_REF.style.hasOwnProperty(style)){
                num += 1;
                if(num > 1){
                  hasMultipleStyles = true;
                  break;
                }
              }
            }
          }
          if(hasMultipleStyles){
            var styleSelect = $('<select>', {
              css: {
                width: $('#ol_lyrSwitcherContainer').width() - 40 + 'px' // XXX: don't hard code this
              },
              change: function(){
                var newStyle = $(this).val();
                var newName = $(this).text();
                // when not cached, we merge the style -- if cached, a style name in gwc is the layer name

                /*
                 * TODO: if there is a mismatch between layer cached_url and the style cached_url,
                 * nothing will happen here -- because the parent url will be used.
                 * perhaps there is something we can do at the apache level?
                 */
                if(lyr.CIESIN_REF.style[newStyle].cached_url === null){
                  lyr.mergeNewParams({ styles: newStyle });
                }else{
                  lyr.mergeNewParams({ layers: newStyle });
                }
                var thisLgnd = $(this).parent().find('.ol_lgndImg');
                thisLgnd.hide();
                thisLgnd.parent().find('.ol_lgndLoadImg').show();
                thisLgnd.attr('src', lgndUrl+'&style='+newStyle).load(function(){
                  $(this).parent().find('.ol_lgndLoadImg').hide();
                  $(this).show();
                });
              }
            });
            for(var s in lyr.CIESIN_REF.style){
              if(lyr.CIESIN_REF.style.hasOwnProperty(s)){
                var title = lyr.CIESIN_REF.style[s].title;
                var styleOpt = $('<option>', { value: s, 'title': title }).text(title);
                if(s.split(":")[1] == 'default'){
                  styleOpt.attr('selected', 'selected');
                  styleOpt.text('Default Style');
                  styleSelect.prepend(styleOpt);
                }else{
                  styleSelect.append(styleOpt);
                }
              }
            }
            lgndDiv.before(styleSelect);
          }
        }// if lyr.CIESIN_REF is defined (if is CIESIN layer)

        /*
         * Only add the following items if layer switcher is marked as editable
         */
        if (CIESIN.ol.lyrSwitcherOpts.editable) {
          // cell for meny options should not be created unless editable
          ol_lyrVis.after($('<td class="ol_lyrOpts"></td>'));
          var ol_lyrOpts = tableElem.find('.ol_lyrOpts');

          /*
           * Layer Menu
           */
          var lyrMenu = $('<ul class="ol_lyrMenu"><li><img src="imgs/gear_icon.gif" alt="layer options"/></li></ul>');
          lyrMenu.find('img').click(function(){
            lyrMenuOpts.toggle();
          });
          var lyrMenuOptsTimeOut;
          var lyrMenuOpts = $('<ul>', {
            mouseover: function(){
              clearTimeout(lyrMenuOptsTimeOut);
            },
            mouseout: function(){
              lyrMenuOptsTimeOut = setTimeout(function(){ lyrMenuOpts.hide(); }, 400);
            }
          }).hide();
          lyrMenu.find('img').after(lyrMenuOpts);
          ol_lyrOpts.append(lyrMenu);

          /*
           * remove layer
           */
          var removeLyr = $('<a>', {
            href: '#',
            title: 'Remove Layer',
            click: function(){
              e.preventDefault();
              var thisLyr = map.getLayer(lyr.id);
              thisLyr.destroy();
              CIESIN.ol.layerSwitcher(map);
            }
          }).text('Remove Layer');
          lyrMenuOpts.append($('<li>').append(removeLyr));

          /*
           * Change layer opacity
           */
          if (options.type == 'checkbox') {
            var curOpacity;
            if(lyr.opacity !== null){
              curOpacity = lyr.opacity*100;
            }else{
              curOpacity = 100;
            }
            var opacityCtrl = $('<div class="ol_lyrOpacity">').text('Opacity:');
            var opacityInput = $('<input>', {
              type: 'text',
              size: '3',
              maxlegnth: '3',
              value: curOpacity
            }).appendTo(opacityCtrl);

            var opacitySlider = $('<div>').css('width', '90%').slider({
              min:1,
              max:100,
              value:curOpacity,
              slide:function(e, u){
                opacityInput.attr('value', u.value);
                lyr.setOpacity(u.value*0.01);
              }
            }).appendTo(opacityCtrl);
            lyrMenuOpts.append($('<li>').append(opacityCtrl));
          }

          /*
           * Metadata Link
           */
          if (typeof lyr.CIESIN_REF !== 'undefined' && lyr.CIESIN_REF.metadata !== null) {
            var metadata = $('<a>', {
              href: '#',
              title: 'View Metadata'
            }).text('View Metadata');
            lyrMenuOpts.append($('<li>').append(metadata));
          }
        }// only if editable
      });

      //add group to layer switcher
      lyrSwitcherUl.append($('<li>').text(options.name).append(groupUl));

      /*
       * handle sorting of layers, and addition of layers from LOL panel.
       * this is only available is 'sortable' is set to true
       */
      if(options.type == 'checkbox' && CIESIN.ol.lyrSwitcherOpts.sortable){
        groupUl.sortable({
          axis: 'y',
          opacity: 0.75,
          /* look into later...
          helper: function(ev, ui){
            var helper = ui.remove('.lgnd_div').clone();
            return helper;
          },
          */
          receive: function(ev, ui){
            ui.item.draggable('disable');
            var lyrPkg = ui.item.find('input').attr('name');
            var lyrName = ui.item.find('input').attr('value');
            var newLyr = CIESIN.ol.createLayer(CIESIN.layers[lyrPkg][lyrName], { transparent: true }, { isBaseLayer: false });
            map.addLayer(newLyr);
          },
          start: function(ev, ui){
            ui.item.data('startIndex', groupUl.children().index(ui.item));
          },
          stop: function(ev, ui){
            var startIndex = ui.item.data('startIndex');
            var endIndex = groupUl.children().index(ui.item);
            var curLyr;

            /*
             * XXX: REWORK THIS CODE, NOT TO USE olayersID -- it no longer exists, need better approach
             * this code is only required for dragging items from the lolpanel to the layerswitcher
             *
            if (ui.item.hasClass('ui-draggable')) {
              var lyrPkg = ui.item.find('input').attr('name');
              var lyrName = ui.item.find('input').attr('value');
              var lyrId = CIESIN.layers[lyrPkg][lyrName].olayersID; // XXX: figure this out IMPORTANT
              curLyr = map.getLayer(lyrId);
              map.raiseLayer(curLyr, (startIndex-endIndex)-(startIndex+map.getLayersBy('isBaseLayer', true).length)); ///////// the +1 is hard coded for now....
              CIESIN.ol.layerSwitcher(map);
            }else{
            */
              if(startIndex != endIndex){
                curLyr = map.getLayer(ui.item.find('input').val());
                map.raiseLayer(curLyr, startIndex-endIndex);
              }
            /*
            }
            */
          }
        }).disableSelection();
      }
    }// if lyrArray.length
  };

  /*
   * Function: lolPanel
   *
   * ************************************************
   * THIS IS CURRENTLY IN ROUGH STAGES OF DEVELOPMENT
   * AND NOT READY FOR PRODUCTION USE
   * ************************************************
   */
  CIESIN.ol.lolPanel = function(map){
    /*
     * if element does not exist, create it. if it does, then we just need
     * to update it.
     */
    var lolPanel;
    if(!$('#ol_lolPanel').length){
      lolPanel = $('<div>').attr('id', 'ol_lolPanel');
      $("#ol_lyrControlPanel").append(lolPanel);
    }else{
      lolPanel = $('#ol_lolPanel');
      lolPanel.empty();
    }


    var lolPanelUl = $('<ul>');
    lolPanel.prepend(lolPanelUl);

    $.each(CIESIN.layers, function(pkg){
      var lolPkgLi = $('<li>').text(pkg).appendTo(lolPanelUl);
      var pkgLyrsUl = $('<ul>').appendTo(lolPkgLi);

      $.each(this, function(lyr){
        var lyrLi = $('<li>').text(this.title).appendTo(pkgLyrsUl);
        lyrLi.draggable({
          connectToSortable: '.acceptLol',
          helper: 'clone',
          revert: 'invalid'
        }).disableSelection();

        var input = $('<input>').attr({
         type: 'hidden',
         name: pkg,
         value: lyr
        }).prependTo(lyrLi);

        /*
         * XXX: Rewrite not using olayersID -- no longer exists, need better way to do this.
         *
        if(typeof this.olayersID != "undefined"){
          lyrLi.draggable('disable');
        }
        */

      }); //each lyr
    }); //each pkg
  };

  /*
   * Function: addLegends
   *
   * ************************************************
   * THIS IS CURRENTLY IN ROUGH STAGES OF DEVELOPMENT
   * AND NOT READY FOR PRODUCTION USE
   * ************************************************
   *
   * Adds legends to div#ol_legends (creating it inside the map if it doesn't exist).
   * If only the map object is passed it will loop through all of it's layers and add a legend for
   * each layer that has one.  If an optional array of CIESIN layers is passed, then legends are added
   * only for those layers.
   *
   * If #ol_title element is found the map title will be placed there (which is based on the current base layer).
   *
   * The function can safely be called again after adding new layers without duplicating legends.
   *
   * Parameters:
   *   map - OpenLayers map object
   *   layers - optional array of CIESIN layers
   *
   * Example:
   *
   * > CIESIN.ol.addLegends(map);
   */
  CIESIN.ol.addLegends = function(map, layers){
    var lyrArr = [];

    // Add legends div if does not exists
    if (!$('#ol_legends').length) {
      /*
       * Currently working on the code in this if statment
       * ==============================================
       * === please leave all commented code as-is ====
       * ==============================================
       */
      var lgnd = $('<div>').attr('id', 'ol_legends');
      //var lgndToggle = $('<p>'('p')).text('x');
      var lgndDiv = $('<div>');
      //var lgndDivW = null;
      //lgnd.prepend(lgndToggle).append(lgndDiv);
      lgnd.append(lgndDiv);

      /*
      lgndToggle.click(function(){
        if (lgndDiv.width() != 0) {
          if (lgndDivW === null) {
            lgndDivW = lgndDiv.width();
          }
          lgndDiv.animate({
            width: 0,
            marginLeft: "0.5em",
            opacity: 0
          }, "slow");
        }
        else {
          lgndDiv.animate({
            width: lgndDivW + 'px',
            marginLeft: 0,
            opacity: 100
          }, "slow");
        }
      });
      */
      $(map.div).prepend(lgnd);
    }else{
      $('#ol_legends div').empty();
    }


    if (layers && typeof layers == 'object') {
      $(layers).each(function(){
        var curTitle;
        curTitle = (this.CLASS_NAME === "OpenLayers.Layer.WMS") ? this.CIESIN_REF.title : this.title;

        if (map.getLayersByName(curTitle).length) {
          lyrArr.push(map.getLayersByName(curTitle)[0]);
        } else {
          CIESIN.warn("The following CIESIN layer, " + curTitle + ", does not exist in the OpenLayers map object");
        }
      });
    } else {
      lyrArr = map.layers;
    }

    $(lyrArr).each(function(){
      /*
       * Add all legend images, but do not display them
       */
      if (this.CIESIN_REF.legend && this.CIESIN_REF.legend.url) { //// && $('#' + this.CIESIN_REF.name).length == 0
        //replace colons with underscores or will cause problems with jQuery selectors
        this.CIESIN_REF.name = this.CIESIN_REF.name.replace(/:/g, "_");
        var img = $('<img>').attr({
          id: this.CIESIN_REF.name,
          alt: this.name + ' legend',
          src: this.CIESIN_REF.legend.url
        }).hide();
        $('#ol_legends div').append(img);

        /*
         * Change legend when changing layer
         * Note: for base layers, this event does not register
         * when the layer is shown (v 2.8). showing the legend for
         * base layers is taken care of with the 'changebaselayer' event below.
         */
        this.events.register("visibilitychanged", this, function(){
          if (this.getVisibility()) {
            $('#' + this.CIESIN_REF.name).show();
          } else {
            $('#' + this.CIESIN_REF.name).hide();
          }
        });

        if (this.options.isBaseLayer) {
          map.events.register("changebaselayer", this, function(){
            if (this.getVisibility()) {
              // this really should be handled with 'visibilitychanged'
              // as of version 2.8 of oLayers this is not possible
              $('#' + this.CIESIN_REF.name).show();
              if ($("#ol_title").length) {
                $("#ol_title").text("Base Layer: " + this.name);
              }
            }
          });
        }

        // display visible legends, and map title
        if (this.visibility === true) {
          $('#' + this.CIESIN_REF.name).show();
          if (this.isBaseLayer) {
            if ($("#ol_title").length) {
              $('#ol_title').text("Base Layer: " + this.CIESIN_REF.title);
            }
          }
        }
      }
    });

    if($.browser.msie){
      $('#ol_legends').css('background-color', 'white');
    }

    return null;
  };

  /*
   * Function: printVisibleLayers
   */
  CIESIN.ol.printVisibleLayers = function(map){
    var overlays = [];
    var baseLayer = null; // can only hold one layer
    var lyrsStr = '';

    $(map.getLayersBy('visibility', true)).each(function(){
      // don't print layers not displayed in switcher or cartographic layers
      if(this.displayInLayerSwitcher === true && !this.CIESIN_REF.name.match(/^cartographic:/)){
        if(this.isBaseLayer){
          baseLayer = this.name;
        }else{
          overlays.push(this.name);
        }
      }
    });

    /*
     * reverse overlays to match visible order (instead of draw order)
     * and add the base layer at the end of the array
     */
    overlays.reverse();
    if(baseLayer !== null){
      overlays.push(baseLayer);
    }

    $(overlays).each(function(index){
      if(index === 0){ // the first one
        lyrsStr = lyrsStr + this;
      }else if(overlays.length === 2 && index === 1){ // last one when only two
        lyrsStr = lyrsStr + ' and ' + this;
      }else if(overlays.length > 2 && index === overlays.length-1){ // last one when multiple
        lyrsStr = lyrsStr + ', and ' + this;
      }else if(overlays.length > 2){ // the in-betweens when multiple
        lyrsStr = lyrsStr + ', ' + this;
      }
    });

    $('#ol_printVisibleLayers').text(lyrsStr);
  };

  /*
   * Function: getLayer
   *
   * Searches map's layers and returns the layer with the name passed as a parameter.
   *
   * Parameters:
   *    map - OpenLayers.Map object
   *    layerName - String name of layer
   *
   * Returns:
   *    OpenLayers.Layer object if found, otherwise null.
   *
   */
  CIESIN.ol.getLayer = function(map, layerName){
    var nbrLayers = map.getNumLayers();
    var layer = map.layers[0];
    for (var index = 0; index < nbrLayers; index++) {
      layer = map.layers[index];
      if (layerName == layer.name) {
        return layer;
      }
    }
    return null;
  };

  /*
   * Function: isDuplicateLayer
   *
   * Searches map's layers and returns true if the layer with the name passed as a parameter already exists, otherwise false.
   *
   * Parameters:
   *    map - OpenLayers.Map object
   *    layerName - String name of layer
   *
   * Returns:
   *    true if the layer with the name passed as a parameter already exists, otherwise false.
   *
   */
  CIESIN.ol.isDuplicateLayer = function(map, layerName){
    var layer = CIESIN.ol.getLayer(map, layerName);
    if (null !== layer) {
      return true;
    }
    return false;
  };

  /*
   * Function: removeLayer
   *
   * Removes the layer with the name passed as a parameter from the map.
   *
   * Parameters:
   *    map - OpenLayers.Map object
   *    layerName - String name of layer
   *
   */
  CIESIN.ol.removeLayer = function(map, layerName){
    var layer = CIESIN.ol.getLayer(map, layerName);
    if (null !== layer) {
      map.removeLayer(layer, false);
    }
  };

  /*
   * Function: hideLayer
   *
   * Hides the layer with the name passed as a parameter.
   *
   * Parameters:
   *    map - OpenLayers.Map object
   *    layerName - String name of layer
   *
   */
  CIESIN.ol.hideLayer = function(map, layerName){
    var layer = CIESIN.ol.getLayer(map, layerName);
    if (null !== layer) {
      layer.setVisibility(false);
    }
  };

  /*
   * Function: showLayer
   *
   * Searches map's layers for a layer with the name passed as a parameter and shows the layer if it is found.
   *
   * Parameters:
   *    map - OpenLayers.Map object
   *    layerName - String name of layer
   *
   */
  CIESIN.ol.showLayer = function(map, layerName){
    var layer = CIESIN.ol.getLayer(map, layerName);
    if (null !== layer) {
      layer.setVisibility(true);
    }
  };

  /*
   * Function: info
   *
   * Throws info msg in js console
   *
   * Parameters:
   *    msg - Info message to be displayed
   *
   */
  CIESIN.info = function(msg){
    if (console) {
      console.info(msg);
    }
  };

  /*
   * Function: warn
   *
   * Throws warn msg in js console
   *
   * Parameters:
   *    msg - Warning message to be displayed
   *
   */
  CIESIN.warn = function(msg){
    if (console) {
      console.warn(msg);
    }
  };

  /*
   * Function: error
   *
   * Throws error msg in js console
   *
   * Parameters:
   *    msg - Error message to be displayed
   *
   */
  CIESIN.error = function(msg){
    if (console) {
      console.error(msg);
    }
  };

  /*
   * Wait for CIESIN.ol.init, when found, execute it.
   * If not found after 20 attempts (5 seconds in total), stop and return an error.
   */
  CIESIN.ol.initIntervalCounter = 0;
  CIESIN.ol.initInterval = setInterval(function(){
    CIESIN.ol.initIntervalCounter++;
    if (CIESIN.ol.init) {
      CIESIN.ol.init();
      clearInterval(CIESIN.ol.initInterval);
    }else if (CIESIN.ol.initIntervalCounter > 20) {
      clearInterval(CIESIN.ol.initInterval);
      CIESIN.info("CIESIN.ol.init function not defined in this document");
    }
  }, 250);

};//loadGistOl

