/************************************************************************
* DYNAMIC COLUMNS extension for jTable                                  *
* (Show/hide/resize columns)                                            *
*************************************************************************/
(function ($) {

    //Reference to base object members
    var base = {
        _create: $.hik.jtable.prototype._create,
        _normalizeFieldOptions: $.hik.jtable.prototype._normalizeFieldOptions,
        _createHeaderCellForField: $.hik.jtable.prototype._createHeaderCellForField,
        _createCellForRecordField: $.hik.jtable.prototype._createCellForRecordField
    };

    //extension members
    $.extend(true, $.hik.jtable.prototype, {

        /************************************************************************
        * DEFAULT OPTIONS / EVENTS                                              *
        *************************************************************************/

        options: {
            tableId: undefined,
            columnResizable: true,
            columnSelectable: true
        },

        /************************************************************************
        * PRIVATE FIELDS                                                        *
        *************************************************************************/

        _$columnSelectionDiv: null,
        _$columnResizeBar: null,
        _cookieKeyPrefix: null,
        _currentResizeArgs: null,

        /************************************************************************
        * OVERRIDED METHODS                                                     *
        *************************************************************************/

        /* Overrides _addRowToTableHead method.
        *************************************************************************/

        _create: function () {
            base._create.apply(this, arguments);

            this._createColumnResizeBar();
            this._createColumnSelection();

            if (this.options.saveUserPreferences) {
                this._loadColumnSettings();
            }

            this._normalizeColumnWidths();
        },

        /* Normalizes some options for a field (sets default values).
        *************************************************************************/
        _normalizeFieldOptions: function (fieldName, props) {
            base._normalizeFieldOptions.apply(this, arguments);

            //columnResizable
            if (this.options.columnResizable) {
                props.columnResizable = (props.columnResizable != false);
            }

            //visibility
            if (!props.visibility) {
                props.visibility = 'visible';
            }
        },

        /* Overrides _createHeaderCellForField to make columns dynamic.
        *************************************************************************/
        _createHeaderCellForField: function (fieldName, field) {
            var $headerCell = base._createHeaderCellForField.apply(this, arguments);

            //Make data columns resizable except the last one
            if (this.options.columnResizable && field.columnResizable && (fieldName != this._columnList[this._columnList.length - 1])) {
                this._makeColumnResizable($headerCell);
            }

            //Hide column if needed
            if (field.visibility == 'hidden') {
                $headerCell.hide();
            }

            return $headerCell;
        },

        /* Overrides _createHeaderCellForField to decide show or hide a column.
        *************************************************************************/
        _createCellForRecordField: function (record, fieldName) {
            var $column = base._createCellForRecordField.apply(this, arguments);

            var field = this.options.fields[fieldName];
            if (field.visibility == 'hidden') {
                $column.hide();
            }

            return $column;
        },

        /************************************************************************
        * PUBLIC METHODS                                                        *
        *************************************************************************/

        /* Changes visibility of a column.
        *************************************************************************/
        changeColumnVisibility: function (columnName, visibility) {
            this._changeColumnVisibilityInternal(columnName, visibility);
            this._normalizeColumnWidths();
            if (this.options.saveUserPreferences) {
                this._saveColumnSettings();
            }
        },

        /************************************************************************
        * PRIVATE METHODS                                                       *
        *************************************************************************/

        /* Changes visibility of a column.
        *************************************************************************/
        _changeColumnVisibilityInternal: function (columnName, visibility) {
            //Check if there is a column with given name
            var columnIndex = this._columnList.indexOf(columnName);
            if (columnIndex < 0) {
                this._logWarn('Column "' + columnName + '" does not exist in fields!');
                return;
            }

            //Check if visibility value is valid
            if (['visible', 'hidden', 'fixed'].indexOf(visibility) < 0) {
                this._logWarn('Visibility value is not valid: "' + visibility + '"! Options are: visible, hidden, fixed.');
                return;
            }

            //Get the field
            var field = this.options.fields[columnName];
            if (field.visibility == visibility) {
                return; //No action if new value is same as old one.
            }

            //Hide or show the column if needed
            var columnIndexInTable = this._firstDataColumnOffset + columnIndex + 1;
            if (field.visibility != 'hidden' && visibility == 'hidden') {
                this._$table
                    .find('>thead >tr >th:nth-child(' + columnIndexInTable + '),>tbody >tr >td:nth-child(' + columnIndexInTable + ')')
                    .hide();
            } else if (field.visibility == 'hidden' && visibility != 'hidden') {
                this._$table
                    .find('>thead >tr >th:nth-child(' + columnIndexInTable + '),>tbody >tr >td:nth-child(' + columnIndexInTable + ')')
                    .show()
                    .css('display', 'table-cell');
            }

            field.visibility = visibility;
        },

        /* Prepares dialog to change settings.
        *************************************************************************/
        _createColumnSelection: function () {
            var self = this;

            //Create a div for dialog and add to container element
            this._$columnSelectionDiv = $('<div />')
                .addClass('jtable-column-selection-container')
                .appendTo(self._$mainContainer);
            
            this._$table.children('thead').bind('contextmenu', function (e) {
                if (!self.options.columnSelectable) {
                    return;
                }
                
                e.preventDefault();

                //Make an overlay div to disable page clicks
                $('<div />')
                    .addClass('jtable-contextmenu-overlay')
                    .click(function () {
                        $(this).remove();
                        self._$columnSelectionDiv.hide();
                    })
                    .bind('contextmenu', function () { return false; })
                    .appendTo(document.body);

                self._fillColumnSelection();
                
                //Calculate position of column selection list and show it

                var containerOffset = self._$mainContainer.offset();
                var selectionDivTop = e.pageY - containerOffset.top;
                var selectionDivLeft = e.pageX - containerOffset.left;
                
                var selectionDivMinWidth = 100; //in pixels
                var containerWidth = self._$mainContainer.width();

                //If user clicks right area of header of the table, show list at a little left
                if ((containerWidth > selectionDivMinWidth) && (selectionDivLeft > (containerWidth - selectionDivMinWidth))) {
                    selectionDivLeft = containerWidth - selectionDivMinWidth;
                }

                self._$columnSelectionDiv.css({
                    left: selectionDivLeft,
                    top: selectionDivTop,
                    'min-width': selectionDivMinWidth + 'px'
                }).show();
            });
        },
        
        /* Prepares content of settings dialog.
        *************************************************************************/
        _fillColumnSelection: function () {
            var self = this;

            var $columnsUl = $('<ul></ul>')
                .addClass('jtable-column-select-list');
            for (var i = 0; i < this._columnList.length; i++) {
                var columnName = this._columnList[i];
                var field = this.options.fields[columnName];

                //Crete li element
                var $columnLi = $('<li></li>').appendTo($columnsUl);

                //Create label for the checkbox
                var $label = $('<label for="' + columnName + '"></label>')
                    .append($('<span>' + (field.title || columnName) + '</span>'))
                    .appendTo($columnLi);

                //Create checkbox
                var $checkbox = $('<input type="checkbox" name="' + columnName + '">')
                    .prependTo($label)
                    .click(function () {
                        var $clickedCheckbox = $(this);
                        var clickedColumnName = $clickedCheckbox.attr('name');
                        var clickedField = self.options.fields[clickedColumnName];
                        if (clickedField.visibility == 'fixed') {
                            return;
                        }

                        self.changeColumnVisibility(clickedColumnName, $clickedCheckbox.is(':checked') ? 'visible' : 'hidden');
                    });

                //Check, if column if shown
                if (field.visibility != 'hidden') {
                    $checkbox.attr('checked', 'checked');
                }

                //Disable, if column is fixed
                if (field.visibility == 'fixed') {
                    $checkbox.attr('disabled', 'disabled');
                }
            }

            this._$columnSelectionDiv.html($columnsUl);
        },

        /* creates a vertical bar that is shown while resizing columns.
        *************************************************************************/
        _createColumnResizeBar: function () {
            this._$columnResizeBar = $('<div />')
                .addClass('jtable-column-resize-bar')
                .appendTo(this._$mainContainer)
                .hide();
        },

        /* Makes a column sortable.
        *************************************************************************/
        _makeColumnResizable: function ($columnHeader) {
            var self = this;

            //Create a handler to handle mouse click event
            $('<div />')
                .addClass('jtable-column-resize-handler')
                .appendTo($columnHeader.find('.jtable-column-header-container')) //Append the handler to the column
                .mousedown(function (downevent) { //handle mousedown event for the handler
                    downevent.preventDefault();
                    downevent.stopPropagation();

                    var mainContainerOffset = self._$mainContainer.offset();

                    //Get a reference to the next column
                    var $nextColumnHeader = $columnHeader.nextAll('th.jtable-column-header:visible:first');
                    if (!$nextColumnHeader.length) {
                        return;
                    }

                    //Store some information to be used on resizing
                    var minimumColumnWidth = 10; //A column's width can not be smaller than 10 pixel.
                    self._currentResizeArgs = {
                        currentColumnStartWidth: $columnHeader.outerWidth(),
                        minWidth: minimumColumnWidth,
                        maxWidth: $columnHeader.outerWidth() + $nextColumnHeader.outerWidth() - minimumColumnWidth,
                        mouseStartX: downevent.pageX,
                        minResizeX: function () { return this.mouseStartX - (this.currentColumnStartWidth - this.minWidth); },
                        maxResizeX: function () { return this.mouseStartX + (this.maxWidth - this.currentColumnStartWidth); }
                    };

                    //Handle mouse move event to move resizing bar
                    var resizeonmousemove = function (moveevent) {
                        if (!self._currentResizeArgs) {
                            return;
                        }

                        var resizeBarX = self._normalizeNumber(moveevent.pageX, self._currentResizeArgs.minResizeX(), self._currentResizeArgs.maxResizeX());
                        self._$columnResizeBar.css('left', (resizeBarX - mainContainerOffset.left) + 'px');
                    };

                    //Handle mouse up event to finish resizing of the column
                    var resizeonmouseup = function (upevent) {
                        if (!self._currentResizeArgs) {
                            return;
                        }

                        $(document).unbind('mousemove', resizeonmousemove);
                        $(document).unbind('mouseup', resizeonmouseup);

                        self._$columnResizeBar.hide();

                        //Calculate new widths in pixels
                        var mouseChangeX = upevent.pageX - self._currentResizeArgs.mouseStartX;
                        var currentColumnFinalWidth = self._normalizeNumber(self._currentResizeArgs.currentColumnStartWidth + mouseChangeX, self._currentResizeArgs.minWidth, self._currentResizeArgs.maxWidth);
                        var nextColumnFinalWidth = $nextColumnHeader.outerWidth() + (self._currentResizeArgs.currentColumnStartWidth - currentColumnFinalWidth);

                        //Calculate widths as percent
                        var pixelToPercentRatio = $columnHeader.data('width-in-percent') / self._currentResizeArgs.currentColumnStartWidth;
                        $columnHeader.data('width-in-percent', currentColumnFinalWidth * pixelToPercentRatio);
                        $nextColumnHeader.data('width-in-percent', nextColumnFinalWidth * pixelToPercentRatio);

                        //Set new widths to columns (resize!)
                        $columnHeader.css('width', $columnHeader.data('width-in-percent') + '%');
                        $nextColumnHeader.css('width', $nextColumnHeader.data('width-in-percent') + '%');

                        //Normalize all column widths
                        self._normalizeColumnWidths();

                        //Finish resizing
                        self._currentResizeArgs = null;

                        //Save current preferences
                        if (self.options.saveUserPreferences) {
                            self._saveColumnSettings();
                        }
                    };

                    //Show vertical resize bar
                    self._$columnResizeBar
                        .show()
                        .css({
                            top: ($columnHeader.offset().top - mainContainerOffset.top) + 'px',
                            left: (downevent.pageX - mainContainerOffset.left) + 'px',
                            height: (self._$table.outerHeight()) + 'px'
                        });

                    //Bind events
                    $(document).bind('mousemove', resizeonmousemove);
                    $(document).bind('mouseup', resizeonmouseup);
                });
        },

        /* Normalizes column widths as percent for current view.
        *************************************************************************/
        _normalizeColumnWidths: function () {

            //Set command column width
            var commandColumnHeaders = this._$table
                .find('>thead th.jtable-command-column-header')
                .data('width-in-percent', 1)
                .css('width', '1%');

            //Find data columns
            var headerCells = this._$table.find('>thead th.jtable-column-header');

            //Calculate total width of data columns
            var totalWidthInPixel = 0;
            headerCells.each(function () {
                var $cell = $(this);
                if ($cell.is(':visible')) {
                    totalWidthInPixel += $cell.outerWidth();
                }
            });

            //Calculate width of each column
            var columnWidhts = {};
            var availableWidthInPercent = 100.0 - commandColumnHeaders.length;
            headerCells.each(function () {
                var $cell = $(this);
                if ($cell.is(':visible')) {
                    var fieldName = $cell.data('fieldName');
                    var widthInPercent = $cell.outerWidth() * availableWidthInPercent / totalWidthInPixel;
                    columnWidhts[fieldName] = widthInPercent;
                }
            });

            //Set width of each column
            headerCells.each(function () {
                var $cell = $(this);
                if ($cell.is(':visible')) {
                    var fieldName = $cell.data('fieldName');
                    $cell.data('width-in-percent', columnWidhts[fieldName]).css('width', columnWidhts[fieldName] + '%');
                }
            });
        },

        /* Saves field setting to cookie.
        *  Saved setting will be a string like that:
        * fieldName1=visible;23|fieldName2=hidden;17|...
        *************************************************************************/
        _saveColumnSettings: function () {
            var self = this;
            var fieldSettings = '';
            this._$table.find('>thead >tr >th.jtable-column-header').each(function () {
                var $cell = $(this);
                var fieldName = $cell.data('fieldName');
                var columnWidth = $cell.data('width-in-percent');
                var fieldVisibility = self.options.fields[fieldName].visibility;
                var fieldSetting = fieldName + "=" + fieldVisibility + ';' + columnWidth;
                fieldSettings = fieldSettings + fieldSetting + '|';
            });

            this._setCookie('column-settings', fieldSettings.substr(0, fieldSettings.length - 1));
        },

        /* Loads field settings from cookie that is saved by _saveFieldSettings method.
        *************************************************************************/
        _loadColumnSettings: function () {
            var self = this;
            var columnSettingsCookie = this._getCookie('column-settings');
            if (!columnSettingsCookie) {
                return;
            }

            var columnSettings = {};
            $.each(columnSettingsCookie.split('|'), function (inx, fieldSetting) {
                var splitted = fieldSetting.split('=');
                var fieldName = splitted[0];
                var settings = splitted[1].split(';');
                columnSettings[fieldName] = {
                    columnVisibility: settings[0],
                    columnWidth: settings[1]
                };
            });

            var headerCells = this._$table.find('>thead >tr >th.jtable-column-header');
            headerCells.each(function () {
                var $cell = $(this);
                var fieldName = $cell.data('fieldName');
                var field = self.options.fields[fieldName];
                if (columnSettings[fieldName]) {
                    if (field.visibility != 'fixed') {
                        self._changeColumnVisibilityInternal(fieldName, columnSettings[fieldName].columnVisibility);
                    }

                    $cell.data('width-in-percent', columnSettings[fieldName].columnWidth).css('width', columnSettings[fieldName].columnWidth + '%');
                }
            });
        }

    });

})(jQuery);
