/***************************************************************************************************************
Revisions:
Rev: 388(svn Rev)
Function: onLayerCheckboxChange
Changes: If the layer is a base class, we check to see if the check state being passed in is false, if so we
          don't do anything since a base layer must be checked at all times.
          
***************************************************************************************************************/
/*################################################################################################
 There are 3 openLayers.Layer options I've added to the options hash:
  LAYERURL: The is the link to the data provider. I use this in the tree control to make the check box text clickable.
  GROUP: This is used by the layer tree to group the layers together in a parent/leaf arrangement. All like GROUPS end up under the same parent.
  QUERYABLE: Specifies if a given layer is clickable for a GetFeatureInfo request. Will popup an Info icon next to the active query layer.
  legend: Specifies the image file to use as the legend in the layer tree control.
################################################################################################*/

OpenLayers.Control.LayerSwitcherExt = OpenLayers.Class(OpenLayers.Control, 
{
    /**
     * Property: redrawing
     * {Boolean} indicates, if currently checked WMS or MapServer layer
     * is redrawed.
     */
    redrawing: false,

    /**
     * Property: subLayer
     * {Object} currently redrawed MapServer or WMS sublayer
     */
    subLayer: null,

    /**
     * Property: groups
     * {Object} for groups, their titles, names, layers and status
     * The later generated Ext.trees are generated based on this object.
     */
    groups: {},

    /**
     * Property: groupList
     * {Object} List of groups, which does not have a hierarchy
     */
    groupList: {},

    /**
     * Property: emptyGroupName
     * {String} className for layers, which are listed in no group
     */
    emptyGroupName: ".",

    /**
     * Property: titles
     * {Object} groupName: "Title in layer switcher"
     * If not set, groupName is used
     * Example: {base:"Base layers",aerial:"Remote sensing data"}
     */
    titles: {},

    /**
     * Property: legendNodes
     * {Array} 
     * List of legend nodes
     */
    legendNodes: [],

    /**
     * Property: switcherDivId
     * {String} identifier for the HTML element (div), where the layer
     * switcher should be created
     */
    switcherDivId: "",
    
    layerInfoIcon: "x-tree-node-icon-info",
    
    layerSelectedInfoIcon: "x-tree-node-icon-infoselected",
    
    layerLegend: 'x-tree-node-legend-icon',

    initialize: function(switcherDivId,options,LayerStateChangeCallback) 
    {
        this.switcherDivId = switcherDivId;
        OpenLayers.Control.prototype.initialize.apply(this, arguments);
        this.layerStates = [];
        this.LayerStateChangeCallback = LayerStateChangeCallback;
    },
    destroy : function ()
    {
        OpenLayers.Event.stopObservingElement(this.div);
        OpenLayers.Event.stopObservingElement(this.minimizeDiv);
        OpenLayers.Event.stopObservingElement(this.maximizeDiv);
        //this.clearLayersArray("base");
        //this.clearLayersArray("data");
        this.map.events.un(
        {
            "addlayer" : this.redraw,
            "changelayer" : this.redraw,
            "removelayer" : this.redraw,
            "changebaselayer" : this.redraw, 
            scope : this
        });
        OpenLayers.Control.prototype.destroy.apply(this, arguments);
    },
    setMap : function (map)
    {
        OpenLayers.Control.prototype.setMap.apply(this, arguments);
        this.map.events.on(
        {
            "addlayer" : this.redraw, 
            "changelayer" : this.redraw, 
            "removelayer" : this.redraw, 
            "changebaselayer" : this.redraw, 
            scope : this
        });
    },
    draw : function ()
    {
        OpenLayers.Control.prototype.draw.apply(this);
        /* initialize Group object */
        this.rebuildLayerGroupObject();
        this.initGrpTree();
        
        this.redraw();
        return this.div;
    },
    
    initGrpTree : function ()
    {
      Ext.QuickTips.init();
    
      /* create root element for the Group View */
      this.groupsRoot = new Ext.tree.TreeNode
                                              ({
                                                  text: 'Base Maps and layers',
                                                  expanded: true,
                                                  expandable: false,
                                                  draggable:false,
                                                  ls: this
                                              });


      /* create Tree for Group View */
      this.groupTree = new Ext.tree.TreePanel
                                              ({
                                                   autoScroll:true,
                                                    animate:true,
                                                    enableDD:false,
                                                    expanded: true,
                                                    renderTo: this.switcherDivId,
                                                    root:this.groupsRoot,
                                                    rootVisible:true,
                                                    title:'Base Maps and Layers',
                                                    //height: Ext.get(this.switcherDivId).parent().getHeight(true)-35,
                                                    containerScroll: false,
                                                    autoScroll:true
                                              });
    
      this.groupTree.setRootNode(this.groupsRoot);
    },
    
    clearLayersArray : function (layersType)
    {
        /*var layers = this [layersType + "Layers"];
        if (layers)
        {
            for (var i = 0; i < layers.length; i++)
            {
                var layer = layers[i];
                OpenLayers.Event.stopObservingElement(layer.inputElem);
                OpenLayers.Event.stopObservingElement(layer.labelSpan);
            }
        }
        this [layersType + "LayersDiv"].innerHTML = "";
        this [layersType + "Layers"] = [];*/
    },
    checkRedraw : function ()
    {
        var redraw = false;
        if (!this.layerStates.length || (this.map.layers.length != this.layerStates.length)) {
            redraw = true;
        }
        else
        {
            for (var i = 0; i < this.layerStates.length; 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;
    },
    redraw : function ()
    {

        /* rebuild this.group object */
        this.rebuildLayerGroupObject();

        /* actualize Group View */
        this.addGroupTreeNode(this.groupObject,this.groupsRoot);
        
        return this.div;
    },
    
    rebuildLayerGroupObject: function() {

        /* initialize groupList */
        this.groupList = {};

        /* create root group object with empty name */
        this.groupObject = this.initNewGroup(this.emptyGroupName);

        /* append groupList to the root group object */
        this.groupList[this.emptyGroupName] = this.groupObject;

        /* for each map.layer initialize it's group object */
        for (var i = 0; i < this.map.layers.length; i++) 
        {

          var layer = this.map.layers[i];

          /* skip layers, which should not be displayed in layerswitcher
           */
          if (layer.displayInLayerSwitcher == false) 
          {
              continue;
          }
          
          var layerGroup = layer.options.GROUP;
          if( layerGroup == null )
          {
            layerGroup = this.emptyGroupName;
          }
          
          /* if there is no group for the layer specified, add the
           * default one */
          if (!layerGroup || layerGroup == this.emptyGroupName) 
          {
              layerGroup = this.emptyGroupName;
          }
          else 
          {
              /* create and initialize new group element */
              this.appendGroupToParent(this.groupObject,layerGroup);
          }
        }

        /* second run: now we can add each layer to it's group object */
        for (var i = 0; i < this.map.layers.length; i++)
        {
          var layer = this.map.layers[i];
          var layerGroup = layer.options.GROUP;
          if( layerGroup == null )
          {
            layerGroup = this.emptyGroupName;
          }            

          /* jump over layers, which should not be displayed */
          if (layer.displayInLayerSwitcher == false) {
              continue;
          }
          // Add layer into group list.
          this.groupList[layerGroup].layers.push(layer);           
        }
    },
    /**
     * Method: initNewGroup
     * Initializes new group structure, appends new object to this.group
     * 
     * Properties:
     * groupid - {String} group identifier
     *
     * Returns:
     * {Object} with new group:
     *    * title String 
     *    * layers Array
     *    * visibility Boolean
     */  
    initNewGroup: function(groupid) {

        var title = groupid;

        if (groupid.split("/").length > 0) {
            var maxIdx = groupid.split("/").length-1;
            title = groupid.split("/")[maxIdx];
            
        }

        if (this.titles[groupid]) {
            title = this.titles[groupid];
        }

        return {
                id: groupid,
                title: title,
                layers: [],
                groups: []
              };
    },
    /**
     * Method: appendGroupToParent
     * Appends group to it's parent group. Group will be created, if it
     * does not exist yet.
     * 
     * Properties:
     * parGroup {Object} - parent group object
     * grpname {String} - name of group, which should be created and
     * appended
     */
    appendGroupToParent: function(parGroup,grpname) 
    {
        var names = grpname.split("/");
        var group = null;
        var grpId = (parGroup.id == this.emptyGroupName ? names[0] : parGroup.id + "/" + names[0]);

        /* search after already existing group in this parent group */
        for (var i = 0; i < parGroup.groups.length; i++) {
            if (parGroup.groups[i].id == grpId) {
                group = parGroup.groups[i];
            }
        }

        /* group does not exist create it */
        if(!group) {
            group = this.initNewGroup(grpId);
            parGroup.groups.push(group);
            this.groupList[grpId] = group;
        }

        /* call recursively this method, if depth of the group name is
         * bigger than 1,
         *
         * e.g. if the newgrpname was "foo", only new group "foo" was
         * allready initialized. If it was "foo/bar", than
         * this.appendGroupToParent({id:"foo",....}, "bar") will be
         * called
         */
        if (names.length > 1) {
            newgrpname = grpname.replace(names[0]+"/","");
            var newgroup = this.appendGroupToParent(group, newgrpname);
        }

        return group;
    },
    /**
    * Method: addGroupTreeNode
    * For each element in parent group creates new Ext.treeNode,
    * which is appended to parent Node
    * 
    * Parameters:
    * parGroup {Object} Parent group object
    * parNode {Ext.treeNode} it's corresponding node
    *
    */
    addGroupTreeNode: function(parGroup,parNode) {
               
        /* fist we have to work on groups, which are in this parent group */
        for(var i = 0; i < parGroup.groups.length; i++) {

            var group = parGroup.groups[i];

            //Check, if the group is already available in current tree node.            
            var isHere = false;
            var j;
            for ( j = 0; j < parNode.childNodes.length; j++) {
                if (parNode.childNodes[j].text == group.title) {
                    isHere = true;
                    break;
                }
            }
            /* group is here, skip */
            //if (isHere) { continue; }
            // Node does not exist, add it.
            if( !isHere )
            {
              /* create new group tree node */
              var treeNode = new Ext.tree.TreeNode({
                      //checked: false,
                      draggable: false,
                      allowChildren: true,
                      leaf : false,
                      singleClickExpand : true,
                      text : group.title,
                      //icon: OpenLayers.Util.getImagesLocation()+"/layer-group.png",
                      expanded: true,
                      ls: this
                      });
              /* set events and append to parent node */
              //treeNode.on("checkchange",this.onGroupCheckboxChange);
              parNode.appendChild(treeNode);
              this.addGroupTreeNode(group, treeNode);           
            }
            // Node exists, however we want to add any children that are attached to the group.
            else
            {
              this.addGroupTreeNode(group, parNode.childNodes[j]);           
            }
        }

        /* now append all layers from this group as well */
        for (var i = 0; i < parGroup.layers.length; i++) 
        {

            var layer = parGroup.layers[i];

            //Check, if the group is already available in current tree node.            
            var isHere = false;
            for (var j = 0; j < parNode.childNodes.length; j++) {
                if (parNode.childNodes[j].text == layer.name) {
                    isHere = true;
                    break;
                }
            }
            /* group is here, skip */
            if (isHere) { continue; }
            
            /* create new group tree node */
            var strIcon = ".x-tree-node-icon-blank";
            if( layer.options.QUERYABLE )
            {
              strIcon = this.layerInfoIcon;
              if( layer.getVisibility() )
              {
                //icon = this.layerSelectedInfoIcon;
              }              
            }
            var treeNode = new Ext.tree.TreeNode({
                    checked: layer.visibility,
                    draggable: false,
                    allowChildren: true,
                    expandable : (layer.options.legend ? true : false),
                    href: layer.options.LAYERURL,
                    hrefTarget : 'new',
                    leaf : true,
                    text : layer.name,
                    iconCls: strIcon,
                    expanded: false,
                    mapLayer: layer,
                    qtipCfg:new Ext.QuickTip({
                          text: layer.options.attribution,
                          showDelay:20,
                          width:200,
                          height:200,
                          autoHide:true,
                          bufferResize:true,
                          frame: true,
                          shadow: true
                            })
                    });


            /* set events and append to parent node */
            treeNode.on("checkchange",this.onLayerCheckboxChange, this);
            treeNode.on("contextmenu",this.onLayerRightClicked);
            layer.groupTreeNode = treeNode;
            parNode.appendChild(treeNode);

            if (layer.options.legend) {
                var legendNode = new Ext.tree.TreeNode({
                    dragable: false,
                    allowChildren: false,
                    //cls: this.layerLegend,
                    iconCls: this.layerLegend,
                    icon: layer.options.legend
                    });

                this.legendNodes.push(legendNode);
                treeNode.appendChild(legendNode);
            }
        }
    },
    
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Events
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    onLayerClick : function (e)
    {
        this.updateMap();
    },
    updateMap : function ()
    {
        for (var i = 0; i < this.baseLayers.length; i++)
        {
            var layerEntry = this.baseLayers[i];
            if (layerEntry.inputElem.checked) {
                this.map.setBaseLayer(layerEntry.layer, false);
            }
        }
        for (var i = 0; i < this.dataLayers.length; i++)
        {
            var layerEntry = this.dataLayers[i];
            layerEntry.layer.setVisibility(layerEntry.inputElem.checked);
        }
    },
    ignoreEvent : function (evt)
    {
        OpenLayers.Event.stop(evt);
    },
    mouseDown : function (evt)
    {
        this.isMouseDown = true;
        this.ignoreEvent(evt);
    },
    mouseUp : function (evt)
    {
        if (this.isMouseDown) {
            this.isMouseDown = false;
            this.ignoreEvent(evt);
        }
    },
    /**
     * Method: onLayerCheckboxChange 
     * Called, when layer checkbox has changed
     *
     * Parameters:
     * node {Ext.tree.Node} node
     * checked {Boolean} checked or no
     */
    onLayerCheckboxChange: function(node,checked) {
        var layer = node.attributes.mapLayer;
        // layer is baselayer, set new baselayer.
        if (layer.isBaseLayer) 
        {          
          //Check for other base layers and uncheck them if they are checked.
          for (var i = 0; i < layer.map.layers.length; i++)
          {
            var prevBaseLayer = layer.map.layers[i];
            //if the layer that is unchecked is the one that is checked, recheck the node, we don't want to turn off a base layer.
            if( checked == false )
            {
              // Get the UI and then recheck the node.
              node.getUI().toggleCheck(true);
              return;
            }
            if( prevBaseLayer.isBaseLayer )
            {
              prevBaseLayer.setVisibility(false);
              break;
            }
          }
          //Now let's loop through the treenodes and uncheck the previously checked base layer.
          for( var i = 0; i < node.parentNode.childNodes.length; i++ )
          {
            // If the node is checked and it is not the node that fired this event, we'll uncheck it.
            if( node.parentNode.childNodes[i].getUI().isChecked() && 
                node.parentNode.childNodes[i].id != node.id )
            {
              // Suspend the event handling since we don't want this event handler firing when we uncheck the node.
              node.parentNode.childNodes[i].suspendEvents();
              node.parentNode.childNodes[i].getUI().toggleCheck(false);
              // Now turn the event handling back on.
              node.parentNode.childNodes[i].resumeEvents();
            }
          }
          layer.map.setBaseLayer(layer);
        }
        else
        {
          if( node.attributes.icon != "" )
          {
            if( checked )
            {
              for (var i = 0; i < layer.map.layers.length; i++)
              {
                var oldLayer = layer.map.layers[i];
                if( oldLayer.options.QUERYABLE == 'true' && oldLayer.getVisibility() ) 
                {
                  node.getUI().iconNode.className = "x-tree-node-icon x-tree-node-icon-blank";
                }
              }
              // We only add the icon if the layer has "GetFeatureInfo"(QUERYABLE == true) abilties.
              if( layer.options.QUERYABLE == true )
              {
                node.getUI().iconNode.className = "x-tree-node-icon " + layerSwitcherExt.layerSelectedInfoIcon;
              }
            }
            else
            {
              if( layer.options.QUERYABLE == true )
              {
                node.getUI().iconNode.className = "x-tree-node-icon x-tree-node-icon-blank";
              }
            }
          }
        }
        layer.setVisibility(checked);
        this.LayerStateChangeCallback( layer, checked );
    },
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    
    
    CLASS_NAME : "OpenLayers.Control.LayerSwitcherExt"
});

