//Copyright 2007 Roman Ardern-Corris
//Licenced to 3legs.com


/*
    ligoGrid
*/

extend(ligoGrid, ligoDSVisualiser);

function ligoGrid (paramsObj) {
    this.autosum = false;               //bool, apply autosum routines
    this.footerStrings = [];            //array of values to apply to the footer (this has the lowest priority of all footer actions)
    this.applyScrolledTable = false;    //This applies the special scrolling table code which handles horizontal scrolling correctly (for footers/headers)
                                        //this an overhead though as it chops the table up (see this._make_table_scrolled() )
    this.allowSort = false;           
    
    this.persistentHighlight;   // Tells grid to re-highlight the same row across data change if possible
    this.selectedRowKey;        // Stores current row if above is enabled

    this.notifyNoData = false;  // If this is set and the dataset is empty, display a "<div>No Data</div>" instead of an empty table.

    

    // Provides a way to suppress output of individual table parts if wanted
    // This isn't compatible with scrolled tables, don't use both on one table at once.
    this.doParts = {
        header: true,
        body:   true,
        footer: true
    };

    this.col_classes = []; // Array of column formatting classes

    ligoGrid.superclass.call(this, paramsObj);

    // Put the status indicator in early if possible
    //document.getElementById(this.div).style.position = 'relative'; // Necessary for the loading animation overlay CSS
    //this._set_status('loading');
}

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

ligoGrid.prototype.render = function () {

    if (this.active) {        

        //create the data grid
        document.getElementById(this.div).innerHTML = this._asTable();
    
        if (this.initialNoRowSel) {
            //no row selected
            this.Dataset.setCurrentRow(-1);
        }
        else {
            //select the first row
            this.Dataset.setCurrentRow(0);
        }
        
        if (this.applyScrolledTable) {
            //apply the chop up table method to handle scrolling tables
            this._make_table_scrolled();
        }
        else {
            //apply hack only when using the css based method of scrolling (this is the only thing that we do with it)
            this._fix_scrolled_table();
        }

    }
    else {
        document.getElementById(this.div).innerHTML = '';
    }
    
}

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

ligoGrid.prototype._fix_scrolled_table = function () {
    // Do hacks to make a scrolled table work right
    
    var my_div = $(this.div);
    if ( ! my_div.className.match(/\bligoScrolledGrid\b/) ) {
        // not a ligoScrolledGrid, do nothing
        return;
    }

    // IE doesn't display scrollbars unless we force hasLayout by doing this
    // The @-thing is an IE conditional comment to make sure only IE sees this code
    if (/*@cc_on!@*/false) {
        my_div.style.zoom = 1;
    }
    // This hack makes the <tbody> element itself scrollable by forcing a height on it
    // Needed in Firefox, has no effect in Webkit
    else if (navigator.appName == 'Netscape') {
        var table = my_div.getElementsByTagName("table")[0];
        var tbody = table.getElementsByTagName("tbody")[0];
        var thead = table.getElementsByTagName("thead")[0];
        var tfoot = table.getElementsByTagName("tfoot")[0];

        // XXX this breaks if borders are set
        var request_table_height = my_div.clientHeight - thead.clientHeight - tfoot.clientHeight;
    
        tbody.style.height = request_table_height + "px";
    }
    // We can't get ligoScrolledTable working yet in webkit/opera
       
}

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

ligoGrid.prototype._make_table_scrolled = function () {
    //all supporting functions are prefixed with _mts_
    
    //There must be a height and width defined for the containing div (held in this.div)
    
    //first get all of the dimensions that we will be needing    
    var dims = this._mts_calc_dimensions();
    
    
    var dheader = document.getElementById('div_header');
    var dbody = document.getElementById('div_body');
    var dfooter = document.getElementById('div_footer');
                                

    var dims = calc_dimentions(dbody);

    set_header(dims, dbody, dheader);
    set_body(dims, dbody, dheader, dfooter);
    set_footer(dims, dbody, dfooter);
    
}

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

ligoGrid.prototype._mts_calc_dimensions = function () {
    //this calculates the dimensions that will be used for each element
    var dims = {
        'table': {
            'width': null,
            'height': null
        },
        'footer': {
            'height': null
        },
        'header': {
            'height': null
        },
        'constraint': {
            'width': null,
            'height': null
        },
        'cellWidths': []
    };

    var table = this._div.getElementsByTagName('table')[0];
    dims.table.width = table.clientWidth;
    dims.table.height = table.clientHeight;

    var thead = table.getElementsByTagName('thead')[0];
    dims.header.height = thead.clientHeight;

    var tfoot = table.getElementsByTagName('tfoot')[0];
    dims.footer.height = tfoot.clientHeight;

    dims.constraint.width = this._div.style.width;
    dims.constraint.height = this._div.style.height;

    //iterate over the cells of the first row
    var num_table_rows = table.rows[0].cells.length; //stops recounting on each loop iteration
    for (var i=0; i<num_table_rows; i++) {
        dims.cellWidths[i] = table.rows[0].cells[i].clientWidth;
    }

    return(dims);

}

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

