/**
@fileOverview
Part of the QlikView WorkBench Javascript Library (http://www.qlikwebworkbench.com)<br /><br />
Copyright (c) 2009 QlikTech International AB (http://www.qliktech.com)<br /><br />
All Rights Reserved. Email support@qliktech.com for licensing and support information.<br /><br />
@author <a href="mailto:email@industrialcodebox.com">Industrial CodeBox</a>
*/

Qww.Table = {};

/** 
Constructs a new Qww.Table.Header instance.
@class Represents a table header.<br />
*/
Qww.Table.Header = function() {

    /**
    Text for the column header.
    @type String
    */
    this.HeaderText = "";

    /**
    Index of the column header within the TableMgr.
    @type Int
    */
    this.HeaderIndex = -1;

    /**
    Index of the column header within the QlikView table. Use this when calling Qww.Table.Mgr.Sort.
    @see Qww.Table.Mgr.Sort
    @type Int
    */
    this.HeaderSortIndex = -1;

    /**
    @ignore
    */
    this.HeaderSortKey = "";

    /**
    Direction which the column is sorted in.
    @type Qww.Table.Header.SortDirection
    */
    this.SortDirection = null;

    /**
    Sort order for the column.
    @type Int
    */
    this.SortOrder = -1;

};

Qww.Table.Header.prototype.GetSummary = function() {

    return "HeaderText:" + this.HeaderText +
        ",HeaderIndex:" + this.HeaderIndex +
        ",HeaderSortIndex:" + this.HeaderSortIndex +
        ",HeaderSortKey:" + this.HeaderSortKey +
        ",SortDirection:" + this.SortDirection +
        ",SortOrder:" + this.SortOrder;
};

/** 
@class Enumeration representing the direction in which a table column is sorted.<br />
*/
Qww.Table.Header.SortDirection = {

    /**
    @constant
    */
    Ascending: "ASI",

    /**
    @constant
    */
    Descending: "DSI",

    /**
    @constant
    */
    Unsorted: "Unsorted"
};

/**
Constructs a new Qww.Table.Row instance.
@class Represents a row of data in the table.<br />
*/
Qww.Table.Row = function() {

    /** 
    Returns whether the row is a header row.
    @type Bool        
    */
    this.IsHeader = false;

    /** 
    Record number for the row.
    @type Int
    @see Qww.Table.Mgr#SelectSingleRecord
    */
    this.RecordNumber = -1;

    /** 
    Array of Qww.Table.Cell objects for the row.<br />
    @type Qww.Table.Cell[]
    */
    this.Cells = [];
};

/**
Returns a summary string of the row.
*/
Qww.Table.Row.prototype.GetSummary = function() {

    var res = "IsHeader:" + this.IsHeader + ",RecordNumber:" + this.RecordNumber + "(Data:";

    for (var i = 0; i < this.Cells.length; i++)
        res += "," + this.Cells[i].Text;

    return res;
};

/**
Constructs a new Qww.Table.Cell instance.
@class Represents a cell within a row of data in the table.<br />
*/
Qww.Table.Cell = function() {

    /**
    Text value of the cell.
    @type String
    */
    this.Text = "";

    //rowItem.Style = elem.getAttribute("style");
    //rowItem.IsNumber = elem.getAttribute("isnum");
    //rowItem.SelectType = elem.getAttribute("selecttype");

};

