extend(ligoTreeGrid, ligoGrid);

/* This class adds a tree-structured view using nested ligoTreeGrids.
 * Each row can be expanded into subtrees, and all of the subtree objects and HTML are created on the fly.
 *
 * It works by creating a top-level ligoTreeGrid like a normal ligoGrid. When a row is expanded it creates
 * another table row beneath it, with a <td> to hold the subtree. To avoid temporary variables in global scope
 * it stores the ligoTreeGrid object in $(<td>).ligoTreeGrid. Everything is done internally using generated
 * HTML IDs which you shouldn't need to know about from outside.  Double clicking a row again deletes it from
 * the DOM tree and should also get rid of the associated objects.
 *
 * To access subtrees directly in code, you can use this.getSelectedTreeIDs() to get a list of HTML IDs for
 * each grid that can be reached by following selected rows, then use $(id).ligoTreeGrid to access the
 * subtree.  The getSelectionObject() function returns Dataset.getVerboseCurrentRowObject() for the row last
 * clicked on in the browser. If you need to get a list of every selected row in the tree, instead of just
 * what was last clicked on, read below.
 *
 * If getVerboseCurrentRowObject() is called on the root of the tree (the grid that gets created initially)
 * the object it returns has an added property named "tree", which is an array of all the selected rows going
 * down the tree (uses same format as the "data" property). This doesn't include the row already in "data".
 * This also happens when another grid references a ligoTreeGrid as its master dataset.
 */

// Constructor - this ignores any instanceName given and uses the div id instead
function ligoTreeGrid (paramsObj) {

    ligoTreeGrid.superclass.call(this, paramsObj);

    this.notifyNoData = true;

    // Dataset needs to be able to refer back to the treeGrid (see function below),
    // so we set its uniqueName to a known value (the grid div name)
    this.Dataset.uniqueName = this.instanceName = this.div;

    // Store a pointer to the grid on the div it renders inside of
    document.getElementById(this.div).ligoTreeGrid = this;

    // Override ligoDataset._row_to_json() to add an extra "tree" parameter, containing all selected rows
    this.Dataset._row_to_json = function(row_struct) {

        var js_obj = this.getVerboseRowObject(row_struct);

        /* ligoTreeGrid specific stuff */
        // Get the top of the tree, then follow it down again to the current selected row
        var tree = document.getElementById(this.uniqueName).ligoTreeGrid.getSelectedTreeIDs();

        // Check if this is the root node, and if it is return the rest of the selected rows as js_obj.tree[]
        if ( this.uniqueName == tree[0] && tree.length > 1 ) {
            js_obj.tree = new Array();
            for (var i = 1; i < tree.length; i++ ) {
                js_obj.tree.push( document.getElementById(tree[i]).ligoTreeGrid.Dataset.getVerboseCurrentRowObject() );
            }
        }

        return JSON.stringify(js_obj);
    }
}

//####################################################################################################

/* Onclick handler */
ligoTreeGrid.prototype.onRowClick = function(row_num, clicked_div) {
    // clicked_div is the ID of the div directly clicked on
    // If this was the row clicked, deselect any subtree
    if ( this.display.deselectInactiveNodes && clicked_div == this.div ) {
        var d = document.getElementById(this.selectedRowId+'_t');
        if ( d ) {
            d.ligoTreeGrid.Dataset.setCurrentRow(-1);
        }
    }

    ligoTreeGrid.superclass.prototype.onRowClick.call(this, row_num);

    // If this wasn't the div being clicked, this is an onclick going through the tree
    // In this case the row should be deselected
    // Note that this doesn't really deselect the row, just makes it look that way
    if ( this.display.deselectInactiveNodes && clicked_div != this.div ) {
        document.getElementById(this.selectedRowId).removeAttribute('class');
    }
}

//####################################################################################################

ligoTreeGrid.prototype.onDS_currentRowChange = function(source) {

    if ( this.display.deselectInactiveNodes ) {
        // Find any rows that aren't the selected row and have a subtree, and deselect the subtrees
        for ( var i = this.Dataset.data.length-1; i >= 0; i-- ) {
            var subtree_div = this.makeRowId(i)+'_t';
            var d = document.getElementById(subtree_div);

            if ( d && i != source.currentRowNum ) {
                d.ligoTreeGrid.Dataset.setCurrentRow(-1);
            }
        }
    }

    ligoTreeGrid.superclass.prototype.onDS_currentRowChange.call(this, source);
}

//####################################################################################################

/* Returns an array of div IDs, one for each ligoTreeGrid that has a selected row */
ligoTreeGrid.prototype.getSelectedTreeIDs = function() {
    var grid_object = this;
    var selection = [ ];

    // Go up the tree until grid_object doesn't have a parent treeGrid (i.e. the top level)
    while ( (parent_id = document.getElementById(grid_object.div).className) ) {
        // Make sure the classname is actually referencing a treeGrid container
        var d = document.getElementById(parent_id);
        if ( ! (d && d.ligoTreeGrid) ) {
            break;
        }
        grid_object = d.ligoTreeGrid;
    }

    // Follow the tree down while there is a row selected
    while ( grid_object.selectedRowId && grid_object.Dataset.currentRowNum >= 0 ) {
        // Add this to the array
        selection.push(grid_object.div);

        // "$(grid_object.selectedRowId+'_t')" contains a subtree of the currently selected row (i.e. a this.div)
        // If it doesn't exist it means this is the bottom of the tree so stop the loop here
        var d = document.getElementById(grid_object.selectedRowId+'_t');
        if ( ! d ) {
            break;
        }

        // Change pointer to new object
        grid_object = d.ligoTreeGrid;
    }

    return selection;
}

