/*
 * 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.Dialog',
                'nf.ErrorHandler'],
            function ($, Slick, nfCommon, nfDialog, nfErrorHandler) {
                return (nf.TemplatesTable = factory($, Slick, nfCommon, nfDialog, nfErrorHandler));
            });
    } else if (typeof exports === 'object' && typeof module === 'object') {
        module.exports = (nf.TemplatesTable =
            factory(require('jquery'),
                require('Slick'),
                require('nf.Common'),
                require('nf.Dialog'),
                require('nf.ErrorHandler')));
    } else {
        nf.TemplatesTable = factory(root.$,
            root.Slick,
            root.nf.Common,
            root.nf.Dialog,
            root.nf.ErrorHandler);
    }
}(this, function ($, Slick, nfCommon, nfDialog, nfErrorHandler) {
    'use strict';

    var isDisconnectionAcknowledged = false;

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

    /**
     * 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 (a.permissions.canRead && b.permissions.canRead) {
                if (sortDetails.columnId === 'timestamp') {
                    var aDate = nfCommon.parseDateTime(a.template[sortDetails.columnId]);
                    var bDate = nfCommon.parseDateTime(b.template[sortDetails.columnId]);
                    return aDate.getTime() - bDate.getTime();
                } else {
                    var aString = nfCommon.isDefinedAndNotNull(a.template[sortDetails.columnId]) ? a.template[sortDetails.columnId] : '';
                    var bString = nfCommon.isDefinedAndNotNull(b.template[sortDetails.columnId]) ? b.template[sortDetails.columnId] : '';
                    return aString === bString ? 0 : aString > bString ? 1 : -1;
                }
            } else {
                if (!a.permissions.canRead && !b.permissions.canRead) {
                    return 0;
                }
                if (a.permissions.canRead) {
                    return 1;
                } else {
                    return -1;
                }
            }
        };

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

    /**
     * Prompts the user before attempting to delete the specified template.
     *
     * @argument {object} templateEntity     The template
     */
    var promptToDeleteTemplate = function (templateEntity) {
        // prompt for deletion
        nfDialog.showYesNoDialog({
            headerText: 'Delete Template',
            dialogContent: 'Delete template \'' + nfCommon.escapeHtml(templateEntity.template.name) + '\'?',
            yesHandler: function () {
                deleteTemplate(templateEntity);
            }
        });
    };

    /**
     * Opens the access policies for the specified template.
     *
     * @param templateEntity
     */
    var openAccessPolicies = function (templateEntity) {
        // only attempt this if we're within a frame
        if (top !== window) {
            // and our parent has canvas utils and shell defined
            if (nfCommon.isDefinedAndNotNull(parent.nf) && nfCommon.isDefinedAndNotNull(parent.nf.PolicyManagement) && nfCommon.isDefinedAndNotNull(parent.nf.Shell)) {
                parent.nf.PolicyManagement.showTemplatePolicy(templateEntity);
                parent.$('#shell-close-button').click();
            }
        }
    };

    /**
     * Deletes the template with the specified id.
     *
     * @argument {string} templateEntity     The template
     */
    var deleteTemplate = function (templateEntity) {
        $.ajax({
            type: 'DELETE',
            url: templateEntity.template.uri + '?' + $.param({
                'disconnectedNodeAcknowledged': isDisconnectionAcknowledged
            }),
            dataType: 'json'
        }).done(function () {
            var templatesGrid = $('#templates-table').data('gridInstance');
            var templatesData = templatesGrid.getData();
            templatesData.deleteItem(templateEntity.id);

            // update the total number of templates
            $('#total-templates').text(templatesData.getItems().length);
        }).fail(nfErrorHandler.handleAjaxError);
    };

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

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

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

            // update the search criteria
            templatesData.setFilterArgs({
                searchString: getFilterText(),
                property: $('#templates-filter-type').combo('getSelectedOption').value
            });
            templatesData.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.template[args.property].search(filterExp) >= 0;
    };

    /**
     * Downloads the specified template.
     *
     * @param {object} templateEntity     The template
     */
    var downloadTemplate = function (templateEntity) {
        window.open(templateEntity.template.uri + '/download');
    };

    return {
        /**
         * Initializes the templates list.
         */
        init: function (disconnectionAcknowledged) {
            isDisconnectionAcknowledged = disconnectionAcknowledged;

            // define the function for filtering the list
            $('#templates-filter').keyup(function () {
                applyFilter();
            });

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

            var timestampFormatter = function (row, cell, value, columnDef, dataContext) {
                if (!dataContext.permissions.canRead) {
                    return '';
                }

                return nfCommon.escapeHtml(dataContext.template.timestamp);
            };

            var nameFormatter = function (row, cell, value, columnDef, dataContext) {
                if (!dataContext.permissions.canRead) {
                    return '<span class="blank">' + nfCommon.escapeHtml(dataContext.id) + '</span>';
                }

                return nfCommon.escapeHtml(dataContext.template.name);
            };

            var descriptionFormatter = function (row, cell, value, columnDef, dataContext) {
                if (!dataContext.permissions.canRead) {
                    return '';
                }

                return nfCommon.formatValue(dataContext.template.description);
            };

            var groupIdFormatter = function (row, cell, value, columnDef, dataContext) {
                if (!dataContext.permissions.canRead) {
                    return '';
                }

                return nfCommon.escapeHtml(dataContext.template.groupId);
            };

            // function for formatting the actions column
            var actionFormatter = function (row, cell, value, columnDef, dataContext) {
                var markup = '';

                if (dataContext.permissions.canRead === true) {
                    markup += '<div title="Download" class="pointer export-template icon icon-template-save"></div>';
                }

                if (dataContext.permissions.canWrite === true) {
                    markup += '<div title="Remove Template" class="pointer prompt-to-delete-template fa fa-trash"></div>';
                }

                // allow policy configuration conditionally if framed
                if (top !== window && nfCommon.canAccessTenants()) {
                    if (nfCommon.isDefinedAndNotNull(parent.nf) && nfCommon.isDefinedAndNotNull(parent.nf.CanvasUtils) && parent.nf.CanvasUtils.isManagedAuthorizer()) {
                        markup += '<div title="Access Policies" class="pointer edit-access-policies fa fa-key"></div>';
                    }
                }

                return markup;
            };

            // initialize the templates table
            var templatesColumns = [
                {
                    id: 'timestamp',
                    name: 'Date/Time',
                    sortable: true,
                    defaultSortAsc: false,
                    resizable: false,
                    formatter: timestampFormatter,
                    width: 225,
                    maxWidth: 225
                },
                {
                    id: 'name',
                    name: 'Name',
                    sortable: true,
                    resizable: true,
                    formatter: nameFormatter
                },
                {
                    id: 'description',
                    name: 'Description',
                    sortable: true,
                    resizable: true,
                    formatter: descriptionFormatter
                },
                {
                    id: 'groupId',
                    name: 'Process Group Id',
                    sortable: true,
                    resizable: true,
                    formatter: groupIdFormatter
                },
                {
                    id: 'actions',
                    name: '&nbsp;',
                    sortable: false,
                    resizable: false,
                    formatter: actionFormatter,
                    width: 100,
                    maxWidth: 100
                }
            ];

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

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

            // initialize the sort
            sort({
                columnId: 'timestamp',
                sortAsc: false
            }, templatesData);

            // initialize the grid
            var templatesGrid = new Slick.Grid('#templates-table', templatesData, templatesColumns, templatesOptions);
            templatesGrid.setSelectionModel(new Slick.RowSelectionModel());
            templatesGrid.registerPlugin(new Slick.AutoTooltips());
            templatesGrid.setSortColumn('timestamp', false);
            templatesGrid.onSort.subscribe(function (e, args) {
                sort({
                    columnId: args.sortCol.id,
                    sortAsc: args.sortAsc
                }, templatesData);
            });

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

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

                // determine the desired action
                if (templatesGrid.getColumns()[args.cell].id === 'actions') {
                    if (target.hasClass('export-template')) {
                        downloadTemplate(item);
                    } else if (target.hasClass('prompt-to-delete-template')) {
                        promptToDeleteTemplate(item);
                    } else if (target.hasClass('edit-access-policies')) {
                        openAccessPolicies(item);
                    }
                }
            });

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

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

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

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

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

        /**
         * Load the processor templates table.
         */
        loadTemplatesTable: function () {
            return $.ajax({
                type: 'GET',
                url: config.urls.templates,
                dataType: 'json'
            }).done(function (response) {
                // ensure there are groups specified
                if (nfCommon.isDefinedAndNotNull(response.templates)) {
                    var templatesGrid = $('#templates-table').data('gridInstance');
                    var templatesData = templatesGrid.getData();

                    // set the items
                    templatesData.setItems(response.templates);
                    templatesData.reSort();
                    templatesGrid.invalidate();

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

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