//-----------------------------------------------------------------------------
// Dependencies:  common.js
//                ClusterConnector.js
//                yui/treeview/treeview-min.js
//-----------------------------------------------------------------------------

/*global ClusterConnector, YAHOO, document */

/**
 * All parameter constructor for the ClusterPane
 * @param clusterGroup a value that will uniquely indentify this group of clusters on the server
 * @param displayPane the index to the pane into which the cluster results will be displayed
 * @param clusterTypeSortMap the types of clusters to retrieve in a map describing if
 *        each cluster type should be sorted alphabetically by name
 * @param maxClusterBreadth the number of top level clusters
 * @param maxClusterDepth the number of hierarchical levels each cluster can expand to.
 */
function ClusterPane(clusterGroup, displayPane, clusterTypeSortMap, maxClusterBreadth, maxClusterDepth)
{
    if (typeof(clusterTypeSortMap) == "undefined")
    {
        clusterTypeSortMap = null;
        maxClusterBreadth  = null;
        maxClusterDepth    = null;
    }

    this._initMembers(clusterGroup, displayPane, clusterTypeSortMap, maxClusterBreadth, maxClusterDepth);
}

/**
 * True if calls to retrieve results for a cluster should be blocked.
 */
ClusterPane.blockCalls = false;

/**
 * The label that will be used for the all results link.
 */
ClusterPane.allResultsLabel = "<<<uninitialized>>>";

/**
 * The label that will be used when their are more rsults to view
 */
ClusterPane.moreLabel = "<<<uninitialized>>>";

/**
 * The default style for tree labels
 */
ClusterPane.defaultLabelStyle = "ygtvlabel";

/**
 * The array of all available cluster panes.
 */
ClusterPane.instances = [];

/**
 * initializes all the data members used in this object and adds the newly created object
 * to the cluster manager list.
 * @param clusterGroup a value that will uniquely indentify this group of clusters on the server
 * @param displayPane the index to the pane into which the cluster results will be displayed
 * @param clusterTypeSortMap the types of clusters to retrieve in a map describing if
 *        each cluster type should be sorted alphabetically by name
 * @param maxClusterBreadth the number of top level clusters
 * @param maxClusterDepth the number of hierarchical levels each cluster can expand to.
 */
ClusterPane.prototype._initMembers = function(clusterGroup, displayPane, clusterTypeSortMap, maxClusterBreadth, maxClusterDepth)
{
    this._clusterGroup       = clusterGroup;
    this._displayPane        = displayPane;
    this._clusterTypeSortMap = clusterTypeSortMap;
    this._maxClusterBreadth  = maxClusterBreadth;
    this._maxClusterDepth    = maxClusterDepth;
    this._clusters           = null;
    this._clusterTree        = null;
    this._clusterElement     = null;
    this._isInitiallyVisible = false;
    this._selectedNode       = null;
    this._listeners          = [];
    this._totalCounts        = 0;

    // Set the instance in the array of instances, indexed by displayPane
    ClusterPane.instances[clusterGroup] = this;
};

/**
 * Calls the ClusterConnector to get all the clusters for a given search and pane
 * @param ssid the search session identifier, used to uniquely identify the search and its results
 * @param isInitiallyVisible true if the pane is initially visible
 * @param totalCounts the total number of counts in the tab.
 * @param isSearchFinished flag indicating if the search is finished.
 * @param filters the filters that should be applied to the clusters
 */
ClusterPane.prototype.getClusters = function(ssid, isInitiallyVisible, totalCounts, isSearchFinished, filters)
{
    this._isInitiallyVisible = isInitiallyVisible;
    this._totalCounts        = totalCounts;

    if (this._clusterTypeSortMap === null)
    {
        ClusterConnector.getClusters(ssid, this._clusterGroup, isSearchFinished, filters, ClusterPane.getClusters_callback);
    }
    else
    {
        ClusterConnector.getCustomClusters(ssid, this._clusterGroup, isSearchFinished, filters, this._clusterTypeSortMap,
                this._maxClusterBreadth, this._maxClusterDepth, ClusterPane.getClusters_callback);
    }
};

/**
 * Sets the clusters field which will be used to build out the clusterTree
 * @param clusters the list of clusters that is associated with a particular pane
 */
ClusterPane.prototype.setClusters = function(clusters)
{
    this._clusters = clusters;
};

/**
 * Sets the clusters field which will be used to build out the clusterTree
 * @param clusters the list of clusters that is associated with a particular pane
 */
ClusterPane.prototype.setMaxClusterBreadth = function(maxClusterBreadth)
{
    this._maxClusterBreadth = maxClusterBreadth;
};

/**
 * Callback function that receives the list of clusters via AJAX.  This method will
 * create the tree by which the clusters are displayed to the user.
 * @param clusterData contains the list of clusters to display, the paneIndex,
 * and the maxClusterBreadth
 */
