// SPDX-FileCopyrightText: 2018 Icinga GmbH <https://icinga.com>
// SPDX-License-Identifier: GPL-3.0-or-later

/**
 * Icinga.Behavior.ActionTable
 *
 * A multi selection that distincts between the table rows using the row action URL filter
 */
(function(Icinga, $) {

    "use strict";

    /**
     * Remove one leading and trailing bracket and all text outside those brackets
     *
     * @param   str     {String}
     * @returns         {string}
     */
    var stripBrackets = function (str) {
        return str.replace(/^[^\(]*\(/, '').replace(/\)[^\)]*$/, '');
    };

    /**
     * Parse the filter query contained in the given url filter string
     *
     * @param   filterString    {String}
     *
     * @returns                 {Array}   An object containing each row filter
     */
    var parseSelectionQuery = function(filterString) {
        var selections = [];
        $.each(stripBrackets(filterString).split('|'), function(i, row) {
            var tuple = {};
            $.each(stripBrackets(row).split('&'), function(i, keyValue) {
                var s = keyValue.split('=');
                tuple[s[0]] = decodeURIComponent(s[1]);
            });
            selections.push(tuple);
        });
        return selections;
    };

    /**
     * Handle the selection of an action table
     *
     * @param   table   {HTMLElement}   The table
     * @param   icinga  {Icinga}
     *
     * @constructor
     */
    var Selection = function(table, icinga) {
        this.$el = $(table);
        this.icinga = icinga;
        this.col = this.$el.closest('div.container').attr('id');

        if (this.hasMultiselection()) {
            if (! this.getMultiselectionKeys().length) {
                icinga.logger.error('multiselect table has no data-icinga-multiselect-data');
            }
            if (! this.getMultiselectionUrl()) {
                icinga.logger.error('multiselect table has no data-icinga-multiselect-url');
            }
        }
    };

    Selection.prototype = {

        /**
         * The container id in which this selection happens
         */
        col: null,

        /**
         * Return all rows as jQuery selector
         *
         * @returns         {jQuery}
         */
        rows: function() {
            return this.$el.find('tr');
        },

        /**
         * Return all row action links as jQuery selector
         *
         * @returns         {jQuery}
         */
        rowActions: function() {
            return this.$el.find('tr[href]');
        },

        /**
         * Return all selected rows as jQuery selector
         *
         * @returns         {jQuery}
         */
        selections: function() {
            return this.$el.find('tr.active');
        },

        /**
         * If this selection allows selecting multiple rows
         *
         * @returns         {Boolean}
         */
        hasMultiselection: function() {
            return this.$el.hasClass('multiselect');
        },

        /**
         * Return all filter keys that are significant when applying the selection
         *
         * @returns         {Array}
         */
        getMultiselectionKeys: function() {
            var data = this.$el.data('icinga-multiselect-data');
            return (data && data.split(',')) || [];
        },

        /**
         * Return the main target URL that is used when multi selecting rows
         *
         * This URL may differ from the url that is used when applying single rows
         *
         * @returns         {String}
         */
        getMultiselectionUrl: function() {
            return this.$el.data('icinga-multiselect-url');
        },

        /**
         * Check whether the given url is
         *
         * @param {String}  url
         */
        hasMultiselectionUrl: function(url) {
            var urls = this.$el.data('icinga-multiselect-url').split(' ');

            var related = this.$el.data('icinga-multiselect-controllers');
            if (related && related.length) {
                urls = urls.concat(this.$el.data('icinga-multiselect-controllers').split(' '));
            }

            var hasSelection = false;
            $.each(urls, function (i, object) {
                if (url.indexOf(object) === 0) {
                    hasSelection = true;
                }
            });
            return hasSelection;
        },

        /**
         * Read all filter data from the given row
         *
         * @param   row     {jQuery}    The row element
         *
         * @returns         {Object}    An object containing all filter data in this row as key-value pairs
         */
        getRowData: function(row) {
            var params = this.icinga.utils.parseUrl(row.attr('href')).params;
            var tuple = {};
            var keys = this.getMultiselectionKeys();
            for (var i = 0; i < keys.length; i++) {
                var key = keys[i];
                for (var j = 0; j < params.length; j++) {
                    if (params[j].key === key && (params[j].value || params[j].value === null)) {
                        tuple[key] = params[j].value ? decodeURIComponent(params[j].value): params[j].value;
                        break;
                    }
                }
            }
            return tuple;
        },

        /**
         * Deselect all selected rows
         */
        clear: function() {
            this.selections().removeClass('active');
        },

        /**
         * Add all rows that match the given filter to the selection
         *
         * @param filter {jQuery|Object}    Either an object containing filter variables or the actual row to select
         */
        select: function(filter) {
            if (filter instanceof jQuery) {
                filter.addClass('active');
                return;
            }
            var _this = this;
            this.rowActions()
                .filter(
                    function (i, el) {
                        return _this.icinga.utils.objectsEqual(_this.getRowData($(el)), filter);
                    }
                )
                .closest('tr')
                .addClass('active');
        },

        /**
         * Toggle the selection of the row between on and off
         *
         * @param   row     {jQuery}    The row to toggle
         */
        toggle: function(row) {
            row.toggleClass('active');
        },

        /**
         * Add a new selection range to the closest table, using the selected row as
         * range target.
         *
         * @param   row     {jQuery}    The target of the selected range.
         *
         * @returns         {boolean}   If the selection was changed.
         */
        range: function(row) {
            var from, to;
            var selected = row.first().get(0);
            this.rows().each(function(i, el) {
                if ($(el).hasClass('active') || el === selected) {
                    if (!from) {
                        from = el;
                    }
                    to = el;
                }
            });
            var inRange = false;
            this.rows().each(function(i, el) {
                if (el === from) {
                    inRange = true;
                }
                if (inRange) {
                    $(el).addClass('active');
                }
                if (el === to) {
                    inRange = false;
                }
            });
            return false;
        },

        /**
         * Select rows that target the given url
         *
         * @param   url     {String}    The target url
         */
        selectUrl: function(url) {
            var formerHref = this.$el.closest('.container').data('icinga-actiontable-former-href')

            var $row = this.rows().filter('[href="' + url + '"]');

            if ($row.length) {
               this.clear();
               $row.addClass('active');
            } else {
                if (this.col !== 'col2') {
                    // rows sometimes need to be displayed as active when related actions
                    // like command actions are being opened. Do not do this for col2, as it
                    // would always select the opened URL itself.
                    var $row = this.rows().filter('[href$="' + icinga.utils.parseUrl(url).query + '"]');
                    if ($row.length) {
                        this.clear();
                        $row.addClass('active');
                    } else {
                        var $row = this.rows().filter('[href$="' + formerHref + '"]');
                        if ($row.length) {
                            this.clear();
                            $row.addClass('active');
                        } else {
                            var tbl = this.$el;
                            if (ActionTable.prototype.tables(
                                tbl.closest('.dashboard').find('.container')).not(tbl).find('tr.active').length
                            ) {
                                this.clear();
                            }
                        }
                    }
                }
            }
        },

        /**
         * Convert all currently selected rows into an url query string
         *
         * @returns         {String}    The filter string
         */
        toQuery: function() {
            var _this = this;
            var selections = this.selections();
            var queries = [];
            var utils = this.icinga.utils;
            if (selections.length === 1) {
                return $(selections[0]).attr('href');
            } else if (selections.length > 1 && _this.hasMultiselection()) {
                selections.each(function (i, el) {
                    var parts = [];
                    $.each(_this.getRowData($(el)), function(key, value) {
                        var condition = utils.fixedEncodeURIComponent(key);
                        if (value !== null) {
                            condition += '=' + utils.fixedEncodeURIComponent(value);
                        }

                        parts.push(condition);
                    });
                    queries.push('(' + parts.join('&') + ')');
                });
                return _this.getMultiselectionUrl() + '?(' + queries.join('|') + ')';
            } else {
                return '';
            }
        },

        /**
         * Refresh the displayed active columns using the current page location
         */
        refresh: function() {
            var hash = icinga.history.getCol2State().replace(/^#!/, '');
            if (this.hasMultiselection()) {
                var query = parseSelectionQuery(hash);
                if (query.length > 1 && this.hasMultiselectionUrl(this.icinga.utils.parseUrl(hash).path)) {
                    this.clear();
                    // select all rows with matching filters
                    var _this = this;
                    $.each(query, function(i, selection) {
                        _this.select(selection);
                    });
                }
                if (query.length > 1) {
                    return;
                }
            }
            this.selectUrl(hash);
        }
    };

    Icinga.Behaviors = Icinga.Behaviors || {};

    var ActionTable = function (icinga) {
        Icinga.EventListener.call(this, icinga);

        /**
         * If currently loading
         *
         * @var Boolean
         */
        this.loading = false;

        this.on('rendered', '#main .container', this.onRendered, this);
        this.on('beforerender', '#main .container', this.beforeRender, this);
        this.on('click', 'table.action tr[href], table.table-row-selectable tr[href]', this.onRowClicked, this);
    };
    ActionTable.prototype = new Icinga.EventListener();

    /**
     * Return all active tables in this table, or in the context as jQuery selector
     *
     * @param   context   {HTMLElement}
     * @returns           {jQuery}
     */
    ActionTable.prototype.tables = function(context) {
        if (context) {
            return $(context).find('table.action, table.table-row-selectable');
        }
        return $('table.action, table.table-row-selectable');
    };

    /**
     * Handle clicks on table rows and update selection and history
     */
    ActionTable.prototype.onRowClicked = function (event) {
        var _this = event.data.self;
        var $target = $(event.target);
        var $tr = $(event.currentTarget);
        var table = new Selection($tr.closest('table.action, table.table-row-selectable')[0], _this.icinga);

        if ($tr.closest('[data-no-icinga-ajax]').length > 0) {
            return true;
        }

        // some rows may contain form actions that trigger a different action, pass those through
        if (!$target.hasClass('rowaction') && $target.closest('form').length &&
            ($target.closest('a').length ||                                         // allow regular link clinks
             $target.closest('button').length ||                                    // allow submitting forms
             $target.closest('input').length || $target.closest('label').length)) { // allow selecting form elements
            return;
        }

        event.stopPropagation();
        event.preventDefault();

        // update selection
        if (table.hasMultiselection()) {
            if (event.ctrlKey || event.metaKey) {
                // add to selection
                table.toggle($tr);
            } else if (event.shiftKey) {
                // range selection
                table.range($tr);
            } else {
                // single selection
                table.clear();
                table.select($tr);
            }
        } else {
            table.clear();
            table.select($tr);
        }

        var count = table.selections().length;
        if (count > 0) {
            var query = table.toQuery();
            _this.icinga.loader.loadUrl(query, _this.icinga.loader.getLinkTargetFor($tr));
        } else {
            if (_this.icinga.loader.getLinkTargetFor($tr).attr('id') === 'col2') {
                _this.icinga.ui.layout1col();
                _this.icinga.history.pushCurrentState();
            }
        }

        // redraw all table selections
        _this.tables().each(function () {
            new Selection(this, _this.icinga).refresh();
        });

        // update selection info
        $('.selection-info-count').text(count);
        return false;
    };

    /**
     * Render the selection and prepare selection rows
     */
    ActionTable.prototype.onRendered = function(evt) {
        var container = evt.target;
        var _this = evt.data.self;

        if (evt.currentTarget !== container) {
            // Nested containers are not processed multiple times
            return;
        }

        // initialize all rows with the correct row action
        $('table.action tr, table.table-row-selectable tr', container).each(function(idx, el) {

            // decide which row action to use: links declared with the class rowaction take
            // the highest precedence before hrefs defined in the tr itself and regular links
            var $a = $('a[href].rowaction', el).first();
            if ($a.length) {
                $(el).attr('href', $a.attr('href'));
                return;
            }
            if ($(el).attr('href') && $(el).attr('href').length) {
                return;
            }
            $a = $('a[href]', el).first();
            if ($a.length) {
                $(el).attr('href', $a.attr('href'));
            }
        });

        // draw all active selections that have disappeared on reload
        _this.tables().each(function(i, el) {
            new Selection(el, _this.icinga).refresh();
        });

        // update displayed selection counter
        var table = new Selection(_this.tables(container).first());
        $(container).find('.selection-info-count').text(table.selections().length);
    };

    ActionTable.prototype.beforeRender = function(evt) {
        var container = evt.target;
        var _this = evt.data.self;

        if (evt.currentTarget !== container) {
            // Nested containers are not processed multiple times
            return;
        }

        var active = _this.tables().find('tr.active');
        if (active.length) {
            $(container).data('icinga-actiontable-former-href', active.attr('href'));
        }
    };

    ActionTable.prototype.clearAll = function () {
        var _this = this;
        this.tables().each(function () {
            new Selection(this, _this.icinga).clear();
        });
        $('.selection-info-count').text('0');
    };

    Icinga.Behaviors.ActionTable = ActionTable;

}) (Icinga, jQuery);