ligoGrid.prototype._asTable = function () {
    
    //This is the new improved rendering method,
    //it is much tidier and also provides support for fixed header and footer
    //It provides support for formatting data and for creating automatic totals
    //We will not be using DOM methods, we will use innerHTML as it is much faster for IE
    //Also string concatenation is very slow in IE, we will join an array
    //Firefox seems to be about the same speed with arrays and concatenation
    //In IE the number of concatenations seems to be the slowing factor,
    // specifically when the string grows it is slow at allocating the memory
    
    var html = [];  //array that will hold each segment of html before we join it and make it a string
    
    //this sets up the column display mask
    this._set_cols_to_display();

    // Calculate the formatting classes once instead of for every row
    for (var i=0; i < this.Dataset.columnNames.length; i++) {
        if ( this.colsToDisplay[i] ) {
            this.col_classes.push(this.getFormatClass(this.Dataset.columnNames[i], i));
        }
    }

    if ( this.notifyNoData && ! this.Dataset.data.length ) {
        return '<div id="ligogrid_' + this.instanceName + '">No Data.</div>';
    }

    html.push( '<table id="ligogrid_' + this.instanceName + '">' );
    if ( this.doParts.header ) {
        html.push( this._make_html_header() );
    }
    if ( this.doParts.footer ) {
        html.push( this._make_html_footer() );
    }
    html.push( this._make_html_body() );
    html.push( '</table>' );
        
    //serialise and return the string
    return html.join('');
    
}

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

ligoGrid.prototype._make_html_header = function () {
    
    var dataset = this.Dataset;
    var html = [];
    
    html.push('<thead>');
    html.push('<tr>');
    for (var i=0; i < dataset.columnNames.length; i++) {
        if (this.colsToDisplay[i]) {
            var sort_string = '';
            if (this.allowSort) {
                sort_string = ' ' + this.show_column_sort_status(i)
            }
            html.push('<th class="grid_column_' + i + '">' + String(this._label_for_column(i)).escapeHTML() + sort_string + '</th>');
        }
    }
    html.push('</tr>');
    html.push('</thead>');
    return( html.join('') );
    
}

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

ligoGrid.prototype._make_html_footer = function () {

    var dataset = this.Dataset;
    var html = [];

    if (this.autosum || this.footerStrings) {
        html.push('<tfoot>');
        html.push('<tr>');
        
        var footer_cells = this.createFooterCells();
        for (var i=0; i < footer_cells.length; i++) {
            html.push( ( this.col_classes[i]
                            ? '<th class="' + this.col_classes[i] + '">'
                            : '<th>' )
                     + footer_cells[i]
                     + '</th>' );
        }
        
        html.push('</tr>');
        html.push('</tfoot>');
    }
    
    return( html.join('') );
    
}

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

ligoGrid.prototype._make_html_body = function () {
    
    var dataset = this.Dataset;
    var html = [];
            
    html.push('<tbody>');

    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 = 'javascript:' + this.instanceName + '.onRowClick(' + i + ')';
        var dblclickHandler = this.instanceName + '.onRowDblClick(' + i + ')';
        html.push( '<tr onclick="' + clickHandler + '" ondblclick="' + dblclickHandler + '" id="' + row_id + '">' );
        //html.push( '<tr onclick="javascript:' + this.instanceName + '.onRowClick(' + i + ')" 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('');
}

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

ligoGrid.prototype.isColumnAggregatable = function (columnIdx) {
    //using the display formatspec, see if it is possible to aggregate this column
    
    var aggregatable = false;
        
    var formatspec = this.displayFormatsColIdx[columnIdx];
    
    if (formatspec != null) {
        aggregatable = formatspec.aggregatable;
    }
    
    return(aggregatable);
    
}

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

ligoGrid.prototype.createFooterCells = function () {
    
    var dataset = this.Dataset;
    
    var footer = [];
    
    for (var i=0; i<dataset.columnNames.length; i++) { //iterate over each column name
        if (! this.colsToDisplay[i]) {
            // Don't add empty footer cells for nonexistent columns - it breaks the layout
            continue;
        }

        if (this.displayFormats) {  //only attempt autosum if we know what our displayFormats are
            if (this.autosum) {
                if (this.isColumnAggregatable(i)) {
                    footer[i] = this.displayValue( dataset.aggregate_column(i, 'sum'), i );
                }
            }
        }
        
        //Handle adding footer strings if there is no aggregated value
        if (! footer[i]) {
            if ( this.footerStrings[i] ) {
                footer[i] = this.footerStrings[i];
            }
            else {
                footer[i] = '';     //no value for footer
            }
        }
    }

    return( footer );
    
}

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

ligoGrid.prototype.getFormatClass = function (fieldName, fieldNumber) {
    //precedence is name, number, global
    var formats = this.displayFormats;

    if (formats[fieldName]) {
        return(formats[fieldName]);
    }

    if (formats[fieldNumber]) {
        return(formats[fieldNumber]);
    }

    if (formats['*']) {
        return(formats['*']);
    }

    return('');

}

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

