// (C) Copyright 2020 Hewlett-Packard Enterprise Company, L.P.

define(['hp/core/Localizer',
    'hp/core/Style',
    'text!hpPages/core/hp_select_sort_directions.html',
    'jquery',
    'hp/lib/jquery.inArrayCaseInsensitive'],
function(localizer, style, sortDirectionsHtml) {
"use strict";

    (function($) {
        // jQuery plugin definition

        /**
         * You can describe the control completely in HTML or
         * by passing in configuration options here, for example:
         * {label: 'Label',
         *   selected: 'Value 1',
         *   options:['Value 1', 'Value 2'],
         *   multiSelect: false,
         *   sorting: true}
         *
         * options can either be a array of simple strings or an array of objects
         *         with additional annotations:
         *   options: [{id: 1, name: 'Value 1', help: 'help'},
         *      {id: 2, name: 'Value 2', error: 'error'}]
         *
         * You can also use this with a <select> element, in which case you can
         * populate the options ahead of time or lazily when the user clicks the
         * control. In that case, use hpSelect('reset').
         *
         * You can extend individual choices by adding an hp-select-extended class
         * to a particular option. This will prevent hpSelect from closing
         * the drop down when that options is clicked, so that it's value
         * can be customized by additional controls. This is used for things
         * like the custom date range extension that provides controls
         * for choosing a range of dates.
         */
        $.fn.hpSelect = function(action, args) {

            var BODY = 'body';
            var LABEL = '> label';
            var VALUE = '> .hp-value';
            var OPTIONS = '> .hp-options';
            var OPTION = OPTIONS + ' li';
            var SELECT_OPTION = OPTIONS +
                ' li:not([data-sort-direction]):not(.hp-sub-options)';
            var SORT_DIRECTION_OPTION = OPTIONS + ' li[data-sort-direction]';
            var ALL_OPTION = OPTION + '[data-id=all], ' + OPTION + '[data-all=true]';
            var ACTIVE = 'hp-active';
            var SELECTED = 'hp-selected';
            var DISABLED = 'hp-disabled';
            var VALUE_SEPARATOR = ', ';
            var SPACE = 32;
            var TAB = 9;
            var ESCAPE = 27;
            var ENTER = 13;
            var UP_ARROW = 38;
            var DOWN_ARROW = 40;

            function hpSelect(elem) {

                var wrapper;
                var control;
                var multiSelect = false;
                var sorting = false;

                // extract the id for an option, what we send in a change event
                function optionId(option) {
                    if ($(option).attr('data-id')) {
                        // option has a 'data-id' attribute
                        return $(option).attr('data-id');
                    } else if ($('> a', option).length > 0) {
                        // special case anchor options
                        // get the text of the anchor
                        return $.trim($('> a', option).text());
                    } else {
                        return $.trim(option.text());
                    }
                }

                // extract the visible value for an option, what we show the user
                function optionValue(option) {
                    // special case anchor and rich options
                    var child = $('> a, > .hp-name', option);
                    if (child.length > 0) {
                        return $.trim(child.text());
                    } else {
                        var alt = option.attr('data-alt-localize');
                        return $.trim(alt ? localizer.getString(alt) : option.text());
                    }
                }
                
                function idsForValues(values) {
                    var options = $(SELECT_OPTION, control);
                    var result = [];
                    
                    options.each(function (index, option) {
                        var optValue = optionValue($(option));
                        if ($.inArray(optValue, values) !== -1) {
                            result.push(optionId($(option)));
                        }
                    });
                    return result;
                }
                
                function setFromSelection() {
                    var oldValue = $(VALUE, control).text();
                    var oldId = $(VALUE, control).attr('data-id');
                    var oldDirection = null;
                    var values = [];
                    var ids = [];
                    var idString;
                    var value;
                    var direction = null;

                    $(SELECT_OPTION + '.' + SELECTED, control).each(
                        function (index, option) {
                            values.push(optionValue($(option)));
                            ids.push(optionId($(option)));
                        });
                    value = values.join(VALUE_SEPARATOR);
                    idString = ids.join(VALUE_SEPARATOR);
                    
                    if (value.length === 0) {
                        // no option selected, use whatever the value has
                        value = $(VALUE, control).text();
                        idString = $(VALUE, control).attr('data-id');
                        if (! idString || idString.length === 0) {
                            idString = value;
                        }
                    }

                    if (sorting) {
                        $(SORT_DIRECTION_OPTION, control).each(function (index, option) {
                            var optionDirection = $(option).data('sort-direction');
                            if (control.hasClass('hp-sort-' + optionDirection)) {
                                oldDirection = optionDirection;
                                control.removeClass('hp-sort-' + optionDirection);
                                return false;
                            }
                        });
                        direction = $(SORT_DIRECTION_OPTION + '.' + SELECTED, control).
                            data('sort-direction');
                        $(control).addClass('hp-sort-' + direction);
                    }

                    if (value !== oldValue || direction !== oldDirection ||
                        (! oldId && idString) || idString !== oldId) {
                        if (value && idString) {
                            $(VALUE, control).text(value).attr('data-id', idString);
                        }
                        if (sorting) {
                            control.trigger('change', {id : ids[0], direction: direction});
                        } else {
                            if (wrapper) {
                                $('select', wrapper).val(ids.length === 1 ? ids[0] : ids).change();
                            } else {
                                control.trigger('change',
                                    (ids.length === 1 ? [ids[0]] : [ids]));
                            }
                        }
                    }
                }

                function initialActive() {
                    if ($(OPTION + '.' + ACTIVE, control).length === 0) {
                        $(OPTION + '.' + SELECTED, control).first().
                            addClass(ACTIVE);
                    }
                }

                function nextActive() {
                    var options = $(OPTION, control).not('.hp-sub-options');
                    initialActive();
                    options.each(function (index, elem) {
                        if ($(elem).hasClass(ACTIVE) &&
                            options.length > (index + 1)) {
                            $(elem).removeClass(ACTIVE);
                            $(options[index+1]).addClass(ACTIVE);
                            return false;
                        }
                    });
                }

                function previousActive() {
                    var options = $(OPTION, control).not('.hp-sub-options');
                    initialActive();
                    options.each(function (index, elem) {
                        if ($(elem).hasClass(ACTIVE) && index > 0) {
                            $(elem).removeClass(ACTIVE);
                            $(options[index-1]).addClass(ACTIVE);
                            return false;
                        }
                    });
                }

                function onMouseclick(ev) {
                    var option = $(ev.target).closest('li');
                    var isIgnore = option.hasClass('hp-select-ignore');
                    
                    if (!isIgnore) {
                        if (sorting && option.data('sort-direction')) {
                            $(SORT_DIRECTION_OPTION, control).removeClass(SELECTED);
                            option.addClass(SELECTED);
                        } else if (multiSelect) {
                            if (option.attr('data-id') === 'all' || option.attr('data-all')) {
                                // user clicked 'all' item, clear everything else
                                $(OPTION, control).not('.hp-sub-options').removeClass(SELECTED);
                                option.addClass(SELECTED);
                            } else {
                                option.toggleClass(SELECTED);
                                if (option.hasClass(SELECTED)) {
                                    // clear 'all' item, if any
                                    $(ALL_OPTION, control).removeClass(SELECTED);
                                } else {
                                    // if nothing is selected, select the 'all' item, if any
                                    if ($(OPTION + '.' + SELECTED, control).length === 0) {
                                        $(ALL_OPTION, control).addClass(SELECTED);
                                    }
                                }
                            }
                        } else {
                            $(SELECT_OPTION, control).removeClass(SELECTED);
                            option.addClass(SELECTED);
                        }
                    }
                    setFromSelection();
                }

                function onMousemove() {
                    $(OPTION, control).removeClass(ACTIVE);
                }

                function onKeypress(ev) {
                    var keyCode = (ev.which ? ev.which : ev.keyCode);
                    if (keyCode == ENTER) {
                        if (sorting) {
                            var isSortingOption = $(SORT_DIRECTION_OPTION + '.' + ACTIVE, control);
                            if (isSortingOption.length > 0) {
                                $(SORT_DIRECTION_OPTION, control).removeClass(SELECTED);
                                $(SORT_DIRECTION_OPTION + '.' + ACTIVE, control).removeClass(ACTIVE).addClass(SELECTED);     
                            } else { 
                                $(SELECT_OPTION, control).removeClass(SELECTED);
                                $(SELECT_OPTION + '.' + ACTIVE, control).removeClass(ACTIVE).addClass(SELECTED);                             
                            }
                        } else {
                            $(SELECT_OPTION, control).removeClass(SELECTED);
                            $(SELECT_OPTION + '.' + ACTIVE, control).removeClass(ACTIVE).addClass(SELECTED);
                        }
                        setFromSelection();

                        // follow anchor, if any
                        if (! multiSelect && ! sorting) {
                            var anchor = $(OPTION + '.' + SELECTED + ' a',
                                control);
                            if (anchor.length > 0) {
                                anchor.trigger('click');
                            }
                        }

                        deactivate();
                    } else if (keyCode == ESCAPE || keyCode == TAB) {
                        deactivate();
                    } else if (keyCode == DOWN_ARROW) {
                        nextActive();
                        ev.preventDefault();
                    } else if (keyCode == UP_ARROW) {
                        previousActive();
                        ev.preventDefault();
                    }
                }

                function deactivate(ev) {
                    var proceed = true;
                    
                    if (ev && multiSelect) {
                        // for multi-select, don't deactivate if we click within
                        // the control
                        proceed =
                            ($(ev.target).parents('.hp-options').
                                filter(function (index) {
                                    return ($(this).parent()[0] === control[0]);
                                }).length === 0);
                    }
                    
                    if (proceed) {
                        $(BODY).off('click.hpSelect', onBodyClick);
                        if (!multiSelect) {
                            $(document).off('keydown.hpSelect', onKeypress);
                            $(control).off('mousemove.hpSelect', onMousemove);
                        }
                        $(OPTIONS, control).
                            css({width: '', height: '', overflow: '',
                                'padding-top': '', left: '', right: '', 'max-height': ''});
                        $(LABEL, control).css('width', '');
                        $(VALUE, control).css('width', '');
                        control.css('width', '');
                        if (! control.hasClass('hp-pinned')) {
                            control.removeClass(ACTIVE);
                        }
                        $(OPTION, control).removeClass(ACTIVE);
                    }
                }
                
                function onBodyClick(ev) {
                    // ignore clicks inside extended options or from elements that have
                    // been removed, but are still referenced by the event.target.
                    // if the element does not have 'body' as an ancestor, then it has been removed.
                	// Note: When the hpSelectDateRange is used in an extended select, the jquery-ui 
                	// datepicker removes and recreates the month calendar elements in
                	// the DOM before this event is handled, which still points to the old month. 
                    if ( ($(ev.target).closest('.hp-select-extended').length === 0) && 
                            ($(ev.target).closest('body').length !== 0) ) {
                       	deactivate(ev);
                    }
                }

                function activate() {
                    var options = $(OPTIONS, control);
                    var value = $(VALUE, control);
                    var label = $(LABEL, control);

                    control.addClass(ACTIVE);

                    options.css({
                    	// Add 2 px to width/height to prevent unnecessary scroll bars across browsers.  
                    	// Previous adjustments were 12 for width and 0 for height.
                        width: Math.max(options.width(), value.outerWidth(), label.outerWidth()) + 14,
                        'padding-top': label.outerHeight(),
                        overflow: 'auto'
                    });
                    label.css({width: options.width() - style.scrollBarWidth()});
                    value.css({width: value.width()}); // freeze value size

                    // adjust to fit within boundary
                    var boundary = options.
                        parents('.hp-menu-boundary, body').first();
                    if ((options.offset().left + options.outerWidth()) >
                        (boundary.offset().left + boundary.innerWidth()) &&
                        options.offset().left > options.outerWidth()) {

                        options.css({left: 'auto', right: '0px'});
                    }
                    var maxHeight = Math.max(0, boundary.innerHeight() - label.outerHeight() -
                        (options.offset().top - boundary.offset().top) - 25);
                    options.css({'max-height': maxHeight});

                    if (!multiSelect) {
                        $(document).on('keydown.hpSelect', onKeypress);
                        $(control).on('mousemove.hpSelect', onMousemove);
                    }
                    // avoid bouncing
                    setTimeout(function () {
                        $(BODY).on('click.hpSelect', onBodyClick);
                    }, 50);
                }

                function parseSelection(arg) {
                    var selection = {};
                    if (typeof arg === 'string') {
                        // initialize from args if it's a string
                        selection.ids = [$.trim(arg)];
                    } else if ($.isArray(arg)) {
                        // initialize from args if it's an array
                        selection.ids = arg;
                    } else if (arg) {
                        if (arg.hasOwnProperty('ids')) {
                            selection.ids = arg.ids;
                        } else if (arg.hasOwnProperty('id')) {
                            selection.ids = [arg.id];
                        } else {
                            selection.ids = [];
                        }
                        if (sorting) {
                            selection.direction = arg.direction;
                        }
                    }
                    return selection;
                }

                function getInitialSelection() {
                    var selection = {};

                    if ($(SELECT_OPTION + '.' + SELECTED, control).length > 0) {
                        // selected option
                        if (multiSelect) {
                            selection.ids = [];
                            $(SELECT_OPTION + '.' + SELECTED, control).each(
                                function (index, option) {
                                    selection.ids.push(optionId($(option)));
                                });
                        } else {
                            selection.ids =
                                [optionId($(OPTION + '.' + SELECTED, control).first())];
                        }
                    } else if ($(VALUE, control).text().length > 0) {
                        // element value
                        if (multiSelect) {
                            selection.ids = idsForValues(
                                $(VALUE, control).text().split(VALUE_SEPARATOR));
                        } else {
                            selection.ids = idsForValues([$(VALUE, control).text()]);
                        }
                    } else {
                        // first option
                        selection.ids = [optionId($(SELECT_OPTION, control).first())];
                    }

                    if (sorting) {
                        if ($(SORT_DIRECTION_OPTION + '.hp-selected', control).length > 0) {
                            selection.direction =
                                $(SORT_DIRECTION_OPTION + '.hp-selected', control).
                                    data('sort-direction');
                        } else {
                            // first direction
                            selection.direction =
                                $(SORT_DIRECTION_OPTION, control).first().
                                    data('sort-direction');
                        }
                    }

                    return selection;
                }
                
                function selectionValue(selection) {
                    var result;
                    if (selection.values) {
                        result = selection.values.join(VALUE_SEPARATOR);
                    } else {
                        result = selection.ids.join(VALUE_SEPARATOR);
                    }
                    return result;
                }

                function alignSelection(selection) {
                    var extended, matched = false;
                    
                    // set selected option(s)
                    $(OPTION, control).removeClass(SELECTED);
                    $(SELECT_OPTION, control).
                        each(function (index, option) {
                            if ($.inArrayCaseInsensitive(optionId($(option)), selection.ids) !== -1) {
                                $(option).addClass(SELECTED);
                                matched = true;
                            }
                        });
                    // if we didn't match any, use unset extended option, if any
                    if (! matched) {
                        extended = $(SELECT_OPTION + '.hp-select-extended', control);
                        if (extended.length > 0) {
                            matched = true;
                            // see if we have a pattern to match
                            if (extended.attr('data-select-pattern')) {
                                matched = false;
                                var regexp = new RegExp(extended.attr('data-select-pattern'));
                                if (selectionValue(selection).match(regexp)) {
                                    matched = true;
                                }
                            }
                            if (matched) {
                                extended.addClass(SELECTED).attr('data-id', selection.ids[0]);
                            }
                        }
                    }
                    if (! matched) {
                        // didn't match an option, set the value element, setFromSelection()
                        // will take it from there
                        if (selection.values) {
                            $(VALUE, control).text(selection.values.join(VALUE_SEPARATOR)).
                                attr('data-id', selection.ids.join(VALUE_SEPARATOR));
                        } else {
                            $(VALUE, control).text(selection.ids.join(VALUE_SEPARATOR));
                        }
                    }

                    if (sorting) {
                        $(SORT_DIRECTION_OPTION, control).removeClass(SELECTED);
                        $(OPTION + '[data-sort-direction="' + selection.direction + '"]',
                            control).addClass(SELECTED);
                    }

                    setFromSelection();
                }

                function onControlClick() {
                    if (!control.hasClass(ACTIVE) && !control.hasClass(DISABLED)) {
                        activate();
                    }
                }

                function onKeyUp(ev) {
                    var keyCode = (ev.which ? ev.which : ev.keyCode);
                    if (keyCode == SPACE) {
                        if (!control.hasClass(ACTIVE) && !control.hasClass(DISABLED)) {
                            activate();
                        }
                    }
                }

                function getSelection() {
                    return {
                        ids: $('li:not([data-sort-direction])' +
                            ':not(.hp-sub-options).hp-selected', control).data('id'),
                        direction: $('li[data-sort-direction].hp-selected',
                            control).data('sort-direction')
                    };
                }
                
                function initialize() {
                    var selection; // {ids: ..., direction: ...}
                    if (args.selection) {
                        selection = parseSelection(args.selection);
                    } else {
                        selection = getInitialSelection();
                    }
                    alignSelection(selection);
                    control.on('click.hpSelect', onControlClick);
                    control.on('keyup.hpSelect', onKeyUp);
                    // unbind the onMouseclickEvent
                    $(OPTION, control).not('.hp-sub-options').
                        off('.hpSelect');
                    $(OPTIONS, control).
                        on('click.hpSelect', "li:not('.hp-sub-options')", onMouseclick);
                    
                    // allow caller to start with it open
                    if (args && args.activate) {
                        activate();
                    }
                }
                
                function populateOptions(options) {
                    $(OPTION, control).remove();
                    $.each(options, function(index, data) {
                        var opt = $('<li></li>');
                        if ('object' === typeof(data)) {
                            if (data.hasOwnProperty('id')) {
                                opt.attr('data-id', data.id);
                            }
                            opt.append($('<span></span>').addClass('hp-name').
                                text(data.name));
                            if (data.help) {
                                opt.prepend($('<span></span>').addClass('hp-help').
                                    text(data.help));
                            }
                            if (data.error) {
                                opt.prepend($('<span></span>').addClass('hp-error').
                                    text(data.error));
                            }
                            if (data.ignore) {
                                opt.addClass('hp-select-ignore');
                            }
                        } else {
                            opt.text(data);
                        }
                        $(OPTIONS, control).append(opt);
                    });
                }
                
                function populateFormOptions(options) {
                    var select = $('select', wrapper);
                    select.empty();
                    $.each(options, function(index, data) {
                        var opt = $('<option></option>');
                        if ('object' === typeof(data)) {
                            opt.attr('value', data.id).text(data.name);
                            if (data.help) {
                                opt.attr('data-help', data.help);
                            }
                            if (data.error) {
                                opt.attr('data-error', data.error);
                            }
                        } else {
                            opt.text(data);
                        }
                        select.append(opt);
                    });
                }
                
                function populate(args){
                    var sortDirectionOptions;
                    
                    if (args.hasOwnProperty('label')) {
                        $(LABEL, control).text(args.label);
                    }

                    if (args.hasOwnProperty('selected')) {
                        $(VALUE, control).text(args.selected.ids);
                    }

                    if (args.hasOwnProperty('options')) {
                        populateOptions(args.options);
                    }

                    if (args.hasOwnProperty('multiSelect') &&
                        args.multiSelect) {
                        control.addClass('hp-select-multi');
                    }

                    if (args.hasOwnProperty('sorting') && args.sorting) {
                        if ($('.hp-sort-options', control).length === 0) {
                            $('.hp-options', control).removeClass('hp-options').
                                wrap('<div class="hp-options"></div>');
                            sortDirectionOptions = $(sortDirectionsHtml);
                            localizer.localizeDom(sortDirectionOptions);
                            $('.hp-options', control).append(sortDirectionOptions);
                            control.addClass('hp-sort');
                        }
                    }
                }
                
                function build() {                    

                    control.addClass('hp-select').attr('tabindex', 0);

                    // set up required DOM elements if they aren't already there.

                    if ($(LABEL, control).length === 0) {
                        control.prepend('<label></label>');
                    }

                    if ($(VALUE, control).length === 0) {
                        $(LABEL, control).after('<div class="hp-value"></div>');
                    }

                    if ($(OPTIONS, control).length === 0) {
                        control.append('<ol class="hp-options"></ol>');
                    }

                    // populate with any supplied options
                    populate(args);                        
                }
                
                function updateArgsFromSelect() {
                    var select = $('select', wrapper);
                    // preserve existing args data if it already exists
                    if (! args.hasOwnProperty('options')) {
                        // extract <option>s from <select>
                        args.options = [];
                        $('option', select).each(function (index, optionElem) {
                            var opt, option = $(optionElem);
                            if (option.val()) {
                                opt = {id: option.val(), name: option.text()};
                                if (option.attr('data-help')) {
                                    opt.help = option.attr('data-help');
                                }
                                if (option.attr('data-error')) {
                                    opt.error = option.attr('data-error');
                                }
                            } else {
                                opt = option.text();
                            }
                            args.options.push(opt);
                        });
                    }
                    if (! args.hasOwnProperty('selection')) {
                        args.selection = select.val();
                    }
                }

                this.init = function () {
                    var selection; // {ids: ..., direction: ...}
                    var result = $(elem);
                    
                    if (! action || 'object' === typeof(action)) {
                        args = action || {};
                        action = 'initialize';
                    } else if (! args) {
                        args = {};
                    }
                    
                    if (result.is('select')) {
                        if (! result.parent().hasClass('hp-select-form')) {
                            // wrap
                            result.wrap('<div class="hp-select-form"></div>');
                            wrapper = result.parent();
                            control = $('<div></div>').addClass('hp-select');
                            wrapper.prepend(control);
                            result.hide().addClass('hp-select-select');
                            updateArgsFromSelect();
                        } else {
                            wrapper = result.parent();
                            control = $('> .hp-select', wrapper);
                        }
                    } else {
                        control = result;
                    }

                    if ('destroy' !== action) {
                        
                        if ('initialize' === action) {
                            build();
                        } else if ('get' === action) {
                            result = getSelection();
                        } else if ('control' === action) {
                            result = control;
                        }

                        multiSelect = control.hasClass('hp-select-multi');
                        sorting = control.hasClass('hp-sort');

                        if ('initialize' === action) {
                            initialize();
                        } else if ('set' === action) {
                            selection = parseSelection(args);
                            alignSelection(selection);
                        } else if ('reset' === action) {
                            if (result.is('select')) {
                                if (! args.hasOwnProperty('options')) {
                                    updateArgsFromSelect();
                                } else {
                                    // caller is passing us the options
                                    if (result.is('select')) {
                                        populateFormOptions(args.options);
                                    }
                                    if (! args.hasOwnProperty('selection')) {
                                        args.selection = args.options[0];
                                    }
                                }
                                populateOptions(args.options);
                                selection = parseSelection(args.selection);
                                alignSelection(selection);
                            }
                        } else if ('enable' === action) {
                            control.removeClass(DISABLED);
                        } else if ('disable' === action) {
                            control.addClass(DISABLED);
                        }

                    } else {
                        control.off('.hpSelect');
                        $(OPTIONS, control).off('.hpSelect');
                        deactivate();
                    }

                    return result;
                };

            }

            // pluginify
            var ret;
            var instance = null;
            this.each(function() {
                var $elem = $(this);
                instance = new hpSelect($elem[0]);
                ret = instance.init();
                if (action === 'get') {
                    // "get" only returns the first elements value, similar to jquery.val()
                    return false;
                }
                ret = ret ? ret.add(ret) : ret;
            });
            return ret;
            
        };
    }(jQuery));
});