ClusterPane.getClusters_callback = function(clusterData)
{
    // Get the clusterPane that is associated with the paneIndex
    var cm = ClusterPane.instances[clusterData.paneIndex];
    cm.setClusters(clusterData.clusters);
    cm.setMaxClusterBreadth(clusterData.maxClusterBreadth);
    cm.createClusterTree();

    // Notify the listeners that there are now clusters
    for (i = 0; i < cm._listeners.length; ++i)
    {
        cm._listeners[i].onCreateClusterTree();
    }
};

/**
 * Gets the location of the selected cluster inside of the current cluster tree.
 * The cluster location is a comma-delimited list that contains the indece into
 * the child nodes of the cluster tree.
 */
ClusterPane.prototype.getSelectedClusterLocation = function()
{
    var node;
    var ret;
    function getNodePos()
    {
        var ret = -1;
        var i;
        for (i = 0; i < node.parent.children.length; ++i)
        {
            if (node.parent.children[i] == node)
            {
                ret = i;
                break;
            }
        }

        return ret;
    }
    try
    {
        if (typeof(this._selectedNode.data) == "string")
        {
            return "";
        }
        else
        {
            node = this._selectedNode;
            ret = getNodePos();
            node = node.parent;
            while (node.parent !== null)
            {
                ret = getNodePos() + "," + ret;
                node = node.parent;
            }
            return ret;
        }
    }
    catch(e)
    {
        return "";
    }
    
};

/**
 * Check to see if the given node is a More... node
 * @param node the node to check
 * @return true if the node is a more node
 */
ClusterPane.prototype.isMoreNode = function(node)
{
    return (node.label === ClusterPane.moreLabel);
};

/**
 * Retrieves the cluster id of the node pointed to by the cluster location.
 * @param clusterLocation the location of the cluster.
 */
ClusterPane.prototype.clusterIdFromClusterLocation = function(clusterLocation)
{
    var labelIdArray;
    var node;
    var labelIdx;
    var clusterPane;

    function getClusterId()
    {
        // if this node has a more... node and the label is the same number or after the more node
        if (clusterPane.isMoreNode(node.children[node.children.length-1]) &&
            labelIdArray[labelIdx] >= node.children.length-1)
        {
            clusterPane.addAddtionalNodes(node.children[node.children.length-1]);
        }

        // Make sure that the node is expanded
        node.expand();
        
        node = node.children[parseInt(labelIdArray[labelIdx], 10)];

        if (labelIdx == labelIdArray.length - 1)
        {
            return node.data.clusterId;
        }
        else
        {
            labelIdx += 1;
            return getClusterId();
        }
    }

    try
    {
        labelIdArray = clusterLocation.split(",");
        node = this._clusterTree.getRoot();
        labelIdx = 0;
        clusterPane = this;

        return getClusterId();
    }
    catch(e)
    {
        return "";
    }
};

/**
 * Creates the cluster tree using the yahoo ui treeview
 */
ClusterPane.prototype.createClusterTree = function()
{
    var clusterDiv = document.getElementById("clusterDiv");

    if (this._clusterElement === null)
    {
        this._clusterElement = document.createElement("div");
        this._clusterElement.id = "cluster_" + this._displayPane;
    }
    else
    {
        while (this._clusterElement.hasChildNodes())
        {
            this._clusterElement.removeChild(this._clusterElement.firstChild);
        }
    }
    
    if (!this._isInitiallyVisible)
    {
        this._clusterElement.style.display = "none";
    }
    
    clusterDiv.appendChild(this._clusterElement);

    this._clusterTree = new YAHOO.widget.TreeView(this._clusterElement.id);

    // Handle the click on the node's label
    this._clusterTree.subscribe("labelClick", ClusterPane.onLabelClick, this, true);

    var root = this._clusterTree.getRoot();

    // Add the node to retrieve all results
    var label = ClusterPane.allResultsLabel + " (" + this._totalCounts + ")";
    var node = new YAHOO.widget.TextNode(label, root, false);

    this._setSelectedNode(node, false);

    // if there is only one cluster type to be displayed, then add the children
    // at the top level.
    if (this._clusters.length == 1)
    {
        this._addClusterChildren(root, this._clusters[0], false, true);
    }
    else
    {
        // Add node for each cluster type
        for (var i = 0; i < this._clusters.length; ++i)
        {
            this._addClusterToTree(root, this._clusters[i], true, false);
        }
    }

    this._clusterTree.draw();
};

/**
 * Adds the children of a top-level cluster to the tree
 * @param parentNode the node to insert the child clusters under
 * @param cluster the cluster to add
 * @param expandWhenAdded true if the clusters should be expanded when added
 * @param includeClusterCount true if the label should include the cluster count
 */
ClusterPane.prototype._addClusterChildren = function(parentNode, cluster, expandWhenAdded, includeClusterCount)
{

    if (cluster.children !== null)
    {
        var isPartialChildList = (this._maxClusterBreadth < cluster.children.length);
        var initialChildCount = isPartialChildList ? this._maxClusterBreadth : cluster.children.length;

        for (var j = 0; j < initialChildCount; ++j)
        {
            this._addClusterToTree(parentNode, cluster.children[j], expandWhenAdded, includeClusterCount);
        }

        if (isPartialChildList)
        {
            var nodeData = {label: ClusterPane.moreLabel, cluster: cluster};
            var moreNode = new YAHOO.widget.TextNode(nodeData, parentNode, false);
            moreNode.labelStyle = "moreLabel";
        }
    }
};