ligoGrid.prototype.highlightRow = function (rowNum) {

    var normal_row_class = this.display.css_rowClass;
    var selected_row_class = this.display.css_selectedRowClass;

    var new_selected_row_id = this.makeRowId(rowNum);


    //Unselect the currently selected row
    //try is needed, because it will just stop processing here if it can not alter the class of the current row
    //this could occur when you refresh the dataset when the new set of data has less elements than previously
    //and you had one selected that was of a greater row number that is currently available
    try {
        if (this.selectedRowId) {
            document.getElementById(this.selectedRowId).removeAttribute('class');
        }
    }
    catch (error) {
        //Do nothing
    }


    //select the new row
    if ( document.getElementById(new_selected_row_id) ) {
        document.getElementById(new_selected_row_id).className = selected_row_class;
    }

    //store the selected row id
    this.selectedRowId = new_selected_row_id;

    // store the selected row to restore it when data changes
    // this assumes the first field of data is unique for each row and stores that
    if ( this.persistentHighlight ) {
        if ( rowNum >= 0 ) {
            this.selectedRowKey = this.Dataset.data[rowNum][0];
            //console.log('Using '+this.selectedRowKey+' as row identifier');
        }
    }

}

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

ligoGrid.prototype._set_status = function (status_name) {

    //console.log(status_name);

    // Do nothing if:
    //  we can't display status change feedback (no images specified),
    //  if nothing changed,
    //  or if the browser is IE (broken setTimeout)
    if ( (! this.display.status) || status_name == this._load_status || !!document.all ) {
        return;
    }

    this._load_status = status_name; // used for the above line

    // tell browser to do full loading indicator after given number of milliseconds
    // this saves CPU if the page loads quick while giving feedback if it takes a while
    if ( status_name == 'begin_loading' ) {
        this.loadingDelay = setTimeout(this.instanceName+'._set_status("loading");', 500);
        return;
    }
    else {
        clearTimeout(this.loadingDelay);
    }

    // Check whether status div is there yet (it won't be if the grid was just created), if not then add it
    var a = document.getElementById('status_'+this.instanceName);
    var b = document.getElementById(this.div);
    if ( !a && b ) {
        a = document.createElement('div');
        a.style.overflow = 'auto';
        a.style.position = 'absolute';
        a.style.top = '0px';
        a.id = 'status_'+this.instanceName;
        b.appendChild(a);
    }

    a.style.visibility = 'hidden';
    a.className = 'status ' + status_name;

    // If status.<status_name> is defined use it as the image url
    if ( this.display.status.hasOwnProperty(status_name) ) {
        a.innerHTML = '<img src="'+this.display.status[status_name]+'">';
    }
    else { // nothing defined so display nothing
        a.innerHTML = null;
        a.style.display = 'none';
        return;
    }

    // Position the loading image in the middle of the grid
    if ( status_name == 'loading' ) {
        a.style.height = '100%';
        a.style.width = '100%';
        a.style.display = 'block';

        var img = a.firstChild;
        img.style.marginTop = (a.clientHeight - img.clientHeight)/2 + 'px';
        img.style.marginLeft = (a.clientWidth - img.clientWidth)/2 + 'px';
    }
    else {
        a.style.height = 'auto';
        a.style.width = 'auto';
        a.style.display = 'inline';
    }

    // Wait until everything is done before displaying it to avoid the layout jumping around
    a.style.visibility = 'visible';
}

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

ligoGrid.prototype.show_column_sort_status = function(column_idx) {   
    var status_strings = {  'desc': '↓',
                            'asc': '↑',
                            'none': '-' };
             
    var sort_link;
    var sort_symbol = status_strings['none'];
    
    //get the current sort
    if (this.Dataset.sort_column_idx == column_idx) {
        if (this.Dataset.sort_asc) {
            sort_symbol = status_strings['asc'];
        }
        else {
            sort_symbol = status_strings['desc'];
        }
    }
    
    //this bit of code handles the flip flop
    var sort_link_asc = true;   //initially sort as ascending
    if (column_idx == this.Dataset.sort_column_idx) {
        sort_link_asc = ! this.Dataset.sort_asc;
        //console.log(sort_link_asc);
    }
    
    var sort_call = this.instanceName + '.Dataset.Sort(' + column_idx + ', ' + sort_link_asc + ')';
    
    var sort_link = '<a href="javascript:' + sort_call + '">' + sort_symbol + '</a>';
    
    return(sort_link);
    
}

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

ligoGrid.prototype.onDS_dataLoading = function() {
    this._set_status('begin_loading');
}

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

ligoGrid.prototype.onDS_dataLoaded = function() {
    
    //FIXME: this method should be inherited, see the parent for more info
    this._init_display_formats();
    
    this.render();

    if ( this.persistentHighlight && this.selectedRowKey ) {
        for (var i=0; i < this.Dataset.data.length; i++) {
            if ( this.Dataset.data[i][0] == this.selectedRowKey ) {
                this.highlightRow(i);
                //console.log('highlight row ', i);
            }
        }
    }

    this._set_status('loaded');
}

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

ligoGrid.prototype.onDS_dataLoadError = function() {
    this._set_status('failed');
}

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