/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* global define, module, require, exports */

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['jquery',
                'Slick',
                'nf.Common',
                'nf.ErrorHandler',
                'nf.Dialog'],
            function ($, Slick, nfCommon, nfErrorHandler, nfDialog) {
                return (nf.CountersTable = factory($, Slick, nfCommon, nfErrorHandler, nfDialog));
            });
    } else if (typeof exports === 'object' && typeof module === 'object') {
        module.exports = (nf.CountersTable =
            factory(require('jquery'),
                require('Slick'),
                require('nf.Common'),
                require('nf.ErrorHandler'),
                require('nf.Dialog')));
    } else {
        nf.CountersTable = factory(root.$,
            root.Slick,
            root.nf.Common,
            root.nf.ErrorHandler,
            root.nf.Dialog);
    }
}(this, function ($, Slick, nfCommon, nfErrorHandler, nfDialog) {
    'use strict';

    /**
     * Configuration object used to hold a number of configuration items.
     */
    var config = {
        urls: {
            counters: '../nifi-api/counters'
        }
    };

    /**
     * Sorts the specified data using the specified sort details.
     *
     * @param {object} sortDetails
     * @param {object} data
     */
    var sort = function (sortDetails, data) {
        // defines a function for sorting
        var comparer = function (a, b) {
            if (sortDetails.columnId === 'value') {
                var aCount = nfCommon.parseCount(a[sortDetails.columnId]);
                var bCount = nfCommon.parseCount(b[sortDetails.columnId]);
                return aCount - bCount;
            } else {
                var aString = nfCommon.isDefinedAndNotNull(a[sortDetails.columnId]) ? a[sortDetails.columnId] : '';
                var bString = nfCommon.isDefinedAndNotNull(b[sortDetails.columnId]) ? b[sortDetails.columnId] : '';
                return aString === bString ? 0 : aString > bString ? 1 : -1;
            }
        };

        // perform the sort
        data.sort(comparer, sortDetails.sortAsc);
    };

    /**
     * Get the text out of the filter field. If the filter field doesn't
     * have any text it will contain the text 'filter list' so this method
     * accounts for that.
     */
    var getFilterText = function () {
        return $('#counters-filter').val();
    };

    /**
     * Applies the filter found in the filter expression text field.
     */
    var applyFilter = function () {
        // get the dataview
        var countersGrid = $('#counters-table').data('gridInstance');

        // ensure the grid has been initialized
        if (nfCommon.isDefinedAndNotNull(countersGrid)) {
            var countersData = countersGrid.getData();

            // update the search criteria
            countersData.setFilterArgs({
                searchString: getFilterText(),
                property: $('#counters-filter-type').combo('getSelectedOption').value
            });
            countersData.refresh();
        }
    };

    /**
     * Performs the filtering.
     *
     * @param {object} item     The item subject to filtering
     * @param {object} args     Filter arguments
     * @returns {Boolean}       Whether or not to include the item
     */
    var filter = function (item, args) {
        if (args.searchString === '') {
            return true;
        }

        try {
            // perform the row filtering
            var filterExp = new RegExp(args.searchString, 'i');
        } catch (e) {
            // invalid regex
            return false;
        }

        // perform the filter
        return item[args.property].search(filterExp) >= 0;
    };

    /**
     * Resets the specified counter.
     *
     * @argument {object} item     The counter item
     */
    var resetCounter = function (item) {
        // prompt reset confirmation
        nfDialog.showYesNoDialog({
            headerText: 'Reset Counter',
            dialogContent: 'Reset counter \'' + nfCommon.escapeHtml(item.name) + '\' to default value?',
            yesHandler: function () {
                $.ajax({
                    type: 'PUT',
                    url: config.urls.counters + '/' + encodeURIComponent(item.id),
                    dataType: 'json'
                }).done(function (response) {
                    var counter = response.counter;

                    // get the table and update the row accordingly
                    var countersGrid = $('#counters-table').data('gridInstance');
                    var countersData = countersGrid.getData();
                    countersData.updateItem(counter.id, counter);
                }).fail(nfErrorHandler.handleAjaxError);
            }
        });
    };

    return {
        /**
         * Initializes the counters list.
         */
        init: function () {
            // define the function for filtering the list
            $('#counters-filter').keyup(function () {
                applyFilter();
            });

            // filter type
            $('#counters-filter-type').combo({
                options: [{
                    text: 'by name',
                    value: 'name'
                }, {
                    text: 'by context',
                    value: 'context'
                }],
                select: function (option) {
                    applyFilter();
                }
            });

            // initialize the templates table
            var countersColumns = [
                {
                    id: 'context',
                    name: 'Context',
                    field: 'context',
                    sortable: true,
                    resizable: true,
                    formatter: nfCommon.genericValueFormatter
                },
                {
                    id: 'name',
                    name: 'Name',
                    field: 'name',
                    sortable: true,
                    resizable: true,
                    formatter: nfCommon.genericValueFormatter
                },
                {
                    id: 'value',
                    name: 'Value',
                    field: 'value',
                    sortable: true,
                    resizable: true,
                    defaultSortAsc: false,
                    formatter: nfCommon.genericValueFormatter
                }
            ];

            // only allow dfm's to reset counters
            if (nfCommon.canModifyCounters()) {
                // function for formatting the actions column
                var actionFormatter = function (row, cell, value, columnDef, dataContext) {
                    return '<div title="Reset Counter" class="pointer reset-counter fa fa-undo"></div>';
                };

                // add the action column
                countersColumns.push({
                    id: 'actions',
                    name: '&nbsp;',
                    sortable: false,
                    resizable: false,
                    formatter: actionFormatter,
                    width: 100,
                    maxWidth: 100
                });
            }

            var countersOptions = {
                autosizeColsMode: Slick.GridAutosizeColsMode.LegacyForceFit,
                enableTextSelectionOnCells: true,
                enableCellNavigation: false,
                enableColumnReorder: false,
                autoEdit: false,
                rowHeight: 24
            };

            // initialize the dataview
            var countersData = new Slick.Data.DataView({
                inlineFilters: false
            });
            countersData.setItems([]);
            countersData.setFilterArgs({
                searchString: getFilterText(),
                property: $('#counters-filter-type').combo('getSelectedOption').value
            });
            countersData.setFilter(filter);

            // initialize the sort
            sort({
                columnId: 'context',
                sortAsc: true
            }, countersData);

            // initialize the grid
            var countersGrid = new Slick.Grid('#counters-table', countersData, countersColumns, countersOptions);
            countersGrid.setSelectionModel(new Slick.RowSelectionModel());
            countersGrid.registerPlugin(new Slick.AutoTooltips());
            countersGrid.setSortColumn('context', true);
            countersGrid.onSort.subscribe(function (e, args) {
                sort({
                    columnId: args.sortCol.field,
                    sortAsc: args.sortAsc
                }, countersData);
            });

            // configure a click listener
            countersGrid.onClick.subscribe(function (e, args) {
                var target = $(e.target);

                // get the node at this row
                var item = countersData.getItem(args.row);

                // determine the desired action
                if (countersGrid.getColumns()[args.cell].id === 'actions') {
                    if (target.hasClass('reset-counter')) {
                        resetCounter(item);
                    }
                }
            });

            // wire up the dataview to the grid
            countersData.onRowCountChanged.subscribe(function (e, args) {
                countersGrid.updateRowCount();
                countersGrid.render();

                // update the total number of displayed processors
                $('#displayed-counters').text(args.current);
            });
            countersData.onRowsChanged.subscribe(function (e, args) {
                countersGrid.invalidateRows(args.rows);
                countersGrid.render();
            });

            // hold onto an instance of the grid
            $('#counters-table').data('gridInstance', countersGrid);

            // initialize the number of display items
            $('#displayed-counters').text('0');
        },

        /**
         * Update the size of the grid based on its container's current size.
         */
        resetTableSize: function () {
            var countersGrid = $('#counters-table').data('gridInstance');
            if (nfCommon.isDefinedAndNotNull(countersGrid)) {
                countersGrid.resizeCanvas();
            }
        },

        /**
         * Load the processor counters table.
         */
        loadCountersTable: function () {
            return $.ajax({
                type: 'GET',
                url: config.urls.counters,
                dataType: 'json'
            }).done(function (response) {
                var report = response.counters;
                var aggregateSnapshot = report.aggregateSnapshot;

                // ensure there are groups specified
                if (nfCommon.isDefinedAndNotNull(aggregateSnapshot.counters)) {
                    var countersGrid = $('#counters-table').data('gridInstance');
                    var countersData = countersGrid.getData();

                    // set the items
                    countersData.setItems(aggregateSnapshot.counters);
                    countersData.reSort();
                    countersGrid.invalidate();

                    // update the stats last refreshed timestamp
                    $('#counters-last-refreshed').text(aggregateSnapshot.generated);

                    // update the total number of processors
                    $('#total-counters').text(aggregateSnapshot.counters.length);
                } else {
                    $('#total-counters').text('0');
                }
            }).fail(nfErrorHandler.handleAjaxError);
        }
    };
}));
