/* --------------------------------------------------------------
 datatable_responsive_columns.js 2017-03-08
 Gambio GmbH
 http://www.gambio.de
 Copyright (c) 2017 Gambio GmbH
 Released under the GNU General Public License (Version 2)
 [http://www.gnu.org/licenses/gpl-2.0.html]
 --------------------------------------------------------------
 */

/**
 * ## Enable DataTable Responsive Columns
 *
 * This module will enable the responsive columns functionality which will resize the columns until a minimum
 * width is reach. Afterwards the columns will be hidden and the content will be displayed by through an icon
 * tooltip.
 *
 * ### Options
 *
 * **Initial Visibility Toggle Selector | `data-data_relative_columns-visibility-toggle-selector` | String | Optional**
 *
 * Provide a selector relative to each thead > tr element in order to hide the column on page load and then show it
 * again once the responsive widths have been calculated. The provided selector must point to the biggest column in
 * order to avoid broken displays till the table becomes responsive.
 *
 * @module Admin/Extensions/data_relative_columns
 */
gx.extensions.module(
    'datatable_responsive_columns',

    [
        `${jse.source}/vendor/qtip2/jquery.qtip.min.css`,
        `${jse.source}/vendor/qtip2/jquery.qtip.min.js`,
    ],

    function (data) {

        'use strict';

        // ------------------------------------------------------------------------
        // VARIABLES
        // ------------------------------------------------------------------------

        /**
         * Module Selector
         *
         * @type {jQuery}
         */
        const $this = $(this);

        /**
         * Default Options
         *
         * @type {Object}
         */
        const defaults = {
            visibilityToggleSelector: '[data-column-name="actions"]'
        };

        /**
         * Final Options
         *
         * @type {Object}
         */
        const options = $.extend(true, {}, defaults, data);

        /**
         * Module Instance
         *
         * @type {Object}
         */
        const module = {};

        /**
         * DataTable Initialization Columns
         *
         * @type {Array}
         */
        let columnDefinitions;

        /**
         * Width Factor Sum
         *
         * @type {Number}
         */
        let widthFactorSum;

        // ------------------------------------------------------------------------
        // FUNCTIONS
        // ------------------------------------------------------------------------

        /**
         * Update empty table "colspan" attribute.
         *
         * This method will keep the empty table row width in sync with the table width.
         */
        function _updateEmptyTableColSpan() {
            if ($this.find('.dataTables_empty').length > 0) {
                const colspan = ($this.find('thead:first tr:first .actions').index() + 1)
                    - $this.find('thead:first tr:first th.hidden').length;
                $this.find('.dataTables_empty').attr('colspan', colspan);
            }
        }

        /**
         * Add hidden columns content icon to actions cell of a single row.
         *
         * Call this method only if you are sure there is no icon previously set (runs faster).
         *
         * @param {jQuery} $tr
         */
        function _addHiddenColumnsContentIcon($tr) {
            $tr.find('td.actions div:first')
                .prepend(`<i class="fa fa-ellipsis-h meta-icon hidden-columns-content"></i>`);
        }

        /**
         * Get the cell content.
         *
         * This method will also search for child input and select elements and return the appropriate content.
         *
         * @param {jQuery} $td Table cell to be examined.
         *
         * @return {String}
         */
        function _getCellContent($td) {
            if ($td.find('select').length) {
                return $td.find('select option:selected').text();
            } else if ($td.find('input:text').length) {
                return $td.find('input:text').val();
            } else if ($td.find('input:checkbox').length) {
                return $td.find('input:checkbox').prop('checked') ? '✔' : '✘';
            } else {
                return $td.text();
            }
        }

        /**
         * Generates and sets the tooltip content for the hidden columns content.
         *
         * @param {jQuery} $tr The current row selector.
         */
        function _generateHiddenColumnsContent($tr) {
            let hiddenColumnContentHtml = '';

            $tr.find('td.hidden').each((index, td) => {
                hiddenColumnContentHtml += $this.find(`thead:first tr:first th:eq(${$(td).index()})`).text()
                    + ': ' + _getCellContent($(td)) + '<br/>';
            });

            $tr.find('.hidden-columns-content').qtip({
                content: hiddenColumnContentHtml,
                style: {
                    classes: 'gx-qtip info'
                },
                hide: {
                    fixed: true,
                    delay: 300
                }
            });
        }

        /**
         * Hide DataTable Columns
         *
         * This method is part of the responsive tables solution.
         *
         * @param {jQuery} $targetWrapper Target datatable instance wrapper div.
         * @param {jQuery} $firstHiddenColumn The first hidden column (first column with the .hidden class).
         */
        function _hideColumns($targetWrapper, $firstHiddenColumn) {
            const $lastVisibleColumn = ($firstHiddenColumn.length !== 0)
                ? $firstHiddenColumn.prev()
                : $this.find('thead:first th.actions').prev();

            if ($lastVisibleColumn.hasClass('hidden') || $lastVisibleColumn.index() === 0) {
                return; // First column or already hidden, do not continue.
            }

            // Show hidden column content icon.
            if ($this.find('.hidden-columns-content').length === 0) {
                $this.find('tbody tr').each((index, tr) => {
                    _addHiddenColumnsContentIcon($(tr));
                });
            }

            // Hide the last visible column.
            $this.find('tr').each((index, tr) => {
                $(tr)
                    .find(`th:eq(${$lastVisibleColumn.index()}), td:eq(${$lastVisibleColumn.index()})`)
                    .addClass('hidden');

                // Generate the hidden columns content.
                _generateHiddenColumnsContent($(tr));
            });

            _updateEmptyTableColSpan();

            // If there are still columns which don't fit within the viewport, hide them.
            if ($targetWrapper.width() < $this.width() && $lastVisibleColumn.index() > 1) {
                _toggleColumnsVisibility();
            }
        }

        /**
         * Show DataTable Columns
         *
         * This method is part of the responsive tables solution.
         *
         * @param {jQuery} $targetWrapper Target datatable instance wrapper div.
         * @param {jQuery} $firstHiddenColumn The first hidden column (first column with the .hidden class).
         */
        function _showColumns($targetWrapper, $firstHiddenColumn) {
            if ($firstHiddenColumn.length === 0) {
                return;
            }

            const firstHiddenColumnWidth = parseInt($firstHiddenColumn.css('min-width'));
            let tableMinWidth = 0;

            // Calculate the table min width by each column min width.
            $this.find('thead:first tr:first th').each((index, th) => {
                if (!$(th).hasClass('hidden')) {
                    tableMinWidth += parseInt($(th).css('min-width'));
                }
            });

            // Show the first hidden column.
            if (tableMinWidth + firstHiddenColumnWidth <= $targetWrapper.outerWidth()) {
                $this.find('tr').each((index, tr) => {
                    $(tr)
                        .find(`th:eq(${$firstHiddenColumn.index()}), td:eq(${$firstHiddenColumn.index()})`)
                        .removeClass('hidden');

                    _generateHiddenColumnsContent($(tr));
                });

                _updateEmptyTableColSpan();

                // Hide hidden column content icon.
                if ($this.find('thead:first tr:first th.hidden').length === 0) {
                    $this.find('.hidden-columns-content').remove();
                }

                // If there are still columns which would fit fit within the viewport, show them.
                const newTableMinWidth = tableMinWidth + firstHiddenColumnWidth
                    + parseInt($firstHiddenColumn.next('.hidden').css('min-width'));

                if (newTableMinWidth <= $targetWrapper.outerWidth() && $firstHiddenColumn.next('.hidden').length
                    !== 0) {
                    _toggleColumnsVisibility();
                }
            }
        }

        /**
         * Toggle column visibility depending the window size.
         */
        function _toggleColumnsVisibility() {
            const $targetWrapper = $this.parent();
            const $firstHiddenColumn = $this.find('thead:first th.hidden:first');

            if ($targetWrapper.width() < $this.width()) {
                _hideColumns($targetWrapper, $firstHiddenColumn);
            } else {
                _showColumns($targetWrapper, $firstHiddenColumn);
            }
        }

        /**
         * Calculate and set the relative column widths.
         *
         * The relative width calculation works with a width-factor system where each column preserves a
         * specific amount of the table width.
         *
         * This factor is not defining a percentage, rather only a width-volume. Percentage widths will not
         * work correctly when the table has fewer columns than the original settings.
         */
        function _applyRelativeColumnWidths() {
            $this.find('thead:first tr:first th').each(function () {
                if ($(this).css('display') === 'none') {
                    return true;
                }

                let currentColumnDefinition;

                columnDefinitions.forEach((columnDefinition) => {
                    if (columnDefinition.name === $(this).data('columnName')) {
                        currentColumnDefinition = columnDefinition;
                    }
                });

                if (currentColumnDefinition && currentColumnDefinition.widthFactor) {
                    const index = $(this).index();
                    const width = Math.round(currentColumnDefinition.widthFactor / widthFactorSum * 100 * 100) / 100;
                    $this.find('thead').each((i, thead) => {
                        $(thead).find('tr').each((i, tr) => {
                            $(tr).find('th').eq(index).css('width', width + '%');
                        });
                    });
                }
            });
        }

        /**
         * Applies the column width if the current column width is smaller.
         */
        function _applyMinimumColumnWidths() {
            $this.find('thead:first tr:first th').each(function (index) {
                if ($(this).css('display') === 'none') {
                    return true;
                }

                let currentColumnDefinition;

                columnDefinitions.forEach((columnDefinition) => {
                    if (columnDefinition.name === $(this).data('columnName')) {
                        currentColumnDefinition = columnDefinition;
                    }
                });

                if (!currentColumnDefinition) {
                    return true;
                }

                const currentWidth = $(this).outerWidth();
                const definitionMinWidth = parseInt(currentColumnDefinition.minWidth);

                if (currentWidth < definitionMinWidth) {
                    // Force the correct column min-widths for all thead columns.
                    $this.find('thead').each((i, thead) => {
                        $(thead).find('tr').each((i, tr) => {
                            $(tr).find('th').eq(index)
                                .outerWidth(definitionMinWidth)
                                .css('max-width', definitionMinWidth)
                                .css('min-width', definitionMinWidth);
                        });
                    });
                }
            });
        }

        /**
         * On DataTable Draw Event
         */
        function _onDataTableDraw() {
            // Wait until the contents of the table are rendered. DataTables will sometimes fire the draw event
            // even before the td elements are rendered in the browser.
            var interval = setInterval(function () {
                if ($this.find('tbody tr:last td.actions').length === 1) {
                    _applyRelativeColumnWidths();
                    _applyMinimumColumnWidths();
                    _toggleColumnsVisibility();

                    // Hide the tbody cells depending on whether the respective <th> element is hidden.
                    $this.find('thead:first tr:first th').each(function (index, th) {
                        if ($(th).hasClass('hidden')) {
                            $this.find('tbody tr').each(function (i, tr) {
                                $(tr).find('td:eq(' + index + ')').addClass('hidden');
                            });
                        }
                    });

                    // Add the hidden columns icon if needed.
                    if ($this.find('thead th.hidden').length) {
                        $this.find('tbody tr').each(function (index, tr) {
                            if ($(tr).find('.hidden-columns-content').length) {
                                return true;
                            }
                            _addHiddenColumnsContentIcon($(tr));
                            _generateHiddenColumnsContent($(tr));
                        });
                    }

                    clearInterval(interval);
                }
            }, 500);
        }

        /**
         * On Window Resize Event
         */
        function _onWindowResize() {
            $this.find('thead.fixed').outerWidth($this.outerWidth());
            _applyRelativeColumnWidths();
            _applyMinimumColumnWidths();
            _toggleColumnsVisibility();
        }

        /**
         * On DataTable Initialize Event
         */
        function _onDataTableInit() {
            $this.find(options.visibilityToggleSelector).show();
            _updateEmptyTableColSpan();

            columnDefinitions = $this.DataTable().init().columns;
            widthFactorSum = 0;

            columnDefinitions.forEach((columnDefinition) => {
                widthFactorSum += columnDefinition.widthFactor || 0;
            });

            $this.on('draw.dt', _onDataTableDraw);
            $(window).on('resize', _onWindowResize);

            _onWindowResize();
        }

        // ------------------------------------------------------------------------
        // INITIALIZATION
        // ------------------------------------------------------------------------

        module.init = function (done) {
            $this.on('init.dt', _onDataTableInit);

            $(window).on('JSENGINE_INIT_FINISHED', () => {
                if ($this.DataTable().ajax.json() !== undefined) {
                    _onDataTableInit();
                }
            });

            $this.find(options.visibilityToggleSelector).hide();

            done();
        };

        return module;

    });