/**
 * Created by asantoni on 11/09/15.
 */

var AIRTIME = (function(AIRTIME) {

    //Module initialization
    if (AIRTIME.widgets === undefined) {
        AIRTIME.widgets = {};
    }

    //Table widget constructor
    /**
     *
     *
     * @param wrapperDOMNode
     * @param {boolean} bItemSelection
     * @param {Object} toolbarButtons
     * @param {Object} dataTablesOptions
     * @param {Object} [emptyPlaceholder]
     * @param {string} emptyPlaceholder.html
     * @param {string} emptyPlaceholder.iconClass
     *
     * @returns {Table}
     * @constructor
     */
    var Table = function(wrapperDOMNode, bItemSelection, toolbarButtons, dataTablesOptions, emptyPlaceholder) {

        var self = this;

        self.HUGE_INT = Math.pow(2, 53) - 1;

        //Constants and enumerations
        self.SELECTION_MODE = {
            SINGLE : 0,
            MULTI_SHIFT : 1,
            MULTI_CTRL : 2
        };

        //Member variables
        self._datatable = null;
        self._selectedRows = []; //An array containing the underlying objects for each selected row. (Easy to use!)
        //self._selectedRowVisualIdxMap = []; //A map of the visual index of a selected rows onto the actual row data.
        self._selectedRowVisualIdxMin = self.HUGE_INT;
        self._selectedRowVisualIdxMax = -1;
        self._$wrapperDOMNode = null;
        self._toolbarButtons = null;
        //Save some of the constructor parameters
        self._$wrapperDOMNode = $(wrapperDOMNode);
        self._toolbarButtons = toolbarButtons;
        self._emptyPlaceholder = emptyPlaceholder;

        // Exclude the leftmost column if we're implementing item selection
        self._colVisExcludeColumns = bItemSelection ? [0] : [];

        //Finish initialization of the datatable since everything is declared by now.

        // If selection is enabled, add in the checkbox column.
        if (bItemSelection) {
            dataTablesOptions["aoColumns"].unshift(
                /* Checkbox */        { "sTitle" : "", "mData" : self._datatablesCheckboxDataDelegate.bind(this), "bSortable"   : false                 , "bSearchable" : false                   , "sWidth" : "24px"         , "sClass" : "airtime_table_checkbox" }
            );
        }

        var options = {
            "aoColumns": [
                /* Title */           { "sTitle" : $.i18n._("Make sure to override me")              , "mDataProp" : "track_title"  , "sClass"      : "library_title"       , "sWidth"      : "170px"                 },
            ],
            "bProcessing": true,
            "bServerSide": true,
            "sAjaxSource": baseUrl+"rest/media", //Override me
            "sAjaxDataProp": "aaData",
            "bScrollCollapse": false,
            "deferLoading" : 1, //0 tells it there's zero elements loaded and disables the automatic AJAX. We don't want to load until after we bind all our event handlers, to prevent a race condition with the "init" event callback.
            "sPaginationType": "full_numbers",
            "bJQueryUI": true,
            "bAutoWidth": false,
            "aaSorting": [],
            "iDisplayLength": 25,
            "aLengthMenu": [25, 50, 100],
            "oLanguage" : getDatatablesStrings({
                "sEmptyTable": $.i18n._(""),
                "sZeroRecords": $.i18n._("No matching results found.")
            }),
            "oColVis": {
                "sAlign": "right",
                "aiExclude": self._colVisExcludeColumns,
                "buttonText": $.i18n._("Columns"),
                "iOverlayFade": 0
            },
            // z = ColResize, R = ColReorder, C = ColVis
            "sDom": 'Rf<"dt-process-rel"r><"H"<"table_toolbar"C>><"dataTables_scrolling"t<".empty_placeholder"<".empty_placeholder_image"><".empty_placeholder_text">>><"F"lip>>',

            "fnPreDrawCallback": function () {
                $("#draggingContainer").remove();
            },
            "fnServerData": self._fetchData.bind(self),
            //"fnInitComplete" : function() { self._setupEventHandlers(bItemSelection) }
            "fnDrawCallback": function () {
                self.clearSelection();
            }
        };

        //Override any options with those passed in as arguments to this constructor.
        for (var key in dataTablesOptions)
        {
            options[key] = dataTablesOptions[key];
        }

        if (options.fnCreatedRow) {
            options.fnCreatedRow = options.fnCreatedRow.bind(self);
        }
        if (options.fnDrawCallback) {
            options.fnDrawCallback = options.fnDrawCallback.bind(self);
        }

        self._datatable = self._$wrapperDOMNode.dataTable(options);
        // self._datatable.fnDraw(); //Load the AJAX data now that our event handlers have been bound.
        self._setupEventHandlers(bItemSelection);

        //return self._datatable;
        return self;
    };

    Table.prototype.assignDblClickHandler = function(fn) {
        $(this._datatable, 'tbody tr').on('dblclick', this._SELECTORS.SELECTION_TABLE_ROW, fn);
    };


    /* Set up global event handlers for the datatable.
     *  @param bItemSelection Whether or not row selection behaviour should be enabled for this widget.
     * */
    Table.prototype._setupEventHandlers = function(bItemSelection) {

        var self = this;

        /** This table row event handler is created once and catches events for any row. (It's less resource intensive
         *  than having a per-row callback...)
         */
        if (bItemSelection) {
            $(self._datatable, 'tbody tr').on('click contextmenu', self._SELECTORS.SELECTION_TABLE_ROW, function (e) {
                var aData = self._datatable.fnGetData(this);
                var iDisplayIndex = $(this).index();  // The index of the row in the current page in the table.
                var nRow = this;

                e.stopPropagation();
                e.preventDefault();
                document.getSelection().removeAllRanges();

                var selectionMode = self.SELECTION_MODE.SINGLE;
                if (e.shiftKey) {
                    selectionMode = self.SELECTION_MODE.MULTI_SHIFT;
                } else if (e.ctrlKey) {
                    selectionMode = self.SELECTION_MODE.MULTI_CTRL;
                }

                if (e.button == 2) {
                    selectionMode = self.SELECTION_MODE.SINGLE;
                }
                self.selectRow(nRow, aData, selectionMode, iDisplayIndex);
            });

            $(self._datatable, 'tbody tr').on('click', self._SELECTORS.SELECTION_CHECKBOX, function(e) {
                $this = $(this);

                var iVisualRowIdx = $this.parent().index();
                var aData = self._datatable.fnGetData(iVisualRowIdx);
                var selectionMode = self.SELECTION_MODE.MULTI_CTRL; //Behaviour for checkboxes.
                if (e.shiftKey) {
                    selectionMode = self.SELECTION_MODE.MULTI_SHIFT;
                }
                self.selectRow($this.parent(), aData, selectionMode, iVisualRowIdx); //Always multiselect for checkboxes
                e.stopPropagation();
                return true;
            });

            // Clear selection when switching pages
            $(self._datatable).on('page', function () {
                self.clearSelection();
            });
        }

        // On filter, display the number of total and filtered results in the search bar
        $(self._datatable).on('filter', function() {
            var dt = self._datatable, f = dt.closest(".dataTables_wrapper").find(".filter-message"),
                totalRecords = dt.fnSettings().fnRecordsTotal(),
                totalDisplayRecords = dt.fnSettings().fnRecordsDisplay();

            if (f.length === 0) {
                var el = document.createElement("span");
                el.setAttribute("class", "filter-message");
                f = dt.closest(".dataTables_wrapper").find(".dataTables_filter").append(el).find(".filter-message");
            }

            f.text(totalRecords > totalDisplayRecords ?
                $.i18n._("Filtering out ") + (totalRecords - totalDisplayRecords)
                + $.i18n._(" of ") + totalRecords
                + $.i18n._(" records") : ""
            );

            dt.closest(".dataTables_wrapper").find('.dataTables_filter input[type="text"]')
                .css('padding-right', f.outerWidth());
        });

        //Since this function is already called when the datatables initialization is complete, we know the DOM
        //structure for the datatable exists and can just proceed to setup the toolbar DOM elements now.
        self._setupToolbarButtons(self._toolbarButtons);
    };


    /**
     * Member functions
     *
     */

    /** Populate the toolbar with buttons.
     *
     * @param buttons A list of objects which contain button definitions. See self.TOOLBAR_BUTTON_ROLES for an example, or use getStandardToolbarButtons() to get a list of them.
     * @private
     */
    Table.prototype._setupToolbarButtons = function(buttons) {
        var self = this;
        var $menu = self._$wrapperDOMNode.parent().parent().find("div.table_toolbar");
        $menu.addClass("btn-toolbar");

        //Create the toolbar buttons.
        $.each(buttons, function(idx, btn) {
            var buttonElement = self._createToolbarButton(btn.title, btn.iconClass, btn.extraBtnClass, btn.elementId);
            $menu.append(buttonElement);
            btn.element = buttonElement; //Save this guy in case you need it later.
            //Bind event handlers to each button
            $.each(btn.eventHandlers, function(eventName, eventCallback) {
                $(buttonElement).on(eventName, function () {
                    if ($(buttonElement).find("button").is(':disabled')) { return; }
                    eventCallback();
                });
            });
        });

        self._checkToolbarButtons();
    };

    /**
     * Check each of the toolbar buttons for the table and disable them if their constraints are invalid.
     *
     * Passes current Table object context to function calls.
     */
    Table.prototype._checkToolbarButtons = function () {
        var self = this;
        $.each(self._toolbarButtons, function (idx, btn) {
            var btnNode = $(btn.element).find("button").get(0);
            btnNode.disabled = btn.disabled = !btn.validateConstraints.call(self);
        });
    };

    /** Create the DOM element for a toolbar button and return it. */
    Table.prototype._createToolbarButton = function(title, iconClass, extraBtnClass, elementId) {

        if (!iconClass) {
            iconClass = 'icon-plus';
        }

        // var title = $.i18n._('Delete');
        var outerDiv = document.createElement("div");
        outerDiv.className = 'btn-group';
        outerDiv.title = title;
        var innerButton = document.createElement("button");
        //innerButton.className = 'btn btn-small ' + extraBtnClass;
        innerButton.className = 'btn ' + extraBtnClass;
        innerButton.id = elementId;
        var innerIcon = document.createElement("i");
        innerIcon.className = 'icon-white ' + iconClass;
        var innerTextSpan = document.createElement('span');
        var innerText = document.createTextNode(title);
        innerTextSpan.appendChild(innerText);
        innerButton.appendChild(innerIcon);
        innerButton.appendChild(innerTextSpan);
        outerDiv.appendChild(innerButton);

        /* Here's an example of what the button HTML should look like:
         "<div class='btn-group' title=" + $.i18n._('Delete') + ">" +
         "<button class='btn btn-small btn-danger' id='sb-trash'>" +
         "<i class='icon-white icon-trash'></i>" +
         "<span>" + $.i18n._('Delete') + "</span>" +
         "</button>" +
         "</div>"*/
        return outerDiv;
    };

    Table.prototype.clearSelection = function() {
        this._selectedRows = [];
        //self._selectedRowVisualIdxMap = [];
        this._selectedRowVisualIdxMin = self.HUGE_INT;
        this._selectedRowVisualIdxMax = -1;
        this._$wrapperDOMNode.find('.selected').removeClass('selected');
        this._$wrapperDOMNode.find(this._SELECTORS.SELECTION_CHECKBOX).find('input').attr('checked', false);
        this._checkToolbarButtons();
    };

    /** @param nRow is a tr DOM node (non-jQuery)
     * @param aData is an array containing the raw data for the row. Can be null if you don't have it.
     * @param selectionMode is an SELECT_MODE enum. Specify what selection mode you want to use for this action.
     * @param iVisualRowIdx is an integer which corresponds to the index of the clicked row, as it appears to the user.
     *             eg. The 5th row in the table will have an iVisualRowIdx of 4 (0-based).
     */
    Table.prototype.selectRow = function(nRow, aData, selectionMode, iVisualRowIdx) {

        var self = this;

        //Default to single item selection.
        if (selectionMode == undefined) {
            selectionMode = self.SELECTION_MODE.SINGLE;
        }

        var $nRow = $(nRow);

        //Regular single left-click mode
        if (selectionMode == self.SELECTION_MODE.SINGLE) {
            self.clearSelection();

            self._selectedRows.push(aData);
            self._selectedRowVisualIdxMin = iVisualRowIdx;
            self._selectedRowVisualIdxMax = iVisualRowIdx;
            //self._selectedRowVisualIdxMap[iVisualRowIdx] = aData;

            $nRow.addClass('selected');
            $nRow.find(self._SELECTORS.SELECTION_CHECKBOX).find('input').attr('checked', true);
        }
        //Ctrl-click multi row selection mode
        else if (selectionMode == self.SELECTION_MODE.MULTI_CTRL) {

            var foundAtIdx = $.inArray(aData, self._selectedRows);

            //console.log('checkbox mouse', iVisualRowIdx, foundAtIdx);

            //If the clicked row is already selected, deselect it.
            if (foundAtIdx >= 0 && self._selectedRows.length >= 1) {
                self._selectedRows.splice(foundAtIdx, 1);
                $nRow.removeClass('selected');
                $nRow.find(self._SELECTORS.SELECTION_CHECKBOX).find('input').attr('checked', false);
            }
            else {
                self._selectedRows.push(aData);

                self._selectedRowVisualIdxMin = iVisualRowIdx;
                self._selectedRowVisualIdxMax = iVisualRowIdx;

                $nRow.addClass('selected');
                $nRow.find(self._SELECTORS.SELECTION_CHECKBOX).find('input').attr('checked', true);
            }
        }
        //Shift-click multi row selection mode
        else if (selectionMode == self.SELECTION_MODE.MULTI_SHIFT) {

            //If there's no rows selected, just behave like single selection.
            if (self._selectedRows.length == 0) {
                return self.selectRow(nRow, aData, self.SELECTION_MODE.SINGLE, iVisualRowIdx);
            }

            if (iVisualRowIdx > self._selectedRowVisualIdxMax) {
                self._selectedRowVisualIdxMax = iVisualRowIdx;
            }
            if (iVisualRowIdx < self._selectedRowVisualIdxMin) {
                self._selectedRowVisualIdxMin = iVisualRowIdx;
            }

            var selectionStartRowIdx = Math.min(iVisualRowIdx, self._selectedRowVisualIdxMin);
            var selectionEndRowIdx = Math.min(iVisualRowIdx, self._selectedRowVisualIdxMax);


            //We can assume there's at least 1 row already selected now.
            var allRows = self._datatable.fnGetData();

            self._selectedRows = [];
            for (var i = self._selectedRowVisualIdxMin; i <= self._selectedRowVisualIdxMax; i++)
            {
                self._selectedRows.push(allRows[i]);
                $row = $($nRow.parent().children()[i]);
                $row.addClass('selected');
                $row.find(self._SELECTORS.SELECTION_CHECKBOX).find('input').attr('checked', true);
            }

        }
        else {
            console.log("Unimplemented selection mode");
        }

        self._checkToolbarButtons();
    };

    Table.prototype.getSelectedRows = function() {
        return this._selectedRows;
    };

    Table.prototype.getEmptyPlaceholder = function () {
        return this._emptyPlaceholder;
    };

    Table.prototype._handleAjaxError = function(r) {
        // If the request was denied due to permissioning
        if (r.status === 403) {
            $(".dt-process-rel").hide();
            $('.empty_placeholder_text').text($.i18n._("You don't have permission to view this resource."));
            $('.empty_placeholder').show();
        }
    };

    /** Grab data from a REST API and format so that DataTables can display it.
     *  This is the DataTables REST adapter function, basically.
     * */
    Table.prototype._fetchData = function ( sSource, aoData, fnCallback, oSettings )
    {
        var self = this;
        var echo = aoData[0].value; //Datatables state tracking. Must be included.

        var sortColName = "";
        var sortDir = "";
        var search = self._$wrapperDOMNode.closest(".dataTables_wrapper").find(".dataTables_filter").find("input").val();
        if (oSettings.aaSorting.length > 0) {
            var sortColIdx = oSettings.aaSorting[0][0];
            sortColName = oSettings.aoColumns[sortColIdx].mDataProp;
            sortDir = oSettings.aaSorting[0][1].toUpperCase();
        }

        // FIXME: We should probably just be sending aoData back here..?
        $.ajax({
            "dataType": 'json',
            "type": "GET",
            "url": sSource,
            "data": {
                "limit": oSettings._iDisplayLength,
                "offset": oSettings._iDisplayStart,
                "sort": sortColName,
                "sort_dir": sortDir,
                "search": search
            },
            "success": function (json, textStatus, jqXHR) {
                var rawResponseJSON = json;
                json = [];
                json.aaData = rawResponseJSON;
                json.iTotalRecords = jqXHR.getResponseHeader('X-TOTAL-COUNT');
                json.iTotalDisplayRecords = json.iTotalRecords;
                json.sEcho = echo;

                //Pass it along to datatables.
                fnCallback(json);
            },
            "error": self._handleAjaxError
        });
    };

    Table.prototype._datatablesCheckboxDataDelegate = function(rowData, callType, dataToSave) {

        if (callType == undefined) {
            //Supposed to return the raw data for the type here.
            return null;
        } else if (callType == 'display') {
            return "<input type='checkbox'>";
        } else if (callType == 'sort') {
            return null;
        } else if (callType == 'type') {
            return "input";
        } else if (callType == 'set') {
            //The data to set is in dataToSave.
            return;
        } else if (callType == 'filter') {
            return null;
        }

        //For all other calls, just return the data as this:
        return "check";
    };


    //Accessors / Mutators

    Table.prototype.getDatatable = function() {
        return this._datatable;
    };


    //Static initializers / Class variables

    Table.prototype._SELECTORS = Object.freeze({
        SELECTION_CHECKBOX: ".airtime_table_checkbox",
        SELECTION_TABLE_ROW: "tr"
    });


    Table.TOOLBAR_BUTTON_ROLES = {
        NEW :    0,
        EDIT :   1,
        DELETE : 2
    };
    Object.freeze(Table.TOOLBAR_BUTTON_ROLES);


    //Set of standard buttons. Use getStandardToolbarButtons() to grab these and pass them to the init() function.
    Table._STANDARD_TOOLBAR_BUTTONS = {};
    Table._STANDARD_TOOLBAR_BUTTONS[Table.TOOLBAR_BUTTON_ROLES.NEW] = {
        title : $.i18n._('New'),
        iconClass : "icon-plus",
        extraBtnClass : "btn-small btn-new",
        elementId : '',
        eventHandlers : {},
        validateConstraints: function () { return true; }
    };
    Table._STANDARD_TOOLBAR_BUTTONS[Table.TOOLBAR_BUTTON_ROLES.EDIT] = {
        title : $.i18n._('Edit'),
        iconClass : "icon-pencil",
        extraBtnClass : "btn-small",
        elementId : '',
        eventHandlers : {},
        validateConstraints: function () { return true; }
    };
    Table._STANDARD_TOOLBAR_BUTTONS[Table.TOOLBAR_BUTTON_ROLES.DELETE] = {
        title : $.i18n._('Delete'),
        iconClass : "icon-trash",
        extraBtnClass : "btn-small btn-danger",
        elementId : '',
        eventHandlers : {},
        validateConstraints: function () { return true; }
    };
    Object.freeze(Table._STANDARD_TOOLBAR_BUTTONS);


    //Static method
    Table.getStandardToolbarButtons = function() {
        //Return a deep copy
        return jQuery.extend(true, {}, Table._STANDARD_TOOLBAR_BUTTONS);
    };

    //Add Table to the widgets namespace
    AIRTIME.widgets.Table = Table;


    return AIRTIME;

}(AIRTIME || {}));


