/* Copyright (c) 2007 Help Service Remote Sensing, s.r.o.
 *  @author Jachym Cepicky <jachym bnhelp cz>
 *  @Licence GNU/GPL
 *
 *  Feel free to reuse and redistribute this code.
 */

/** 
 * @requires OpenLayers/Control.js
 * 
 * Class: OpenLayers.Control.GroupLayerSwitcher
 *
 * Inherits from:
 *  - <OpenLayers.Control>
 */
OpenLayers.Control.GroupLayerSwitcher = 
  OpenLayers.Class(OpenLayers.Control, {

    /**
     * Property: layersDiv
     * {DOMElement} Element, where the layer list is stored
     */
    layersDiv: null,

    outsideViewport: false,

    /**
     * Property: groups
     * {Object} for groups, their titles, names, layers and status
     */
    groups: {},

    /**
     * Property: emptyGroupName
     * {String} className for layers, which are listed in no group
     */
    emptyGroupName: "olNaNGroupName",

    /**
     * Property: mutualExclusion
     * {Boolean} Set, if the groups are mutual disabled
     */
    mutualExclusion: true,

    /**
     * Property: groupTitles
     * {Object} groupName: "Title in layer switcher"
     * If not set, groupName is used
     */
    groupTitles: {},

    /**
     * Property: roundCorner
     * {Boolean} Should the Rico rounding object be used?
     */
    roundCorner: true,

    /**
     * Property: activeColor
     * {String} color of the background
     */
    activeColor: "darkblue",

    /**
     * Property: switcherIsVisible
     * {Boolean} indicates, whether the Maximize and Minimize buttons are
     * drawed
     */
    switcherIsVisible: false,
 

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

    /**
     * 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 
        //FIXME!! this.clearLayersArray("base");
        //FIXME!! this.clearLayersArray("data");
        
        this.map.events.unregister("addlayer", this, this.redraw);
        this.map.events.unregister("changelayer", this, this.redraw);
        this.map.events.unregister("removelayer", this, this.redraw);
        this.map.events.unregister("changebaselayer", this, this.redraw);
        
        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.register("addlayer", this, this.redraw);
        this.map.events.register("changelayer", this, this.redraw);
        this.map.events.register("removelayer", this, this.redraw);
        this.map.events.register("changebaselayer", this, this.redraw);
    },

    /**
     * Method: draw
     *
     * Returns:
     * {DOMElement} A reference to the DIV DOMElement containing the 
     *     switcher tabs.
     */  
    draw: function() {
        if (this.div) {
            this.outsideViewport = true;
        }
        OpenLayers.Control.prototype.draw.apply(this);

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


        return this.div;
    },

    /**
     * Method: initNewGroup
     * Initializes new group structure, appends new object to this.group
     * 
     * Properties:
     * groupName - {String} group identifier
     *
     * Returns:
     * {Object} with new group:
     *    * title String 
     *    * layers Array
     *    * visibility Boolean
     */  
   initNewGroup: function(groupName) {
        var title = this.groupTitles[groupName] ? this.groupTitles[groupName] : groupName;
        this.groupTitles[groupName] = title;
        this.groups[groupName] = {title:title,layers:[],visibility:false};

        return this.groups[groupName];
    },

    /**
     * Method: appendLayerToGroup
     * Adds new layer to group, according to group, defined as Layer attribute
     * 
     * Properties:
     * layer - {<OpenLayer.Layer>} 
     *
     * Returns:
     * {Object} with group:
     *    * title String 
     *    * layers Array
     *    * visibility Boolean
     */  
    appendLayerToGroup: function(layer) {
        var i;
        var groupName = layer.group;

        // create the group, if it does not exist yet
        if (!this.groups[groupName]) {
            this.initNewGroup(groupName);
        }

        // check, if the layer is allready there
        var allreadyThere = false;
        for (i = 0; i < this.groups[groupName].layers.length; i++) {
            if (this.groups[groupName].layers[i] == layer) {
                allreadyThere = true;
                break;
            }
        }

        if (!allreadyThere) {
            this.groups[layer.group].layers.push(layer);
        }

        return this.groups[layer.group];
    },

    /**
     * Method: setGroupVisible
     * Toggle visibility for specified group
     * 
     * Properties:
     * groupName {String}
     *
     */  
    setGroupVisible: function(groupName,visibility) {
        if (!visibility) {
            visibility = true;
        }
        // set visibility to each group
        for (var grp in this.groups) {
            if (grp == groupName) {
                this.groups[grp].visibility = visibility;
            }
            else {
                if (this.mutualExclusion) {
                    this.groups[grp].visibility = false;
                }
            }

        }
        for (var grp in this.groups) {
            // for each layer in the group
            for (var i = 0; i < this.groups[grp].layers.length; i++) {
                var layer = this.groups[grp].layers[i];
                // base layers
                if (layer.isBaseLayer && this.groups[grp].visibility) {
                    this.map.setBaseLayer(layer);
                }
                if (grp != this.emptyGroupName) {
                    layer.setVisibility(this.groups[grp].visibility);
                }
                if (this.groups[grp].li) {
                    this.groups[grp].li.className = (this.groups[grp].visibility ? "olActiveGroupListItem" : "olInactiveGroupListItem"); 
                }
            }
        }
    },

    /**
     * Method: rebuldLayerGroupObject
     * Rebuilds this.groups object according to layers
     * 
     */  
    rebuildLayerGroupObject: function() {

        // for each layer
        for (var i = 0; i < this.map.layers.length; i++) {
            var layer = this.map.layers[i];

            // append layer to group
            if (!layer.group) {
                layer.group = this.emptyGroupName;
            }

            // create group first, if not exsits
            if(!this.groups[layer.group]) {
                this.initNewGroup(layer.group);
            }
            
            // append
            this.appendLayerToGroup(layer);
        }
    },

    /** 
     * Method: redraw
     * Goes through and takes the current state of the Map and rebuilds the
     *     control to display that state. Groups layers into groups. 
     *
     * Returns: 
     * {DOMElement} A reference to the DIV DOMElement containing the control
     */  
    redraw: function(evt) {
        var i;
        var layer;
        var layerLi;
        var layerLabel;
        var layerInput;

        // remove the control, if it allready exists
        if (this.layersDiv !== null) {
            var nodeNumber = this.div.childNodes.length;
            for (i = 0; i < nodeNumber; i++) {
                this.div.removeChild(this.div.childNodes[0]);
            }
            this.layersDiv = null;
        }

        // initialize new elements
        this.layersDiv = OpenLayers.Util.createDiv(undefined, undefined, undefined, undefined, "relative");
        this.layersDiv.className ="olLayersDiv";
        this.div.appendChild(this.layersDiv);

        // rebuild the group structure
        this.rebuildLayerGroupObject();

        // <ul>
        var switcherUl = document.createElement("ul");
        switcherUl.className = "olGroupLayerSwitcherList";
        //switcherUl.style.display = "inline";
        this.layersDiv.appendChild(switcherUl);

        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);

        // make the list with groups and layers
        //
        // for each group
        for (var grp in this.groups) {
            if (grp == this.emptyGroupName) {
                continue;
            }
            var group = this.groups[grp];
            var groupLi = document.createElement("li");
            var groupLabel = document.createTextNode(this.groupTitles[grp]);
            //var groupUl = document.createElement("ul");

            groupLi.className = (group.visibility ? "olActiveGroupListItem" : "olInactiveGroupListItem"); 

            var groupInput = document.createElement("input");
            groupInput.type= (this.mutualExclusion ? "radio" : "checkbox");
            groupInput.name = "olGroupLevel"+0;
            groupInput.className="olGroupCheckbox";

            OpenLayers.Event.observe(groupLi, "mouseup", 
                OpenLayers.Function.bindAsEventListener(
                    this.onGroupInputClick, 
                    {input:groupInput,switcher: this,groupName:grp}));

            groupLi.appendChild(groupInput);
            groupLi.appendChild(groupLabel);

            this.groups[grp].li = groupLi;

            switcherUl.appendChild(groupLi);
            //switcherUl.appendChild(groupUl);
            
            groupInput.checked = group.visibility;

            // for each layer in the group
            for (i = 0; i < group.layers.length; i++) {
                layer = group.layers[i];
                if (!layer.displayInLayerSwitcher) {
                    continue;
                }
                layerLi = document.createElement("li");
                layerLabel = document.createTextNode(layer.name);
                layerInput = document.createElement("input");
                layerInput.type= (layer.isBaseLayer ? "radio" : "checkbox");
                layerInput.name = (layer.isBaseLayer ? "baseLayers" : "overlayLayers");
                layerInput.className="olLayerCheckbox";

                layerLi.className = (layer.visibility ? "olActiveLayerListItem" : "olInactiveLayerListItem"); 

                OpenLayers.Event.observe(layerLi, "mouseup", 
                    OpenLayers.Function.bindAsEventListener(this.onLayerInputClick, {input:layerInput,switcher: this,layer:layer}));

                layerLi.appendChild(layerInput);
                layerLi.appendChild(layerLabel);
                //groupUl.appendChild(layerLi);

                layerInput.checked = layer.visibility;
            }
        }

        // layers without group name
        if (this.groups[this.emptyGroupName] !== undefined) {
            for (i = 0; i < this.groups[this.emptyGroupName].layers.length; i++) {
                layer = this.groups[this.emptyGroupName].layers[i];
                if (!layer.displayInLayerSwitcher) {
                    continue;
                }
                layerLi = document.createElement("li");
                layerLabel = document.createTextNode(layer.name);

                layerInput = document.createElement("input");
                layerInput.type= (layer.isBaseLayer ? "radio" : "checkbox");
                layerInput.name = (layer.isBaseLayer ? "baseLayers" : "overlayLayers");
                layerInput.className="olLayerCheckbox";

                layerLi.className = (layer.visibility ? "olActiveLayerListItem" : "olInactiveLayerListItem"); 

                OpenLayers.Event.observe(layerLi, "mouseup", 
                    OpenLayers.Function.bindAsEventListener(
                        this.onLayerInputClick,
                        {input:layerInput,switcher: this,layer:layer}));

                layerLi.appendChild(layerInput);
                layerLi.appendChild(layerLabel);
                switcherUl.appendChild(layerLi);

                layerInput.checked = layer.visibility;
            }
        }
         
        // create Minimize and Maximize controls
        

        // set mode to minimize
        if(!this.outsideViewport) {
            this.makeMaxMinDivs();
            if (this.switcherIsVisible == true) {
                this.maximizeControl();
            }
            else {
                this.minimizeControl();
            }
        }
        
        return this.div;
    },

    /**
     * Method: makeMaxMinDivs
     * Creates Minimazing and Maximizing controls 
     */
    makeMaxMinDivs: function () {
        
        // round corners
        if (this.roundCorner) {
            OpenLayers.Rico.Corner.round(this.div, {corners: "tl bl",
                                        bgColor: "transparent",
                                        color: this.activeColor,
                                        blend: false});

            OpenLayers.Rico.Corner.changeOpacity(this.layersDiv, 0.75);
        }

        var imgLocation = OpenLayers.Util.getImagesLocation();
        var sz = new OpenLayers.Size(18,18);        

        // maximize button div
        var img = imgLocation + 'layer-switcher-maximize.png';
        this.maximizeDiv = OpenLayers.Util.createAlphaImageDiv(
                                    "OpenLayers_Control_MaximizeDiv", 
                                    null, sz, img, "absolute");
        this.maximizeDiv.style.top = "5px";
        this.maximizeDiv.style.right = "0px";
        this.maximizeDiv.style.left = "";
        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
        var img = imgLocation + 'layer-switcher-minimize.png';
        var sz = new OpenLayers.Size(18,18);        
        this.minimizeDiv = OpenLayers.Util.createAlphaImageDiv(
                                    "OpenLayers_Control_MinimizeDiv", 
                                    null, sz, img, "absolute");
        this.minimizeDiv.style.top = "5px";
        this.minimizeDiv.style.right = "0px";
        this.minimizeDiv.style.left = "";
        this.minimizeDiv.style.display = "none";

        OpenLayers.Event.observe(this.minimizeDiv, "click", 
            OpenLayers.Function.bindAsEventListener(this.minimizeControl, this)
        );

        this.div.appendChild(this.minimizeDiv);
    },

    /** 
     * Method:
     * Called, when the user clickes on the Group name or input
     * 
     * Parameters:
     * e - {Event} 
     *
     * Context:  
     *  - {DOMElement} input
     *  - {<OpenLayers.Control.LayerSwitcher>} switcher
     *  - {String} groupName
     */

    onGroupInputClick: function(e) {

        if (!this.input.disabled) {
            this.switcher.setGroupVisible(this.groupName);
        }
        OpenLayers.Event.stop(e);
    },

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

    onLayerInputClick: function(e) {

        if (!this.input.disabled) {
            if (this.input.type == "radio") {
                this.input.checked = true;
                this.layer.map.setBaseLayer(this.layer);
            } else {
                var checked = this.input.checked ? false : true;
                this.input.checked = checked;
                this.layer.setVisibility(checked);
                this.input.checked = checked;
            }
        }
        OpenLayers.Event.stop(e);
    },
    
    /** 
     * Method: maximizeControl
     * Set up the labels and divs for the control
     * 
     * Parameters:
     * e - {Event} 
     */
    maximizeControl: function(e) {

        //HACK HACK HACK - find a way to auto-size this layerswitcher
        this.div.style.width = "20em";
        this.div.style.height = "";

        this.showControls(false);

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

        this.div.style.width = "0px";
        this.div.style.height = "0px";

        this.showControls(true);

        if (e) {
            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.switcherIsVisible = !minimize;

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

        this.layersDiv.style.display = minimize ? "none" : "";
    },
    
    /** 
     * 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.GroupLayerSwitcher"
});

