/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
 * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
 * full text of the license. */

/** 
 * @requires OpenLayers/Control.js
 * @requires OpenLayers/Events.js
 * @requires OpenLayers/BaseTypes.js
 * @requires OpenLayers/Lang.js
 * @requires OpenLayers/BaseTypes/Size.js
 */

/**
 * Class: OpenLayers.Control.AtlasLayerSwitcher
 *
 * Inherits from:
 *  - <OpenLayers.Control>
 */
OpenLayers.Control.AtlasLayerSwitcher = 
  OpenLayers.Class(OpenLayers.Control, {

    /** 
     * Deprecated. Use css instead	
     * Property: activeColor
     * {String}
     */
    //activeColor: "darkblue",
    
    /**  
     * Property: layerStates 
     * {Array(Object)} Basically a copy of the "state" of the map's layers 
     *     the last time the control was drawn. We have this in order to avoid
     *     unnecessarily redrawing the control.
     */
    layerStates: null,
    

  // DOM Elements
  
    /**
     * Property: layersDiv
     * {DOMElement} 
     */
    layersDiv: null,
    
    /** 
     * Property: baseLayersDiv
     * {DOMElement}
     */
    baseLayersDiv: null,

    /** 
     * Property: baseLayers
     * {Array(<OpenLayers.Layer>)}
     */
    baseLayers: null,
    
    
    /** 
     * Property: dataLbl
     * {DOMElement} 
     */
    dataLbl: null,
    
    /** 
     * Property: dataLayersDiv
     * {DOMElement} 
     */
    dataLayersDiv: null,

    /** 
     * Property: dataLayers
     * {Array(<OpenLayers.Layer>)} 
     */
    dataLayers: null,


    /** 
     * Property: minimizeDiv
     * {DOMElement} 
     */
    minimizeDiv: null,

    /** 
     * Property: maximizeDiv
     * {DOMElement} 
     */
    maximizeDiv: null,
    
    /**
     * APIProperty: ascending
     * {Boolean} 
     */
    ascending: true,
	
	/**
	 * True when the layer switch causes a change to the visibility of a layer, and 
	 * subsequently causes a 'changelayer' or 'changebaselayer' event. We have this 
	 * boolean to ignore these self generated events as they lead to slow performance
	 * as they trigger a complete redraw, when all that was changed was 
	 */
	ignoreNextEvent:false,

    /**
     * Constructor: OpenLayers.Control.LayerSwitcher
     * 
     * Parameters:
     * options - {Object}
     */
    initialize: function(options) {
        OpenLayers.Control.prototype.initialize.apply(this, arguments);
        this.layerStates = [];
		
    },

    /**
     * APIMethod: destroy 
     */    
    destroy: function() {
        
        OpenLayers.Event.stopObservingElement(this.div);

        OpenLayers.Event.stopObservingElement(this.minimizeDiv);
        OpenLayers.Event.stopObservingElement(this.maximizeDiv);

        //clear out layers info and unregister their events 
        this.clearLayersArray("base");
        this.clearLayersArray("data");
        
        this.map.events.un({
            "addlayer": this.checkRedrawEvent,
			
            "changelayer": this.checkRedrawEvent,
            "removelayer": this.checkRedrawEvent,
			
            //"changebaselayer": this.checkRedrawEvent,
            scope: this
        });
        
        OpenLayers.Control.prototype.destroy.apply(this, arguments);
    },
	

	
    /** 
     * Method: setMap
     *
     * Properties:
     * map - {<OpenLayers.Map>} 
     */
    setMap: function(map) {
        OpenLayers.Control.prototype.setMap.apply(this, arguments);
		this.map.events.addEventType("legendChange");
        this.map.events.on({
            "addlayer": this.checkRedrawEvent,
            "changelayer": this.checkRedrawEvent,
            "removelayer": this.checkRedrawEvent,
			// Don't trigger on change base layer as this causes a redraw when all we did was
			// toggle the visibility of a layer from within the layer switcher. This
			// causes very slow visbility toggling (5-15sec for 400 layers).
			// Not listening to this event means that if the base layer state is
			// changed externally the layer switcher will be out of date.
            //"changebaselayer": this.redraw,
            scope: this
        });
    },

	checkRedrawEvent: function(e) {
		// If the event was triggered by our selfs we want to ignore it.
		if (this.ignoreNextEvent) {
			this.ignoreNextEvent = false;
		} else {
			this.redraw();
		}
	},
	
    /**
     * Method: draw
     *
     * Returns:
     * {DOMElement} A reference to the DIV DOMElement containing the 
     *     switcher tabs.
     */  
    draw: function() {
        OpenLayers.Control.prototype.draw.apply(this);

        // create layout divs
        this.loadContents();

        // set mode to minimize
        if(!this.outsideViewport) {
            this.minimizeControl();
        }

        // populate div with current info
        this.redraw();    

        return this.div;
    },

    /** 
     * Method: clearLayersArray
     * User specifies either "base" or "data". we then clear all the
     *     corresponding listeners, the div, and reinitialize a new array.
     * 
     * Parameters:
     * layersType - {String}  
     */
    clearLayersArray: function(layersType) {
        var layers = this[layersType + "Layers"];
        if (layers) {
            for(var i=0, len=layers.length; i<len ; i++) {
                var layer = layers[i];
                OpenLayers.Event.stopObservingElement(layer.inputElem);
                OpenLayers.Event.stopObservingElement(layer.labelSpan);
            }
        }
        this[layersType + "LayersDiv"].innerHTML = "";
        this[layersType + "Layers"] = [];
    },


    /**
     * Method: checkRedraw
     * Checks if the layer state has changed since the last redraw() call.
     * 
     * Returns:
     * {Boolean} The layer state changed since the last redraw() call. 
     */
    checkRedraw: function() {
        var redraw = false;
        if ( !this.layerStates.length ||
             (this.map.layers.length != this.layerStates.length) ) {
            redraw = true;
        } else {
            for (var i=0, len=this.layerStates.length; i<len; i++) {
                var layerState = this.layerStates[i];
                var layer = this.map.layers[i];
                if ( (layerState.name != layer.name) || 
                     (layerState.inRange != layer.inRange) || 
                     (layerState.id != layer.id) || 
                     (layerState.visibility != layer.visibility) ) {
                    redraw = true;
                    break;
                }    
            }
        }    
        return redraw;
    },
    

    /** 
     * Method: redraw
     * Goes through and takes the current state of the Map and rebuilds the
     *     control to display that state. Groups base layers into a 
     *     radio-button group and lists each data layer with a checkbox.
     *
     * Returns: 
     * {DOMElement} A reference to the DIV DOMElement containing the control
     */  
    redraw: function() {
		// If we are not visible don't do anything. By default the redraw
		// function gets called every time a layer is added (even when the
		// layer switcher panel is not visible. This can lead to poor
		// performance on startup  (20x for 100 layers) if we don't perform 
		// the test below.
		if (this.layersDiv.style.display === "none") {
			return this.div;
		}
		this.redrawCount += 1;
		
		// Benchmarking
		//var start = ATLAS.Util.tic();
		//var times = [];
		
        //if the state hasn't changed since last redraw, no need 
        // to do anything. Just return the existing div.
        if (!this.checkRedraw()) { 
            return this.div; 
        } 

        //clear out previous layers 
        this.clearLayersArray("base");
        this.clearLayersArray("data");
        
        var containsOverlays = false;
        var containsBaseLayers = false;
        
        // Save state -- for checking layer if the map state changed.
        // We save this before redrawing, because in the process of redrawing
        // we will trigger more visibility changes, and we want to not redraw
        // and enter an infinite loop.
        var len = this.map.layers.length;
        this.layerStates = [len];
        for (var i=0; i <len; i++) {
            var layer = this.map.layers[i];
            this.layerStates[i] = {
                'name': layer.name, 
                'visibility': layer.visibility,
                'inRange': layer.inRange,
                'id': layer.id
            };
        }    
		
        var layers = this.map.layers.slice();
        if (!this.ascending) { layers.reverse(); }
		
		var baseLayersList = document.createElement("ul");
		var dataLayersList = document.createElement("ul");
		
		start = ATLAS.Util.tic();
		for(i=0, len=layers.length; i<len; i++) {
            layer = layers[i];
            var baseLayer = layer.isBaseLayer;
			
			// Benchmark 
			//if ((i % 20)=== 0) {
			//	times.push(i+":"+ATLAS.Util.elapsed(start));
			//	start = ATLAS.Util.tic();
			//}
			
            if (layer.displayInLayerSwitcher) {

                if (baseLayer) {
                    containsBaseLayers = true;
                } else {
                    containsOverlays = true;
                }    

                // only check a baselayer if it is *the* baselayer, check data
                //  layers if they are visible
                var layerChecked = (baseLayer) ? (layer == this.map.baseLayer) : layer.getVisibility();

				
                // -----------------------------------
				//      Layer visibility check box
				// -----------------------------------
                // create input element
                var inputElem = document.createElement("input");
                //inputElem.id = this.id + "_input_" + layer.name;				
                //inputElem.name = (baseLayer) ? "baseLayers" : layer.name;
				if (baseLayer) {	
					inputElem.name = "baseLayerGroup"
				}
                inputElem.type = (baseLayer) ? "radio" : "checkbox";
                inputElem.value = layer.name;
                inputElem.checked = layerChecked;
                inputElem.defaultChecked = layerChecked;
                
				if (!baseLayer && !layer.inRange) {
                    inputElem.disabled = true;
                }
				
				var inputElemSpan = document.createElement("span");
				inputElemSpan.className = "showLayerSpan";
				inputElemSpan.appendChild(inputElem);
				
				
				
				// -----------------------------
				//     Layer text span
				// -----------------------------
                var labelSpan = document.createElement("span");
                if (!baseLayer && !layer.inRange) {
                    labelSpan.style.color = "gray";
                }
                labelSpan.innerHTML = layer.name;
				labelSpan.className = "layerSpan";
                labelSpan.style.verticalAlign = (baseLayer) ? "bottom" : "baseline";
                
				
				
				// -----------------------------------
				//      Show in legend checkbox
				// -----------------------------------
				// Only create a legend selector if the layer actually has a
				// legend graphic
				var inputshowItemInLegend = null;
				var inputshowItemInLegendSpan = null;
				if (layer.atlasData.legendUrl) {
					checked = layer.atlasData.showItemInLegend;
					inputshowItemInLegend = document.createElement("input");
					//inputshowItemInLegend.id = this.id + "_legend_" + layer.name;
					//inputshowItemInLegend.name = (baseLayer) ? "baseLayers" : layer.name;
					inputshowItemInLegend.className = "showItemInLegendSpan";
					inputshowItemInLegend.type =  "checkbox";
					inputshowItemInLegend.value = layer.name;
					inputshowItemInLegend.checked = checked;
					inputshowItemInLegend.defaultChecked = checked;
					

					if (!layerChecked) {
						inputshowItemInLegend.disabled = true;
					}
					
					inputshowItemInLegendSpan = document.createElement("span");
					inputshowItemInLegendSpan.className = "showItemInLegendSpan";
					inputshowItemInLegendSpan.appendChild(inputshowItemInLegend);
					
					
				}
				
				// --------------------------------
				//     Events for check boxes
				// --------------------------------
                var context = {
                    'inputElem': inputElem,
					'inputshowItemInLegend': inputshowItemInLegend,		// So we can enable the legend 
																		// checkbox if the layer is visible
                    'layer': layer,
                    'layerSwitcher': this
                };
				OpenLayers.Event.observe(inputElem, "mouseup",
                    OpenLayers.Function.bindAsEventListener(this.onInputClick,
                                                            context)
                );
				
				// If there is a legend item to be show setup a call back event on
				// it checkbox
				if (inputshowItemInLegend !== null) {
					var context = {
							'inputshowItemInLegend': inputshowItemInLegend,
							'layer': layer,
							'layerSwitcher': this,
							'map':this.map
						};
						OpenLayers.Event.observe(inputshowItemInLegend, "mouseup",
							OpenLayers.Function.bindAsEventListener(this.onshowItemInLegendClick,
																	context)
						);
				}
				
				// ----------------------------------------------
				//   Place the all the elements in a list item
				// ----------------------------------------------
				// Wrap the input elements in a list element
				var listElement = document.createElement("li");
				listElement.appendChild(inputElemSpan);
				
				if (inputshowItemInLegendSpan !== null) {
					listElement.appendChild(inputshowItemInLegendSpan);
				}
				listElement.appendChild(labelSpan);
				
				
				// Save the list of all the layers sorted into data and base layers
				var groupArray = (baseLayer) ? this.baseLayers : this.dataLayers;
                groupArray.push({
                    'layer': layer,
                    'inputElem': inputElem,
					'inputshowItemInLegend': inputshowItemInLegend,
                    'labelSpan': labelSpan
					
                });
				
				// Add the list item to the correct list
                var groupDiv = (baseLayer) ? baseLayersList : dataLayersList;
				groupDiv.appendChild(listElement);
            }
			
        }
		// Setup the new lists
		this.baseLayersDiv.appendChild(baseLayersList);
		this.dataLayersDiv.appendChild(dataLayersList);

        // if no overlays, dont display the overlay label
        this.dataLbl.style.display = (containsOverlays) ? "" : "none";        
        
        // if no baselayers, dont display the baselayer label
        this.baseLbl.style.display = (containsBaseLayers) ? "" : "none";  
		
		//var footerDiv = document.getElementById("footer");
		//footerDiv.innerHTML = times.join(','); 
        return this.div;
    },

    /** 
     * Method:
     * A label has been clicked, check or uncheck its corresponding input
     * 
     * Parameters:
     * e - {Event} 
     *
     * Context:  
     *  - {DOMElement} inputElem
     *  - {<OpenLayers.Control.LayerSwitcher>} layerSwitcher
     *  - {<OpenLayers.Layer>} layer
     */

    onInputClick: function(e) {

        if (!this.inputElem.disabled) {
            if (this.inputElem.type == "radio") {
                //this.inputElem.checked = true;
				this.layerSwitcher.ignoreNextEvent = true;
                this.layer.map.setBaseLayer(this.layer);
            } else {
                this.inputElem.checked = !this.inputElem.checked;
                //this.layerSwitcher.updateMap();
				this.layerSwitcher.ignoreNextEvent = true;
				this.layer.setVisibility(this.inputElem.checked);
			
				// Update the enabled state of the legend checkbox based on
				// the visbility of the layer. Do this here rather than using
				// on a redraw to improve the performance.
				if (this.inputshowItemInLegend) {
					this.inputshowItemInLegend.disabled = !this.inputElem.checked;
				}
            }
        }
        OpenLayers.Event.stop(e);
    },
	
	onshowItemInLegendClick: function(e) {
        if (!this.inputshowItemInLegend.disabled) {
			this.inputshowItemInLegend.checked = !this.inputshowItemInLegend.checked;
			this.layer.atlasData.showItemInLegend = this.inputshowItemInLegend.checked;
			if (this.map != null) {
				this.map.events.triggerEvent("legendChange", {layer: this.layer});
            }
        }
        OpenLayers.Event.stop(e);
    },
    
    /**
     * Method: onLayerClick
     * Need to update the map accordingly whenever user clicks in either of
     *     the layers.
     * 
     * Parameters: 
     * e - {Event} 
     */
    //onLayerClick: function(e) {
    //    this.updateMap();
    //},


    /** 
     * Method: updateMap
     * Cycles through the loaded data and base layer input arrays and makes
     *     the necessary calls to the Map object such that that the map's 
     *     visual state corresponds to what the user has selected in 
     *     the control.
     */
 /*   updateMap: function() {
		var i, len;
		var layerEntry;
        // set the newly selected base layer        
        for(i=0, len=this.baseLayers.length; i<len; i++) {
            layerEntry = this.baseLayers[i];
            if (layerEntry.inputElem.checked) {
                this.map.setBaseLayer(layerEntry.layer);
            }
        }

        // set the correct visibilities for the overlays
        for(i=0, len=this.dataLayers.length; i<len; i++) {
            layerEntry = this.dataLayers[i];   
			if (layerEntry.layer.visibility !== layerEntry.inputElem.checked) {
				// SetVisibility will trigger a changelayer event, which we
				// don't want to trigger a redraw so ignore this self generated event
				this.ignoreNextEvent = true;
				layerEntry.layer.setVisibility(layerEntry.inputElem.checked);
			}
        }

    }, */

    /** 
     * Method: maximizeControl
     * Set up the labels and divs for the control
     * 
     * Parameters:
     * e - {Event} (optional)
     */
    maximizeControl: function(e) {

        this.showControls(false);
		// only trigger a possible redraw of the layer switcher list
		// when it becomes visible.
		this.redraw();

        if (typeof e !== 'undefined') {
            OpenLayers.Event.stop(e);                                            
        }
    },
    
    /** 
     * Method: minimizeControl
     * Hide all the contents of the control, shrink the size, 
     *     add the maximize icon
     *
     * Parameters:
     * e - {Event} (optional)
     */
    minimizeControl: function(e) {

        this.showControls(true);

        if (typeof e !== 'undefined') {
            OpenLayers.Event.stop(e);                                            
        }
    },

    /**
     * Method: showControls
     * Hide/Show all LayerSwitcher controls depending on whether we are
     *     minimized or not
     * 
     * Parameters:
     * minimize - {Boolean}
     */
    showControls: function(minimize) {

        this.maximizeDiv.style.display = minimize ? "" : "none";
        this.minimizeDiv.style.display = minimize ? "none" : "";

        this.layersDiv.style.display = minimize ? "none" : "";
    },
    
    /** 
     * Method: loadContents
     * Set up the labels and divs for the control
     */
    loadContents: function() {

        //configure main div
		// Use styles in atlas.css
		this.div.className = "olControlAtlasLayerSwitcher";
    
        OpenLayers.Event.observe(this.div, "mouseup", 
            OpenLayers.Function.bindAsEventListener(this.mouseUp, this));
        OpenLayers.Event.observe(this.div, "click",
                      this.ignoreEvent);
        OpenLayers.Event.observe(this.div, "mousedown",
            OpenLayers.Function.bindAsEventListener(this.mouseDown, this));
        OpenLayers.Event.observe(this.div, "dblclick", this.ignoreEvent);


        // layers list div        
        this.layersDiv = document.createElement("div");
        this.layersDiv.id = this.id + "_layersDiv";
		this.layersDiv.className = "layerList";

        this.baseLbl = document.createElement("div");
        this.baseLbl.innerHTML = OpenLayers.i18n("baseLayer");
		this.baseLbl.className = "baseLayerLabel";
        
        this.baseLayersDiv = document.createElement("div");
		this.baseLayersDiv.className = "baseLayers";
        /*this.baseLayersDiv.style.paddingLeft = "10px";*/
        /*OpenLayers.Event.observe(this.baseLayersDiv, "click", 
            OpenLayers.Function.bindAsEventListener(this.onLayerClick, this));
        */
                     

        this.dataLbl = document.createElement("div");
        this.dataLbl.innerHTML = OpenLayers.i18n("overlays");
        this.dataLbl.style.marginTop = "3px";
        this.dataLbl.style.marginLeft = "3px";
        this.dataLbl.style.marginBottom = "3px";
        
        this.dataLayersDiv = document.createElement("div");
        this.dataLayersDiv.style.paddingLeft = "10px";

        if (this.ascending) {
            this.layersDiv.appendChild(this.baseLbl);
            this.layersDiv.appendChild(this.baseLayersDiv);
            this.layersDiv.appendChild(this.dataLbl);
            this.layersDiv.appendChild(this.dataLayersDiv);
        } else {
            this.layersDiv.appendChild(this.dataLbl);
            this.layersDiv.appendChild(this.dataLayersDiv);
            this.layersDiv.appendChild(this.baseLbl);
            this.layersDiv.appendChild(this.baseLayersDiv);
        }    
 
        this.div.appendChild(this.layersDiv);

        /* OpenLayers.Rico.Corner.round(this.div, {corners: "tl bl",
                                        bgColor: "transparent",
                                        color: "darkblue",
                                        blend: false}); */
		
        //OpenLayers.Rico.Corner.changeOpacity(this.div, 0.75);

		var imgLocation = OpenLayers.Util.getImagesLocation();

        // maximize button div
        this.maximizeDiv = OpenLayers.Util.createAlphaImageDiv(
                                    "OpenLayers_Control_MaximizeDiv", 
                                    null, 
                                    new OpenLayers.Size(18,18), 
                                    imgLocation + 'layer-switcher-maximize.png', 
                                    "absolute");
		this.maximizeDiv.className = "maximizeDiv";
        this.maximizeDiv.style.display = "none";
        OpenLayers.Event.observe(this.maximizeDiv, "click", 
            OpenLayers.Function.bindAsEventListener(this.maximizeControl, this)
        );
        
        this.div.appendChild(this.maximizeDiv);

        // minimize button div
        this.minimizeDiv = OpenLayers.Util.createAlphaImageDiv(
                                    "OpenLayers_Control_MinimizeDiv", 
                                    null, 
                                    new OpenLayers.Size(18,18), 
                                    imgLocation + 'layer-switcher-minimize.png', 
                                    "absolute");
		this.minimizeDiv.className = "minimizeDiv";
        this.minimizeDiv.style.display = "none";
        OpenLayers.Event.observe(this.minimizeDiv, "click", 
            OpenLayers.Function.bindAsEventListener(this.minimizeControl, this)
        );

        this.div.appendChild(this.minimizeDiv);
    },
    
    /** 
     * Method: ignoreEvent
     * 
     * Parameters:
     * evt - {Event} 
     */
    ignoreEvent: function(evt) {
        OpenLayers.Event.stop(evt);
    },

    /** 
     * Method: mouseDown
     * Register a local 'mouseDown' flag so that we'll know whether or not
     *     to ignore a mouseUp event
     * 
     * Parameters:
     * evt - {Event}
     */
    mouseDown: function(evt) {
        this.isMouseDown = true;
        this.ignoreEvent(evt);
    },

    /** 
     * Method: mouseUp
     * If the 'isMouseDown' flag has been set, that means that the drag was 
     *     started from within the LayerSwitcher control, and thus we can 
     *     ignore the mouseup. Otherwise, let the Event continue.
     *  
     * Parameters:
     * evt - {Event} 
     */
    mouseUp: function(evt) {
        if (this.isMouseDown) {
            this.isMouseDown = false;
            this.ignoreEvent(evt);
        }
    },

    CLASS_NAME: "OpenLayers.Control.AtlasLayerSwitcher"
});