//####################################################################################################

// Returns getVerboseCurrentRowObject for the innermost selected row
ligoTreeGrid.prototype.getSelectionObject = function() {
    var treelist = this.getSelectedTreeIDs();

    return document.getElementById(treelist[treelist.length-1]).ligoTreeGrid.Dataset.getVerboseCurrentRowObject();
}

//####################################################################################################

// Double click handler; toggles row expanded/normal state
ligoTreeGrid.prototype.onRowDblClick = function(row_num) {
    var row = document.getElementById(this.makeRowId(row_num));

    // clear the automatic word selection caused by double clicking
    if ( window.getSelection ) {
        window.getSelection().removeAllRanges();
    }
    else if ( document.selection ) { // MSIE
        document.selection.empty();
    }

    // Check whether the row created by expandRow() exists
    if ( document.getElementById(row.id+'_t_container') ) {
        this.collapseRow(row_num);
    }
    else {
        this.expandRow(row_num);
    }
}

//####################################################################################################

// Expand tree when parent row is double clicked
ligoTreeGrid.prototype.expandRow = function(row_num) {
    var parentRow = document.getElementById(this.makeRowId(row_num));
    var row, cell;

    // Create a new table row and cell to put the subtree in
    cell = document.createElement('td');
    cell.id = parentRow.id+'_t'; // The new ligoTreeGrid object's div and instanceName will get set to this later
    cell.className = this.div; // Transfer the ID of the parent grid to the class of the container
    cell.colSpan = this.colsToDisplay.length; // Make cell full table width

    row = document.createElement('tr');
    row.id = parentRow.id+'_t_container';

    // Assemble the html bits and put them after the parent row (the double clicked one)
    row.appendChild(cell);
    parentRow.parentNode.insertBefore(row, parentRow.nextSibling);

    // Insert the table into the container cell
    this.loadSubtree(cell, row_num);

    // Add an event handler so that clicking anything in this cell does the same as clicking the parent row
    function get_closure(div, row) {
        return function() { document.getElementById(div).ligoTreeGrid.onRowClick(row); }
    }
    var click_func = get_closure(cell.className, row_num);

    // This is correct - we do this to overwrite the existing onclick handler
    cell.onclick = click_func;
}

//####################################################################################################

// Collapse tree when row is double clicked again, and try to get rid of all object refs
ligoTreeGrid.prototype.collapseRow = function(row_num) {
    var parentRow = document.getElementById(this.makeRowId(row_num));
    var containerCell = document.getElementById(this.selectedRowId+'_t');

    delete(containerCell.ligoTreeGrid.Dataset);
    delete(containerCell.ligoTreeGrid);
    parentRow.parentNode.removeChild(document.getElementById(parentRow.id+'_t_container'));
}

//####################################################################################################

// Load and display a subtree
ligoTreeGrid.prototype.loadSubtree = function(htmlelement, parent_row_num) {
    // Create the dataset
    var data = new ligoDataset({
        remotePath: this.Dataset.remotePath,
        masterDataset: this.Dataset,
        uniqueName: htmlelement.id
    });

    // Create the subtree
    var subtree = new this.constructor({
        instanceName: htmlelement.id,
        Dataset: data,
        div: htmlelement.id,
        masterRowIdx: parent_row_num,   // the numeric index (row_num) of which row this belongs to
        display: this.display
    });

    htmlelement.ligoTreeGrid.doParts = {
        header: false,
        body:   true,
        footer: false
    };

    if ( this.display.css_subtreeClass ) {
        htmlelement.ligoTreeGrid.display.css_tableClass = this.display.css_subtreeClass;
    }

    data.getData("masterRow=" + this.Dataset.getVerboseCurrentRow());

    // This has to be disabled for stuff to work correctly, or else expanding a row will trigger a
    // huge amount of ajax requests up the tree, and the tree expand part won't work because it gets
    // overwritten by the render() callback from everything doing the ajax
    data.onDS_currentRowChange = null;
}

//#####################################################################################################################

ligoTreeGrid.prototype._make_html_body = function () {
    // Overrides the ligoGrid function
    // The only difference is the click handler syntax, and addition of double click for expanding row.
    
    var dataset = this.Dataset;
    var html = [];
            
    html.push('<tbody>');

    var clickPrefix = 'javascript:document.getElementById(\'' + this.div + '\').ligoTreeGrid.';

    for (var i=0; i < dataset.data.length; i++) {   //loop over each dataset row
        var row_id = this.makeRowId(i);
        var row = dataset.data[i];

        var clickHandler = clickPrefix + 'onRowClick(' + i + ', \'' + this.div + '\')';
        var dblclickHandler = clickPrefix + 'onRowDblClick(' + i + ')';
        html.push( '<tr onclick="' + clickHandler + '" ondblclick="' + dblclickHandler + '" id="' + row_id + '">' );

        for (var j=0; j < row.length; j++) {
            if (this.colsToDisplay[j]) {
                html.push( ( this.col_classes[j]
                                ? '<td class="' + this.col_classes[j] + '">'
                                : '<td>' )
                         +  String(this.displayValue( row[j], j )).escapeHTML()
                         +  '</td>' );
            }
        }
        html.push( '</tr>' );
    }

    html.push( '</tbody>' );

    return html.join('');
}
