// (C) Copyright 2020 Hewlett-Packard Enterprise Company, L.P.
define(['hp/core/Localizer',
    'hp/core/Style',
    'text!hpPages/core/association_edit.html',
    'text!hpPages/core/dialog_overlay.html',
    'jquery',
    'lib/jquery.dataTables',
    'hp/lib/jquery.hpEllipsis'],
function (localizer, style, associationEditHtml, dialogOverlayHtml) {
    "use strict";
    
    // Creates a modal dialog to select resource for addition or removal.

    var AssociationEditView = (function () {
      
        var CONTAINER = '.hp-dialog-container';
        var CONTROLS = 'footer .hp-controls';
        var TABLE = '#hp-edit-choices';
        var ROW = TABLE + ' tbody tr:not(.hp-results-control)';
        var SELECTED_ROW = ROW + '.hp-selected:not(.hp-results-control)';
        var SEARCH = '.hp-search';
        var RESULTS_MESSAGE = '.hp-results-header .hp-message';
        var ADD = '.hp-add';
        var ADD_AGAIN = '.hp-add-again';
        var REMOVE = '.hp-remove';
        var REMOVE_AGAIN = '.hp-remove-again';
        var SELECT = '.hp-select';
        var CANCEL = '.hp-cancel';
        var MESSAGE = '.hp-control-message';
        var CONTROL_ROW = 'tr.hp-results-control';
        var SPINNER = CONTROL_ROW + ' .hp-spinner-small';
        var SHOW_MORE = CONTROL_ROW + ' .hp-show-more';
        var ACTIVE = 'hp-active';
        var SELECTED = 'hp-selected';
        var ERROR = 'hp-error';
        var PRIMARY = 'hp-primary';
        var PAGE_SIZE = 100;
        var ENTER = 13;
        var ESCAPE = 27;
        var A = 65; // for Ctrl-A

        // ### new AssociationEditView()
        // Create a new AssociationEditView.
        // 
        // DEPRECATED: single argument Object with properties as shown in init().
        //
        // This parameter is DEPRECATED, callers should use `new` followed by `init()`.
        
        function AssociationEditView(deprecatedArgs) {
          
            var overlay;
            var component;
            var getResultsFunc;
            var applyFunc;
            var remove = false;
            var multi = true;
            var dataTable;
            var maxResults;
            // remember the last search to avoid redoing
            var lastSearch = null;
            // preserve the width across search changes,
            // keeps it from being jumpy
            var fixedWidth = null;
            var busyCount = 0;
            var haveMore = false;
            var controlRow;
            var addHelp = '';
            var addActionLink = '';
            // Forward reference so Sonar won't complain
            var onKeyDown = function() {};
            
            function removeControlRow() {
                controlRow.remove();
            }
            
            function addControlRow() {
                if (busyCount || haveMore) {
                    $(TABLE, component).append(controlRow);
                    if (busyCount) {
                        $(SPINNER).show();
                        $(SHOW_MORE).hide();
                    } else {
                        $(SPINNER).hide();
                        $(SHOW_MORE).show();
                    }
                }
            }
            
            function calculateTableHeight() {
                var windowHeight = $(window).height();
                // use a percentage of the window, if possible
                var percentage = windowHeight * 0.5;
                var tableTop = $(TABLE, component).offset().top;
                var available = windowHeight -
                    (tableTop +
                    $(MESSAGE, component).outerHeight() +
                    component.offset().top + 20);
                    
                return Math.min(available, percentage);
            }
            
            function onResize() {
                var tableHeight = calculateTableHeight();
                // leave some room for the message
                var footerWidth = $(CONTROLS, component).outerWidth(true) + 120;
                var windowWidth = $(window).width() - 60;
                if (fixedWidth) {
                    component.css('width', Math.max(footerWidth,
                        Math.min(fixedWidth, windowWidth)));
                } else {
                    component.css('width', Math.min(footerWidth, windowWidth));
                }
                $('div.dataTables_scrollBody', component).css('height', tableHeight);
                dataTable.fnAdjustColumnSizing();
                addControlRow();
                // adjust message to take all of the footer except the controls
                $(MESSAGE, component).
                    css({'margin-right': $(CONTROLS, component).outerWidth()});
            }
            
            function updateSelectedMessage() {
                $(MESSAGE, component).
                    text(localizer.getString('core.associationEdit.selected',
                        [$(SELECTED_ROW, component).length]));
            }
            
            function onClickRow(event) {
                var row = $(this);
                if (multi && (event.ctrlKey || event.metaKey)) {
                    // Ctrl, toggle selection of this row only
                    row.toggleClass(SELECTED);
                    document.getSelection().removeAllRanges();
                } else if (multi && event.shiftKey) {
                    // user wants a range, calculate beginning and end
                    // based on nearest earlier selection, or top of table
                    // if nothing is selected.
                    var startIndex = 0;
                    var endIndex = row.index();
                    document.getSelection().removeAllRanges();
                
                    $(SELECTED_ROW, component).each(function () {
                        var index = $(this).index();
                        // select last up to endIndex
                        if (index < endIndex) {
                            startIndex = index;
                        // select first after endIndex
                        } else if (0 === startIndex && index >= endIndex) {
                            startIndex = endIndex;
                            endIndex = index;
                            return false;
                        }
                    });
                
                    $(ROW, component).each(function (index, row2) {
                        if (index >= startIndex && index <= endIndex) {
                          $(row2).addClass(SELECTED);
                        }
                    });
                } else {
                    // de-select everything else
                    $(ROW, component).removeClass(SELECTED);
                    // select just this row
                    row.addClass(SELECTED);
                    document.getSelection().removeAllRanges();
                }
                updateSelectedMessage();
            }
            
            function clearEmptyMessage() {
                $('.hp-association-no-data', component).remove();
                $('.hp-association-empty-message', component).remove();
            }
            
            function isEmptyMessageAvailable() {
                return ((addHelp && addHelp.length > 0) || (addActionLink && addActionLink.length > 0));
            }
            
            function showEmptyMessage(noDataMessage) {
                clearEmptyMessage();
                var helpText = "";
                var actionLink = "";
                var associationEmptyMessage = "";
                var elem = $('<div></div>').addClass('hp-association-no-data').text(noDataMessage);
                $(TABLE, component).after(elem);
                if (isEmptyMessageAvailable()) {
                    if (addHelp && addHelp.length > 0) {
                        helpText = $('<div></div>').addClass('hp-add-help').html(addHelp);
                    }
                    if (addActionLink && addActionLink.length > 0) {
                        actionLink = $('<div></div>').addClass('hp-add-action-link').html(addActionLink);
                    }
                    associationEmptyMessage = $('<div></div>').addClass('hp-association-empty-message');
                    associationEmptyMessage.append(helpText);
                    associationEmptyMessage.append(addActionLink);
                    $('.hp-association-no-data', component).after(associationEmptyMessage);
                }
            }
            
            
            function showHeader(localizationId, args, hasNoData) {
                var text = localizer.getString(localizationId, args);
                if (hasNoData) {
                    showEmptyMessage(text);
                    $(RESULTS_MESSAGE, component).empty();
                } else {
                    $(RESULTS_MESSAGE, component).text(text);
                }
                
            }
            
            function messageForResults(results) {
                var count = (results.hasOwnProperty('start') ? results.start : 0) + results.count;
                if (count > 0) {
                    if (lastSearch) {
                        if (! results.total || count < results.total) {
                            showHeader('search.searchCombo.partialMatch',
                                [count, (results.total ? results.total : '?')]);
                        } else if (count > 1) {
                            showHeader('search.searchCombo.allMatches', [count]);
                        } else {
                            showHeader('search.searchCombo.singleMatch', [count]);
                        }
                    } else {
                        if (! results.total || count < results.total) {
                            showHeader('search.searchCombo.partial',
                                [count, (results.total ? results.total : '?')]);
                        } else if (maxResults > PAGE_SIZE) {
                            // more than a page's worth, show all message
                            showHeader('search.searchCombo.all', [count]);
                        } else {
                            // no search text and showing all, no message needed
                            $(RESULTS_MESSAGE, component).empty();
                        }
                    }
                } else {
                    if (lastSearch) {
                        showHeader('search.searchCombo.noMatches');
                    } else {
                        showHeader('search.searchCombo.none', null, true);
                    }
                }
            }
            
            function onShowMoreClick() {
                // ask caller for results
                busyCount += 1;
                maxResults += PAGE_SIZE;
                $(SPINNER).show();
                $(SHOW_MORE).hide();
                if (lastSearch) {
                    showHeader('search.searching');
                } else {
                    showHeader('core.loading');
                }
                getResultsFunc(lastSearch, {
                    success: onResultsSuccess,
                    error: onResultsError
                }, PAGE_SIZE, (maxResults - PAGE_SIZE));
            }
            
            function onResultsSuccess(results) {
                busyCount -= 1;
                if (busyCount <= 0) {
                    $(SPINNER, component).hide();
                    busyCount = 0;
                }
                removeControlRow();
                
                messageForResults(results);
                
                if (0 === results.start) {
                    dataTable.fnClearTable(results.count === 0);
                    clearEmptyMessage();
                    messageForResults(results);
                }
                dataTable.fnAddData(results.members);
                
                if (results.total &&
                    ((results.start + results.count) > maxResults ||
                      results.total > maxResults)) {
                    haveMore = true;
                    addControlRow();
                } else {
                    haveMore = false;
                }

                updateSelectedMessage();
                
                // on the first time through with some data, use
                // the width of the table from now on
                if (results.count > 0 && ! fixedWidth) {
                    fixedWidth = component.width();
                    onResize();
                }
                $(SEARCH).focus();
                
                $(MESSAGE, component).removeClass(ERROR);
            }
            
            function onResultsError(message) {
                busyCount -= 1;
                if (busyCount <= 0) {
                    $(SPINNER, component).hide();
                    busyCount = 0;
                }
                dataTable.fnClearTable(true);
                $(SEARCH).focus();
                onResize();
                $(MESSAGE, component).text(message);
                $(MESSAGE, component).addClass(ERROR);
            }
            
            function getResults() {
                var search = $(SEARCH, component).val();
                if (getResultsFunc && search !== lastSearch) {
                    // ask caller for results
                    busyCount += 1;
                    lastSearch = search;
                    haveMore = false;
                    maxResults = PAGE_SIZE;
                    $(MESSAGE, component).text('');
                    
                    dataTable.fnClearTable();
                    addControlRow();
                    $(SPINNER).show();
                    
                    if (lastSearch) {
                        showHeader('search.searching');
                    } else {
                        showHeader('core.loading');
                    }
                    
                    getResultsFunc(search, {
                        success: onResultsSuccess,
                        error: onResultsError
                    }, maxResults, 0);
                    
                    onResize();
                } else {
                    lastSearch = search;
                }
            }
            
            function onCancel() {
                overlay.remove();
                $(CONTAINER, overlay).removeClass(ACTIVE);
                overlay.removeClass(ACTIVE);
                $(window).off('resize', onResize);
                $(document).off('keydown', onKeyDown);
            }
            
            function onAddAgain(event) {
                if (event) {
                    // user clicked Add +, make that the primary button
                    $(ADD, component).removeClass(PRIMARY);
                    $(ADD_AGAIN, component).addClass(PRIMARY);
                }
                var rows = $(SELECTED_ROW, component);
                var results = $.map(rows, function(row) {
                    return dataTable.fnGetData(row);
                });
                if (results.length > 0) {
                    // tell caller we have something to apply
                    if (applyFunc) {
                        // allow caller to return false to prevent
                        // us from displaying a message indicating we've
                        // performed the apply
                        if (false !== applyFunc(results)) {
                            $(MESSAGE, component).text(
                                localizer.getString('core.associationEdit.added',
                                    [results.length]));
                        }
                    }
                    rows.removeClass(SELECTED);
                    $(SEARCH, component).val('');
                    lastSearch = null;
                    getResults();
                }
            }
            
            function onAdd(event) {
                if (event) {
                    // user clicked Add, make that the primary button
                    $(ADD_AGAIN, component).removeClass(PRIMARY);
                    $(ADD, component).addClass(PRIMARY);
                }
                onAddAgain();
                onCancel();
            }
            
            function onRemoveAgain(event) {
                if (event) {
                    // user clicked Remove +, make that the primary button
                    $(REMOVE, component).removeClass(PRIMARY);
                    $(REMOVE_AGAIN, component).addClass(PRIMARY);
                }
                var rows = $(SELECTED_ROW, component);
                var results = $.map(rows, function(row) {
                    return dataTable.fnGetData(row);
                });
                if (results.length > 0) {
                    // tell caller we have something to apply
                    if (applyFunc) {
                        // allow caller to return false to prevent
                        // us from displaying a message indicating we've
                        // performed the apply
                        if (false !== applyFunc(results)) {
                            $(MESSAGE, component).text(
                                localizer.getString('core.associationEdit.removed',
                                    [results.length]));
                        }
                    }
                    rows.removeClass(SELECTED);
                    $(SEARCH, component).val('');
                    lastSearch = null;
                    getResults();
                }
            }
            
            function onRemove(event) {
                if (event) {
                    // user clicked Remove, make that the primary button
                    $(REMOVE_AGAIN, component).removeClass(PRIMARY);
                    $(REMOVE, component).addClass(PRIMARY);
                }
                onRemoveAgain();
                onCancel();
            }
            
            function onKeyUp(event) {
                clearEmptyMessage();
                getResults();
            }
            
            /**
             * Note: this function does not capture backspace.
             * Browser backspace behavior should be respected
             */
            onKeyDown = function (event) {
                var keyCode = (event.which ? event.which : event.keyCode);
                if (keyCode == ESCAPE) {
                    onCancel();
                    event.preventDefault();
                } else if (keyCode == ENTER) {
                    if (remove) {
                        if ($(REMOVE_AGAIN).hasClass(PRIMARY)) {
                            onRemoveAgain();
                        } else {
                            onRemove();
                        }
                    } else {
                        if ($(ADD_AGAIN).hasClass(PRIMARY)) {
                            onAddAgain();
                        } else {
                            onAdd();
                        }
                    }
                    event.preventDefault();
                // Ctrl-A outside of the search control means select everything
                } else if (keyCode == A && (event.ctrlKey || event.metaKey) &&
                    ! $(SEARCH, component).is(':focus')) {
                    $(ROW, component).addClass(SELECTED);
                    updateSelectedMessage();
                    event.preventDefault();
                }
            };
            
            function setLanguage(options) {
                if (! options.oLanguage) {
                    options.oLanguage = localizer.getString('core.dataTables.oLanguage');
                }
            }
            
            function initialize(args) {
                remove = args.remove || false;
                if (args.hasOwnProperty('multi')) {
                    multi = args.multi;
                }
                getResultsFunc = args.getResults;
                applyFunc = args.apply;
                addHelp = args.addHelp;
                addActionLink = args.addActionLink;
                component = $(associationEditHtml).filter('#hp-association-edit');
                localizer.localizeDom(component);
                controlRow = $(CONTROL_ROW, component).detach();
                overlay = $(dialogOverlayHtml);
                localizer.localizeDom(overlay);
                $(CONTAINER, overlay).append(component).addClass(ACTIVE);
                $('> header h1', component).text(args.title);
                $('> header .hp-help-current', component).
                    attr('href', '/doc#' + (args.helpLocation || '/')).
                    attr('target', 'hphelp');
                
                var options = {
                    bPaginate : false,
                    bFilter : false,
                    bInfo : false,
                    bDeferRender : false,
                    bAutoWidth : false,
                    sScrollY : calculateTableHeight(),
                    bSort : false,
                    aaData : args.results ? args.results : []
                };

                $.extend(options, args.dataTableOptions);
                // Set the oLanguage property if not set.
                setLanguage(options);
                dataTable = $(TABLE, component).dataTable(options);

                overlay.addClass(ACTIVE);
                $('#hp-body-div').append(overlay);
                $('> header h1', component).hpEllipsis();
                $(MESSAGE, component).toggle(multi);
                                
                $(ADD, component).toggle(multi && ! remove).on('click', onAdd);
                $(ADD_AGAIN, component).toggle(multi && ! remove).on('click', onAddAgain);
                $(REMOVE, component).toggle(multi && remove).on('click', onRemove);
                $(REMOVE_AGAIN, component).toggle(multi && remove).on('click', onRemoveAgain);
                $(SELECT, component).toggle(! multi).on('click', onAdd);
                $(CANCEL, component).on('click', onCancel);
                $(SEARCH, component).focus().on('keyup', onKeyUp);
                $(component).on('click', ROW, onClickRow);
                $(component).on('click', SHOW_MORE, onShowMoreClick);
                $(document).on('keydown', onKeyDown);
                $(window).on('resize', onResize);
                onResize();

                // get initial content
                getResults();
            }

            if (deprecatedArgs) {
                initialize(deprecatedArgs);
            }
            
            // ### init(args)
            // Call after `new` to initialize.
            //
            // Takes a single optional argument object with properties as follows:
            //
            // - **title** title of the dialog
            // - **dataTableOptions** jquery.dataTables options
            //     Recommend at least providing aoColumns with sName and sWidth.
            // - **getResults** function called when results are needed
            //     function(query, handlers, count, start)
            //       The function should call one of the handlers as follows
            //         handlers.success({count: N, total: M, start: Q, members: [...]});
            //         handlers.error(errorMessage);
            // - **apply** function called when the user clicks ok or apply
            //     function(resultsMembers)
            //       The resultsMembers are the same members provided in the
            //       getResults() success case.
            // - **remove** true|(false) - whether this if for remove or add
            //     This primarily affects the labelling of the form buttons.
            // - **multi** (true)|false - whether this supports mult-select
            this.init = function (args) {
                initialize(args);
            };
            
            // ### cancel()
            // Call to cancel the dialog.
            this.cancel = function () {
                onCancel();
            };
        }

        return AssociationEditView;
    }());

    return AssociationEditView;
});