/** 
Constructs a new Qww.Table.Mgr instance.
@constructor
@class Class to manage communication with a QlikView straight table or table control.<br />
@param {Object} cfg JSON object to configure Qww.Table.Mgr.
@param {String} cfg.ObjectID Id of the table in the QlikView document.
@param {String} [cfg.ApplicationID=null] Id/Name of the QlikView document. Note this is <strong>only</strong> necessary when 
multiple QlikView documents are being used on the same web page and should otherwise be set to null.
@param {Int} [cfg.PageSize=50] Number of rows to return in a single page of data.
@param {Bool} [cfg.DoNotInitialise=false] Set this to false if you do not want this particular object to initialise the 
underlying object with QlikView Server. Use this option for example if you are already using another object or control which 
will have already initialised this. In this mode this object will simply parse the existing data for this object as it comese from 
QlikView Server.
@param {Function} [cfg.OnUpdate] Function to call whenever the data is updated.
@returns {Qww.Table.Mgr} Qww.Table.Mgr
@example
var tblMgr = new Qww.Table.Mgr(
{
// ObjectID of the underlying Table Box 
// in QlikView
ObjectID: "TBDEMO_RESULTS",
// The number of results to download for 
// each page.
PageSize: 30,
// Function to call whenever the data updates.
OnUpdate: showResults
});
*/
Qww.Table.Mgr = function(cfg) {

    var _isInitialized = false;
    var me = this;

    /**
    Reference to the configuration object which was used to create construct the object. This 
    allows certain properties to be updated after initial instantiation.
    @type Object
    */
    this.Cfg = cfg;

    function a(s) {
        QwwJs.Alert("QwwJs_TableMgr:" + s);
    };

    if (!cfg)
        a("No cfgObject specified");

    var pageSize = 50;

    if (cfg.PageSize)
        pageSize = cfg.PageSize;

    if (cfg.ObjectID) {
        var objId = cfg.ObjectID;
    }
    else {
        a("No objectId specified");
        return;
    };

    var applicationId = cfg.ApplicationID;

    this.colsOfResults = new Array();
    this.rowsOfResults = new Array();

    /**
    Collection of header columns for the table.
    @type Qww.Table.Header[]
    */
    this.Headers = new Array();

    this.SupportsSorting = true;

    /** 
    Whether the table is enabled (visible) within the QlikView document. This can be dependent, 
    for example, upon show and hide conditions set for the object in QlikView.
    @deprecated Use Mode.
    @type Bool
    */
    this.Enabled = true;

    /** 
    The state of the object. This can be dependent, for example, upon show and hide conditions set 
    for the object in QlikView.
    @type Qww.QlikViewObjectState
    */
    this.Mode = "";

    /** 
    Total number of rows in the table.
    @type Int
    */
    this.TotalSize = -1;

    /** 
    Page offset of the current page of data.
    @type Int
    */
    this.PageOffset = -1;

    /** 
    Number of rows per page of data received for the table.
    @type Int
    */
    this.PageSize = -1;

    /** 
    Number of rows in the current page of data (note that it may be less than the PageSize if it's the last page of data).
    @type Int
    */
    this.NoRows = -1;


    //    /** 
    //    Current Title/Caption of the table control.
    //    @type String
    //    */
    //    this.Title = "";

    /** 
    Caption for text object.
    @type Qww.ObjectCaption
    */
    this.Caption = new Qww.ObjectCaption();

    /** 
    Number of rows in the current page of data (note that it may be less than the PageSize if it's the last page of data).
    @type Int
    */
    this.NoPages = -1;

    this.HeaderRowPresent = false;

    /**
    Returns the current page number which the TableMgr holds data for.
    @property
    @type Int
    */
    this.CurrentPage = 1;

    this.InitialTotalSize = -1;
    this.InitialNoPages = -1;

    var hasThisTableInitialised = false;

    /**
    @ignore
    Returns an array of Qww.QvsConnectorSet objects which represents the initiasation which 
    needs to be sent to QlikView server in order to initialise this object.
    @returns {Qww.QvsConnectorSet[]} Array of Qww.QvsConnectorSet objects which represents 
    the initiasation for this object.
    */
    this.GetInitialisationSets = function() {

        var sets = [];

        sets.push(new Qww.QvsConnectorSet(applicationId, objId, "add", "mode;text;fixedrows;choice;pageoffset;pagesize;totalsize", false));
        sets.push(new Qww.QvsConnectorSet(applicationId, objId + ".Caption", "add", "mode;text", false));
        sets.push(new Qww.QvsConnectorSet(applicationId, objId + ".Head", "add", "mode;text", false));
        sets.push(new Qww.QvsConnectorSet(applicationId, objId, "pagesize", pageSize, false));
        sets.push(new Qww.QvsConnectorSet(applicationId, objId, "pageoffset", 0, true));

        hasThisTableInitialised = true;

        return sets;
    };

    this.Initialise = function() {

        var box = qwwHub.DoAvqSelect(applicationId, objId);

        // if(box == null)
        //
        // The hasThisTableInitialised test was added because if there was, for example, 
        // an export for print button added to the page for the same table, then this would
        // already have been initialised but not with any columns so not data would ever come 
        // back
        //
        if (box == null && hasThisTableInitialised == false) {

            //<set name="Document.TB01" add="mode;text;fixedrows;choice;pageoffset;pagesize;totalsize"/>
            //<set name="Document.TB01.Caption" add="mode;text"/>
            //<set name="Document.TB01.Head" add="mode;text"/>
            //<set name="Document.TB01.RE" add="mode;action;ie6false"/>
            //<set name="Document.TB01" pagesize="20"/>
            //<set name="Document.TB01" pageoffset="0"/>

            qwwHub.DoAllSets(me.GetInitialisationSets());

            //            // qwwHub.DoAvqSet(applicationId, objId, "add", "mode;pageoffset;pagesize;totalsize", false);
            //            qwwHub.DoAvqSet(applicationId, objId, "add", "mode;text;fixedrows;choice;pageoffset;pagesize;totalsize", false);

            //            qwwHub.DoAvqSet(applicationId, objId + ".Caption", "add", "mode;text", false);
            //            qwwHub.DoAvqSet(applicationId, objId + ".Head", "add", "mode;text", false);

            //            // <set name="Document.TB01.RE" add="mode;action;ie6false"/><
            //            // qwwHub.DoAvqSet(applicationId, objId + ".RE", "add", "mode;action;ie6false", false);

            //            //            if (cfg.ColumnIndexes)
            //            //                for (var i = 0; i < cfg.ColumnIndexes.length; i++)
            //            //                    qwwHub.DoAvqSet(applicationId, objId + ".C" + cfg.ColumnIndexes[i], "add", "mode;text;value", false);

            //            qwwHub.DoAvqSet(applicationId, objId, "pagesize", pageSize, false);
            //            qwwHub.DoAvqSet(applicationId, objId, "pageoffset", 0, true);

            hasThisTableInitialised = true;
            return false;
        }

        _isInitialized = true;
        hasThisTableInitialised = true;

        return true; // handshake - there will not be a new update
    };

    /**
    Selects a single row and column in the table.
    @param {Int} columnIndex Column index of input field.
    @param {Int} rowIndex Row index of input field.
    @param {String|Number} value Value to set input field to.
    @param {Bool} [isFinal=true] If this is the final command in this set to send to QlikView Server.
    Set this to false if you intend to call some methods on other objects and would like all the commands to 
    be sent in one request.
    */
    this.SetInputField = function(columnIndex, rowIndex, value, isFinal) {

        if (isFinal == null)
            isFinal = true;

        rowIndex = rowIndex + 1;

        qwwHub.DoAvqSet(applicationId, objId, "inputvalue", columnIndex + ":" + rowIndex + ":" + value, isFinal);
    };

    /**
    Selects a single row and column in the table. Note the .SelectSingleRecord method of the Qww.Ctls.FlexiGrid.Mgr 
    delegates to this method when a TableMgr is being used as a dataprovider for the flexi grid. 
    @param {Int} recordNumber Record number to select.
    @param {Int} [selectColumn=0] Column to select.
    */
    this.SelectSingleRecord = function(recordNumber, selectColumn) {

        if (!selectColumn)
            selectColumn = 0;

        qwwHub.DoAvqSet(applicationId, objId, "rect", selectColumn + ":" + recordNumber + ":1:1", true);
    };

    /** 
    Moves to the page number specified. Note that the first page is 1.
    @param {Int} pageNumber Page number to move to.
    */
    this.SetPage = function(pageNumber) {

        var newOffset;

        this.CurrentPage = pageNumber;

        qwwHub.DoAvqSet(applicationId, objId, "pagesize", pageSize, false);

        //newOffset = ((pageNumber * 1 - 1) * this.PageSize) + 1;

        //newOffset = ((pageNumber * 1 - 1) * this.PageSize);

        if (pageNumber == 1) {
            newOffset = 0;
        }
        else {
            //newOffset = ((pageNumber * 1 - 1) * this.PageSize) + 1;
            newOffset = ((pageNumber * 1 - 1) * this.PageSize);
        }

        //        if (pageNumber == 1) {
        //            qwwHub.DoAvqSet(applicationId, objId, "pagesize", pageSize + 1, false);
        //            newOffset = 0;
        //        }
        //        else {
        //            qwwHub.DoAvqSet(applicationId, objId, "pagesize", pageSize, false);
        //            newOffset = ((pageNumber * 1 - 1) * this.PageSize) + 1;
        //        }

        qwwHub.DoAvqSet(applicationId, objId, "pageoffset", newOffset, true);
    };

    /** 
    Moves to the previous page of data in the table.
    @returns {Bool} Returns true if the page was changed (if the table 
    was already set to the first page then false will be returned).
    */
    this.PageUp = function() {
        if (me.CurrentPage < me.NoPages) {
            this.SetPage(this.CurrentPage + 1);
            return true;
        }
        else {
            return false;
        }
    };

    /** 
    Moves to the next page of data in the table.
    @returns {Bool} Returns true if the page was changed (if the table 
    was already set to the last page then false will be returned).
    */
    this.PageDown = function() {
        if (this.CurrentPage > 1) {
            this.SetPage(this.CurrentPage - 1);
            return true;
        }
        else {
            return false;
        }
    };

    /**
    Returns the specified row for the table.
    @param {Int} rowNumber Index of the row in the table to return. Note this 
    row must be present in the current page of data.
    @returns {Qww.Table.Row} Object representing the table row.
    */
    this.GetRow = function(rowNumber/*, parseAdditionalAllAttributes*/) {

        var req = rowNumber;

        if (this.rowsOfResults[rowNumber] == null) {

            var row = new Qww.Table.Row();

            row.RecordNumber = (1 * this.PageOffset) + (1 * rowNumber);

            var rowToUse = rowNumber;

            if (this.HeaderRowPresent == true) {
                //row.RecordNumber++;
                rowToUse++;
            }

            var arrLength = this.colsOfResults.length;
            
            for (var i = 0; i < arrLength; i++) {

                var colOfResults = this.colsOfResults[i];

                var rowItem = new Qww.Table.Cell();

                if (rowToUse >= colOfResults.length)
                    rowItem.Text = "No data for record " + rowToUse;
                else {
                    var elem = colOfResults[rowToUse];

                    if (!row.IsHeader) {
                        var boolIsHeader = elem.getAttribute("isheader") == "true";
                        row.IsHeader = boolIsHeader;
                    }

                    //rowItem.Text = elem.getAttribute("text");
                    rowItem.Text = elem.getAttribute("title");

                    // TODO : Added by CB to fix for QVS 9
                    if (rowItem.Text == null) rowItem.Text = elem.getAttribute("text");

                    //                    if (parseAdditionalAllAttributes) {
                    //                        rowItem.Style = elem.getAttribute("style");
                    //                        rowItem.IsNumber = elem.getAttribute("isnum");
                    //                        rowItem.SelectType = elem.getAttribute("selecttype");
                    //                    }
                }

                row.Cells[i] = rowItem;
            }

            this.rowsOfResults[rowNumber] = row;
        }

        return this.rowsOfResults[rowNumber];
    };

    /**
    Sort the table data.
    @param {Int} headerSortIndex Column index to sort on.
    @param {Bool} [resetToFirstPage=true] Whether to reset the view to the first page.
    */
    this.Sort = function(headerSortIndex, resetToFirstPage) {

        if (!resetToFirstPage) resetToFirstPage = true;

        qwwHub.DoAvqSet(applicationId, "Document." + objId + ".C" + headerSortIndex, "click", "Document." + objId + ".C" + headerSortIndex, !resetToFirstPage);

        if (resetToFirstPage)
            me.SetPage(1);
    };

    this.OnAvqUpdateComplete = function() {

        if (!cfg.DoNotInitialise || cfg.DoNotInitialise == false) {
            if (!_isInitialized) {
                if (!me.Initialise()) {
                    return;
                }
            }
        }

        var CH = qwwHub.DoAvqSelect(applicationId, objId);

        if (CH == null) {
            // This might be called as the result of an OnAvqUpdateComplete
            // which might be for an unrelated table or lisbox, in which case
            // the XML wont contain that for XH
            return;
        }
        else {

            this.colsOfResults.length = 0;
            this.rowsOfResults.length = 0;

            this.Caption.ParseFromXml(CH);

            var cols = jQuery("value", CH);

            var colIndex = 0;

            var noCols = cols.length;
            
            for (var i = 0; i < noCols; i++) {


                var col = cols[i];

                var name = col.getAttribute("name");

                //                if (name == "Caption")
                //                    this.Title = col.getAttribute("label");

                if (name == "Caption" || name.length > 3 || name.substr(0, 1) != "C")
                    continue;

                var vals = col.childNodes;

                if (vals.length > 0 && vals[0].getAttribute("position") == "top") {

                    var obj = new Qww.Table.Header();

                    obj.HeaderText = vals[0].getAttribute("text");
                    obj.HeaderIndex = colIndex;
                    obj.HeaderSortIndex = 1 * (col.getAttribute("name").replace("C", "")); // col;
                    obj.HeaderSortKey = col.getAttribute("name");

                    obj.SortOrder = col.getAttribute("sortorder");

                    obj.SortDirection = "";

                    if (vals[0].childNodes.length > 0) {

                        var children = vals[0].childNodes;

                        var noChildren = children.length;

                        for (j = 0; j < noChildren; j++) {
                            if (children[j].nodeName == "icon") {
                                var iconNode = children[j];
                                var dir = iconNode.getAttribute("stamp");

                                if (dir == "ASI") {
                                    obj.SortDirection = Qww.Table.Header.SortDirection.Ascending; //"asc";
                                    break;
                                }

                                if (dir == "DSI") {
                                    obj.SortDirection = Qww.Table.Header.SortDirection.Descending; //"desc";
                                    break;
                                }
                            }
                        }
                    }
                    else
                        obj.SortDirection = Qww.Table.Header.SortDirection.Unsorted;

                    this.Headers[colIndex] = obj;
                }

                this.colsOfResults[colIndex] = vals;

                colIndex++;
            }

            this.Mode = CH.getAttribute("mode");
            this.Enabled = (this.Mode == "enabled");

            if (vals != null) {
                this.NoRows = vals.length;

                this.PageSize = CH.getAttribute("pagesize") * 1;

                if (this.NoRows > 0) {
                    if (this.colsOfResults[0][0].getAttribute("isheader") == "true") {
                        this.NoRows--;
                        //this.PageSize--;
                        this.HeaderRowPresent = true;
                    }
                    else {
                        this.HeaderRowPresent = false;
                    }
                }
                else {
                    this.HeaderRowPresent = false;
                }

                this.TotalSize = CH.getAttribute("totalsize"); // -1 // this will include the header; used to include header, no more.
                this.PageOffset = CH.getAttribute("pageoffset") * 1;

                if (this.InitialTotalSize == -1)
                    this.InitialTotalSize = this.TotalSize;

                this.NoPages = this.TotalSize / this.PageSize;
                this.NoPages = Math.round(this.NoPages + 0.49);

                if (this.InitialNoPages == -1)
                    this.InitialNoPages = this.NoPages;

                this.CurrentPage = Math.round(this.PageOffset / this.PageSize) + 1;
            }

            if (this.Cfg.OnUpdate)
                this.Cfg.OnUpdate(me);
        }
    };

    if (qwwHub)
        qwwHub.Register(this);
};

/**
Returns a html string summary of the main properties of the TableMgr.
*/
Qww.Table.Mgr.prototype.GetSummary = function() {

    return "Caption:" + this.Caption.Text + ", " + Qww.GetSummary([/*"Title", */"Enabled", "TotalSize", "PageOffset", "PageSize",
                            "NoRows", "CurrentPage", "NoPages", "InitialTotalSize",
                            "InitialNoPages", "HeaderRowPresent"], this);
};
