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

/**
 * ## Multi Select Widget
 *
 * This module serves as a wrapper of SumoSelect, a jQuery plugin that provides enhanced select-box functionality.
 * Bind this widget to a parent container and mark each child select-box element with the `data-multi_select-instance`
 * attribute.
 *
 * After the initialization of the widget all the marked elements will be converted into SumoSelect instances.
 *
 * ### Options
 *
 * **Options Source | `data-multi_select-source` | String | Optional**
 *
 * Provide a URL that will be used to fetch the options of the select box. The widget will perform a GET request to
 * the provided destination and expects a JSON array with the options:
 *
 * [
 *   {
 *     "value": "1", 
 *     "text": "Option #1"
 *   },
 *   {
 *     "value": "2", 
 *     "text": "Option #2"
 *   }
 * ]
 *
 * You can also pass other configuration directly in the parent element which will be used for every child instance.
 *
 *
 * ### Methods
 *
 * **Reload Options [AJAX]**
 *
 * You can use this method to refresh the options from the already provided data-multi_select-source or by providing
 * a new URL which will also be set as the data-source of the element. If the multi select has no URL then it will just
 * sync its values with the select element.
 *
 * * ```js
 * $('#my-multi-select').multi_select('reload', 'http://shop.de/options/source/url');
 * ```
 *
 * **Refresh Options**
 *
 * Update the multi-select widget with the state of the original select element. This method is useful after performing
 * changes in the original element and need to display them in the multi-select widget.
 *
 * ```js
 * $('#my-multi-select').multi_select('refresh');
 * ```
 *
 * ### Events
 * ```javascript
 * // Triggered when the multi-select widget has performed a "reload" method (after the AJAX call).
 * $('#my-multi-select').on('reload', function(event) {});
 * ```
 *
 * ### Example
 *
 * ```html
 * <form data-gx-widget="multi_select">
 *   <select data-multi_select-instance data-multi_select-source="http://shop.de/options-source-url"></select>
 * </form>
 * ```
 *
 * {@link http://hemantnegi.github.io/jquery.sumoselect}
 *
 * @module Admin/Widgets/multi_select
 * @requires jQuery-SumoSelect
 */