/**
 * Adds a cluster to the tree
 * @param topNode the node to add the cluster under
 * @param cluster the cluster to add
 * @param expandWhenAdded set to true if the node should be expanded when added
 * @param includeClusterCount true if the cluster count should be included
 */
ClusterPane.prototype._addClusterToTree = function(topNode, cluster, expandWhenAdded, includeClusterCount)
{
    var clusterLabel = cluster.name;

    // if this is not a top node, add the cluster count
    if (includeClusterCount)
    {
        clusterLabel += " (" + cluster.count + ")";
    }

    var nodeData = { label: clusterLabel, cluster: cluster, clusterId: cluster.clusterId, hasClusterCount: includeClusterCount};
    var node = new YAHOO.widget.TextNode(nodeData, topNode, expandWhenAdded);

    this._addClusterChildren(node, cluster, false, true);
};

/**
 * This method selects the node given its label
 * @param clusterName the name of the cluster that this node represents
 */
ClusterPane.prototype.selectNodeByClusterId = function(clusterId)
{
    var node;
    var label;

    if (this._clusterTree)
    {
        if (clusterId)
        {
            node = this._clusterTree.getNodeByProperty("clusterId", clusterId);
        }
        else
        {
            node = this._clusterTree.getRoot().children[0];
        }

        if (node)
        {
            this._setSelectedNode(node,true);
        }
    }
};

/**
 * Gets the id of the currently selected cluster.
 */
ClusterPane.prototype.getSelectedClusterId = function()
{
    try
    {
        if (typeof(this._selectedNode.data) == "string")
        {
            return "";
        }
        else
        {
            return this._selectedNode.data.clusterId;
        }
    }
    catch(e)
    {
        return "";
    }
};

/**
 * Shows the cluster element
 */
ClusterPane.prototype.showClusters = function()
{
    if (this._clusterElement !== null)
    {
        this._clusterElement.style.display = "";
    }
};

/**
 * Hides the cluster element
 */
ClusterPane.prototype.hideClusters = function()
{
    if (this._clusterElement !== null)
    {
        this._clusterElement.style.display = "none";
    }
};

/**
 * sets the selected node in the cluster tree
 * @param node the node veing selected
 * @param doRedraw true if the tree should be redrawn
 */
ClusterPane.prototype._setSelectedNode = function(node, doRedraw)
{
    if (this._selectedNode !== null)
    {
        this._selectedNode.labelStyle = ClusterPane.defaultLabelStyle;
    }

    node.labelStyle = "selectedCluster";
    this._selectedNode = node;

    if (doRedraw)
    {
        this._clusterTree.draw();
    }
};

ClusterPane.prototype.addAddtionalNodes = function(node)
{
    var parentNode = node.parent;

    // Add the remaining clusters to the list
    for (i = this._maxClusterBreadth; i < node.data.cluster.children.length; ++i)
    {
        this._addClusterToTree(parentNode, node.data.cluster.children[i], false, true);
    }

    // Delete the node from the tree
    this._clusterTree.removeNode(node);

    parentNode.refresh();
};

/**
 * Handler for the lableClick event from the tree.  This method makes an AJAX call to
 * load the result list with the results contained in this cluser.
 * @param node the node that was clicked
 */
ClusterPane.onLabelClick = function(node)
{
    if (ClusterPane.blockCalls)
    {
        return false;
    }

    var retVal = false;

    var i = 0;

    if (this._selectedNode == node)
    {
        // return false.  Nothing needs to be done
    }

    else if (this.isMoreNode(node))
    {
        this.addAddtionalNodes(node);
    }
    
    else if (node.label.indexOf(ClusterPane.allResultsLabel) != -1)
    {
        this._setSelectedNode(node, true);

        for (i = 0; i < this._listeners.length; ++i)
        {
            this._listeners[i].onAllResultsClicked(this._displayPane);
        }
    }

    // else if not displaying counts, do the default behaviour
    else if (!node.data.hasClusterCount)
    {
        retVal = true;
    }

    else
    {
        if (node.data.cluster.count > 0)
        {
            ClusterPane.blockCalls = true;
            
            for (i = 0; i < this._listeners.length; ++i)
            {
                this._listeners[i].onClusterClicked(node.data.cluster, this._displayPane, this._clusterGroup);
            }
        }
    }
    
    // Remove focus from the node
    node.getEl().blur();

    return retVal;
};

/**
 * Adds a listener for events that are fired from this object.  These events are:
 * onClusterClicked and onAllResultsClicked.  The listener object must implement
 * these methods in order to get called.
 * @param listener the listener object to add to the list of listeners.
 */
ClusterPane.prototype.addListener = function(listener)
{
    this._listeners.push(listener);
};