/*
 * 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 */

/**
 * Views state for a given component.
 */
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['jquery',
                'Slick',
                'nf.ClusterSummary',
                'nf.ErrorHandler',
                'nf.Dialog',
                'nf.Common'],
            function ($, Slick, nfClusterSummary, nfErrorHandler, nfDialog, nfCommon) {
                return (nf.ComponentState = factory($, Slick, nfClusterSummary, nfErrorHandler, nfDialog, nfCommon));
            });
    } else if (typeof exports === 'object' && typeof module === 'object') {
        module.exports = (nf.ComponentState =
            factory(require('jquery'),
                require('Slick'),
                require('nf.ClusterSummary'),
                require('nf.ErrorHandler'),
                require('nf.Dialog'),
                require('nf.Common')));
    } else {
        nf.ComponentState = factory(root.$,
            root.Slick,
            root.nf.ClusterSummary,
            root.nf.ErrorHandler,
            root.nf.Dialog,
            root.nf.Common);
    }
}(this, function ($, Slick, nfClusterSummary, nfErrorHandler, nfDialog, nfCommon) {
    'use strict';

    /**
     * Get the component state column model.
     */
    var getComponentStateColumnModel = function () {
        // initialize the queue listing table
        var componentStateColumns = [
            {
                id: 'key',
                field: 'key',
                name: 'Key',
                sortable: true,
                resizable: true,
                formatter: nfCommon.genericValueFormatter
            },
            {
                id: 'value',
                field: 'value',
                name: 'Value',
                sortable: true,
                resizable: true,
                formatter: nfCommon.genericValueFormatter
            }
        ];

        // conditionally show the cluster node identifier
        if (nfClusterSummary.isConnectedToCluster()) {
            componentStateColumns.push({
                id: 'scope',
                field: 'scope',
                name: 'Scope',
                sortable: true,
                resizable: true,
                formatter: nfCommon.genericValueFormatter
            });
        }

        return componentStateColumns;
    };

    /**
     * Filters the component state table.
     */
    var applyFilter = function () {
        // get the dataview
        var componentStateTable = $('#component-state-table').data('gridInstance');

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

            // update the search criteria
            componentStateData.setFilterArgs({
                searchString: getFilterText()
            });
            componentStateData.refresh();
        }
    };

    /**
     * Determines if the item matches the filter.
     *
     * @param {object} item     The item to filter
     * @param {object} args     The filter criteria
     * @returns {boolean}       Whether the item matches the filter
     */
    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;
        }

        // determine if the item matches the filter
        var matchesKey = item['key'].search(filterExp) >= 0;
        var matchesValue = item['value'].search(filterExp) >= 0;

        // conditionally consider the scope
        var matchesScope = false;
        if (nfCommon.isDefinedAndNotNull(item['scope'])) {
            matchesScope = item['scope'].search(filterExp) >= 0;
        }

        return matchesKey || matchesValue || matchesScope;
    };

    /**
     * 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) {
            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 $('#component-state-filter').val();
    };

    /**
     * Clears the component state table.
     */
    var clearTable = function () {
        var componentStateGrid = $('#component-state-table').data('gridInstance');
        var componentStateData = componentStateGrid.getData();
        componentStateData.setItems([]);

        // hide the partial results details message
        $('#component-state-partial-results-container').hide();

        // clear the total number entries
        $('#displayed-component-state-entries').text('0');
        $('#total-component-state-entries').text('0');
    };

    /**
     * Loads the table with the component state.
     *
     * @param {object} componentState
     */
    var loadComponentState = function (localState, clusterState) {
        var count = 0;
        var totalEntries = 0;
        var showPartialDetails = false;

        var componentStateGrid = $('#component-state-table').data('gridInstance');
        componentStateGrid.setColumns(getComponentStateColumnModel());
        var componentStateData = componentStateGrid.getData();

        // begin the update
        componentStateData.beginUpdate();

        // local state
        if (nfCommon.isDefinedAndNotNull(localState)) {
            $.each(localState.state, function (i, stateEntry) {
                componentStateData.addItem($.extend({
                    id: count++,
                    scope: stateEntry.clusterNodeAddress
                }, stateEntry));
            });
            totalEntries += localState.totalEntryCount;

            if (nfCommon.isDefinedAndNotNull(localState.state) && localState.totalEntryCount !== localState.state.length) {
                showPartialDetails = true;
            }
        }

        if (nfCommon.isDefinedAndNotNull(clusterState)) {
            $.each(clusterState.state, function (i, stateEntry) {
                componentStateData.addItem($.extend({
                    id: count++,
                    scope: 'Cluster'
                }, stateEntry));
            });
            totalEntries += clusterState.totalEntryCount;

            if (nfCommon.isDefinedAndNotNull(clusterState.state) && clusterState.totalEntryCount !== clusterState.state.length) {
                showPartialDetails = true;
            }
        }

        // complete the update
        componentStateData.endUpdate();
        componentStateData.reSort();

        if (showPartialDetails) {
            $('#component-state-partial-results-container').show();
        }

        // update the total number of state entries
        $('#total-component-state-entries').text(nfCommon.formatInteger(totalEntries));
    };

    /**
     * Reset the dialog.
     */
    var resetDialog = function () {
        // clear the fields
        $('#component-state-name').text('');
        $('#component-state-description').text('');

        // clear any filter strings
        $('#component-state-filter').val('');

        // reset clear link
        $('#clear-link').removeClass('disabled').attr('title', '');

        // clear the table
        clearTable();

        // clear the component
        $('#component-state-table').removeData('component');
    };

    return {
        init: function () {
            // intialize the component state filter
            $('#component-state-filter').on('keyup', function () {
                applyFilter();
            });

            // initialize the component state dialog
            $('#component-state-dialog').modal({
                scrollableContentStyle: 'scrollable',
                headerText: 'Component State',
                buttons: [{
                    buttonText: 'Close',
                    color: {
                        base: '#728E9B',
                        hover: '#004849',
                        text: '#ffffff'
                    },
                    handler: {
                        click: function () {
                            $(this).modal('hide');
                        }
                    }
                }],
                handler: {
                    close: function () {
                        resetDialog();
                    },
                    resize: function () {
                        $('#component-state-description')
                            .width($('#component-state-dialog').find('.dialog-content').innerWidth() - 1)
                            .text($('#component-state-description').attr('title'))
                            .ellipsis();
                    }
                }
            });

            // clear state link
            $('#clear-link').on('click', function () {
                if ($(this).hasClass('disabled') === false) {
                    var componentStateTable = $('#component-state-table');

                    // ensure there is state to clear
                    var componentStateGrid = componentStateTable.data('gridInstance');
                    var stateEntryCount = componentStateGrid.getDataLength();

                    if (stateEntryCount > 0) {
                        var componentEntity = componentStateTable.data('component');
                        $.ajax({
                            type: 'POST',
                            url: componentEntity.uri + '/state/clear-requests',
                            dataType: 'json'
                        }).done(function (response) {
                            // clear the table
                            clearTable();

                            // reload the table with no state
                            loadComponentState()
                        }).fail(nfErrorHandler.handleAjaxError);
                    } else {
                        nfDialog.showOkDialog({
                            headerText: 'Component State',
                            dialogContent: 'This component has no state to clear.'
                        });
                    }
                }
            });

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

            // initialize the dataview
            var componentStateData = new Slick.Data.DataView({
                inlineFilters: false
            });
            componentStateData.setItems([]);
            componentStateData.setFilterArgs({
                searchString: '',
                property: 'key'
            });
            componentStateData.setFilter(filter);

            // initialize the sort
            sort({
                columnId: 'key',
                sortAsc: true
            }, componentStateData);

            // initialize the grid
            var componentStateGrid = new Slick.Grid('#component-state-table', componentStateData, getComponentStateColumnModel(), componentStateOptions);
            componentStateGrid.setSelectionModel(new Slick.RowSelectionModel());
            componentStateGrid.registerPlugin(new Slick.AutoTooltips());
            componentStateGrid.setSortColumn('key', true);
            componentStateGrid.onSort.subscribe(function (e, args) {
                sort({
                    columnId: args.sortCol.field,
                    sortAsc: args.sortAsc
                }, componentStateData);
            });

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

                // update the total number of displayed items
                $('#displayed-component-state-entries').text(args.current);
            });
            componentStateData.onRowsChanged.subscribe(function (e, args) {
                componentStateGrid.invalidateRows(args.rows);
                componentStateGrid.render();
            });

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

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

        /**
         * Shows the state for a given component.
         *
         * @param {object} componentEntity
         * @param {boolean} canClear
         */
        showState: function (componentEntity, canClear) {
            return $.ajax({
                type: 'GET',
                url: componentEntity.uri + '/state',
                dataType: 'json'
            }).done(function (response) {
                var componentState = response.componentState;
                var componentStateTable = $('#component-state-table');

                // load the table
                loadComponentState(componentState.localState, componentState.clusterState);

                // show the dialog
                $('#component-state-dialog').modal('show');

                // populate the name/description
                $('#component-state-name').text(componentEntity.component.name);
                $('#component-state-description')
                    .width($('#component-state-dialog').find('.dialog-content').innerWidth() - 1)
                    .text(componentState.stateDescription)
                    .ellipsis();

                // store the component
                componentStateTable.data('component', componentEntity);

                // only activate the link when appropriate
                if (canClear === false) {
                    $('#clear-link').addClass('disabled').attr('title', 'Component state can only be cleared when the component is not actively running');
                }

                // reset the grid size
                var componentStateGrid = componentStateTable.data('gridInstance');
                componentStateGrid.resizeCanvas();
            }).fail(nfErrorHandler.handleAjaxError);
        }
    };
}));