gx.widgets.module(
    'multi_select',

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

    function (data) {

        'use strict';

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

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

        /**
         * Default Options
         *
         * @type {Object}
         */
        const defaults = {
            placeholder: jse.core.lang.translate('SELECT', 'general'),
            selectAll: true,
            csvDispCount: 2,
            captionFormat: `{0} ${jse.core.lang.translate('selected', 'admin_labels')}`,
            locale: [
                'OK',
                jse.core.lang.translate('CANCEL', 'general'),
                jse.core.lang.translate('SELECT_ALL', 'general')
            ]
        };

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

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

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

        /**
         * Add the "multi_select" method to the jQuery prototype.
         */
        function _addPublicMethod() {
            if ($.fn.multi_select) {
                return;
            }

            $.fn.extend({
                multi_select: function (action, ...args) {
                    if (!$(this).is('select')) {
                        throw new Error('Called the "multi_select" method on an invalid object (select box expected).');
                    }

                    $.each(this, function () {
                        switch (action) {
                            case 'reload':
                                _reload($(this), ...args);
                                break;

                            case 'refresh':
                                _refresh(this);
                        }
                    });
                }
            });
        }

        /**
         * Fill a select box with the provided options.
         *
         * @param {jQuery} $select The select box to be filled.
         * @param {Object} options Array with { value: "...", "text": "..." } entries.
         */
        function _fillSelect($select, options) {
            $select.empty();
            $.each(options, (index, option) => {
                $select.append(new Option(option.text, option.value));
            });
        }

        /**
         * Reload the options from the source (data property) or the provided URL,
         *
         * @param {string} url Optional, if provided it will be used as the source of the data and will also update the
         * data-source property of the element.
         */
        function _reload($select, url) {
            url = url || $select.data('source');

            if (!url) {
                throw new Error('Multi Select Reload: Neither URL nor data-source contain a URL value.');
            }

            $select.data('source', url);

            $.getJSON(url)
                .done(function (response) {
                    _fillSelect($select, response);
                    $select[0].sumo.reload();
                    $select.trigger('reload');
                })
                .fail(function (jqxhr, textStatus, errorThrown) {
                    jse.core.debug.error('Multi Select AJAX Error: ', jqxhr, textStatus, errorThrown);
                });
        }

        /**
         * Refresh the multi select instance depending the state of the original select element.
         *
         * @param {Node} select The DOM element to be refreshed.
         */
        function _refresh(select) {
            if (select.sumo === undefined) {
                throw new Error('Multi Select Refresh: The provided select element is not an instance of SumoSelect.', select);
            }

            select.sumo.reload();

            // Update the caption by simulating a click in an ".opt" element.
            _overrideSelectAllCaption.apply($(select.parentNode).find('.opt')[0]);
        }
    
        /**
         * select all elements in a given select
         * @param optWrapper select wrapper
         * @private
         */
        function _selectAllElements(optWrapper) {
            optWrapper.find('.opt').addClass('selected');
            optWrapper
                .siblings('.CaptionCont')
                .children('span')
                .removeClass('placeholder')
                .text(jse.core.lang.translate('all_selected', 'admin_labels'));
        }
    
        /**
         * deselect all elements in a given select
         * @param optWrapper select wrapper
         * @private
         */
        function _deselectAllElements(optWrapper) {
            optWrapper.find('.opt').removeClass('selected');
            optWrapper.find('.select-all').removeClass('selected').removeClass('partial-select');
            optWrapper
                .siblings('.CaptionCont')
                .children('span')
                .text('');
        }
    
        /**
         * Override the multi select caption when all elements are selected.
         *
         * This callback will override the caption because SumoSelect does not provide a setting for this text.
         */
        function _overrideSelectAllCaption() {
            const $optWrapper = $(this).parents('.optWrapper');
            const allCheckboxesChecked = $optWrapper.find('.opt.selected').length === $optWrapper.find('.opt').length;
            const atLeastOneCheckboxChecked = $optWrapper.find('.opt.selected').length > 0;
            const $selectAllCheckbox = $optWrapper.find('.select-all');
            const isSelectAllSelected = $selectAllCheckbox.hasClass('selected')
                && !($selectAllCheckbox.hasClass('partial') || $selectAllCheckbox.hasClass('partial-select'));
            const isDeselectAllSelected = allCheckboxesChecked && !isSelectAllSelected && !$selectAllCheckbox.hasClass('selected');
        
            $selectAllCheckbox.removeClass('partial-select');
        
            if (isDeselectAllSelected) {
                _deselectAllElements($optWrapper);
            } else if (allCheckboxesChecked) {
                _selectAllElements($optWrapper);
            } else if (atLeastOneCheckboxChecked) {
                $selectAllCheckbox.addClass('partial-select');
            } else if (isSelectAllSelected) {
                _selectAllElements($optWrapper);
            }
        }

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

        module.init = function (done) {
            // Add public module method.
            _addPublicMethod();

            // Initialize the elements.
            $this.find('[data-multi_select-instance]').each(function () {
                const $select = $(this);

                $select.removeAttr('data-multi_select-instance');

                // Instantiate the widget without an AJAX request.
                $select.SumoSelect(options);

                if ($select.data('multi_selectSource') !== undefined) {
                    // Remove the data attribute and store the value internally with the 'source' key.
                    $select.data('source', $select.data('multi_selectSource'));
                    $select.removeAttr('data-multi_select-source');

                    // Fetch the options with an AJAX request.
                    $.getJSON($select.data('multi_selectSource'))
                        .done(function (response) {
                            _fillSelect($select, response);
                            $select[0].sumo.unload();
                            $select.SumoSelect(options);
                            $select.trigger('reload');
                        })
                        .fail(function (jqxhr, textStatus, errorThrown) {
                            jse.core.debug.error('Multi Select AJAX Error: ', jqxhr, textStatus, errorThrown);
                        });
                }
            });

            done();
        };

        // When the user clicks on the "Select All" option update the text with a custom translations. This has to
        // be done manually because there is no option for this text in SumoSelect.
        $this.on('click', '.select-all, .opt', _overrideSelectAllCaption);

        return module;

    });