// (C) Copyright 2020 Hewlett-Packard Enterprise Company, L.P.
/*global clearTimeout*/
/**
 * @type {MasterGridView}
 */
define(['hp/core/LinkTargetBuilder', 'hp/core/Localizer', 'hp/core/Style', 'jquery'],
function (linkTargetBuilder, localizer, style) {
"use strict";

    var MasterGridView = (function () {

        // consts can go here
        var MASTER_PANE = '.hp-master-pane';
        var DETAILS_PANE = '.hp-details-pane';
        var GRID = '.hp-master-grid';
        var ITEM = '.hp-master-grid-item';
        var SELECTED = 'hp-selected';
        var KEY_DOWN = 40;
        var KEY_UP = 38;
        var LOADMORE = 'loadMore';
        var LOADMORE_ABOVE = 'loadMoreAbove';
        var LOADMORE_SPINNER = '<div class="hp-spinner-small"><div class="hp-spinner-image"></div></div>';
        var REFRESH  = 'refresh';
        var SELECTED_ITEM_OFFSET  = 1;

        /**
         * @constructor
         */
        function MasterGridView() {

            //
            // private class instance variables
            //
            var presenter = null;
            var resource = null;
            var page = null;
            var pane = null;
            var renderer = null;
            var manualScrollTo = -1;
            var layoutTimer = null;
            var relayoutTimer = null;
            var addHelp = "";
            var addActionLink = "";
            var sorter = null;
            // remember how many rows is in the table before "Load more" is clicked;
            // it is used to maintain the scroll position to the previous Top
            var prevRowStart;
            var disableSelectionScroll = false;
            
            function fireRelayout() {
                // we delay so any CSS animation can finish
                clearTimeout(relayoutTimer);
                relayoutTimer = setTimeout(function () {
                    page.trigger('relayout');
                }, style.animationDelay());
            }

            function clearSelection() {
                $(ITEM, pane).removeClass(SELECTED);
            }

            function setSelection(selection) {
                if (selection.uris && selection.uris.length > 0) {
                    $.each($(ITEM), function() {
                        var item = this;
                        var indexResult = $.data(item, 'hpIndexResult');

                        if ((indexResult) &&
                            ($.inArray(indexResult.uri, selection.uris) !== -1)) {
                            $(item).addClass(SELECTED);
                        }
                    });
                }
            }
            
            /**
             * @private
             * Scroll to the table row with padding rows above it
             * @param {Element} row The row to scroll to
             */
            function scrollAndPad(row) {
                var rowIdx = row.prevAll().length;
                var rowsToScroll;
                var firstRowToScroll;
                var scrollHeight;
                
                if (presenter.haveMoreAbove() && (SELECTED_ITEM_OFFSET >= rowIdx)) {
                    // hide the "Load more" row if it exists
                    rowsToScroll = 1 ;
                } else if (rowIdx >= SELECTED_ITEM_OFFSET) {
                    // if we have enough rows above, apply padding 
                    rowsToScroll = rowIdx - SELECTED_ITEM_OFFSET;
                } else {
                    // show everything
                    rowsToScroll = 0;
                }                
                firstRowToScroll = $(GRID).children()[rowsToScroll];
                scrollHeight = $(firstRowToScroll).position().top - $(GRID).children().first().position().top;                
                $(GRID).animate({'scrollTop' : scrollHeight}, 20);
            }
            
            function scrollToSelection() {
                var row;

                if (manualScrollTo !== -1) {
                    $(GRID).animate({'scrollTop': manualScrollTo}, 20);
                } else if (! disableSelectionScroll) {
                    row = $(ITEM + '.' + SELECTED).first();
                    if (row.length > 0) {
                        scrollAndPad(row);
                    }

                    setTimeout(function () {
                        if (manualScrollTo !== -1) {
                            // reset since we just scrolled
                            manualScrollTo = -1;
                        }
                    }, 5000);
                }
            }
            
            // After an item is clicked, make sure it is not partially hidden
            // from the top or bottom.
            function alignSelectedItem() {
                var scrollTo = manualScrollTo;
                var reset = false;
                
                if (scrollTo === -1) {
                    var item = $(ITEM + '.' + SELECTED).first();
                    if (item.length > 0) {
                        if ($(item).position().top < $(GRID).position().top) {
                            scrollTo =  $(GRID).scrollTop() +
                                item.position().top - $(GRID).position().top;                                                
                        } else if (($(item).position().top + $(item).outerHeight()) >
                            ($(GRID).position().top + $(GRID).innerHeight())) {
                            // make sure we don't scroll up so we can't see the top of the item
                            scrollTo =  Math.min(
                                $(GRID).scrollTop() +
                                    item.position().top - $(GRID).position().top,
                                $(GRID).scrollTop() + item.position().top -
                                    $(GRID).innerHeight() + item.outerHeight()); 
                        }
                    }
                    reset = true;
                }
                
                if (scrollTo !== -1) {
                    $(GRID).animate({'scrollTop' : scrollTo}, 20);
                }
            }

            function resetSelection(selection, keepManualScroll) {
                clearSelection();
                setSelection(selection);

                if (!keepManualScroll) {
                    manualScrollTo = -1;
                }
                if (selection.uris.length > 0) {
                    scrollToSelection();
                }
            }
            
            // @private
            // Preserve current scroll position when "Load more" is clicked, or when new data is added
            // to the grid. If "Load more (above)" link has been clicked, or new data is added above,
            // calculate their height and scroll down. 
            // If "Load more (bottom)" is clicked, then just use the current scrolling position maintained
            // in 'manualScrollTo' variable
            // @param {int} extraRowsAbove The number of rows added above the current table.
            function preserveScrollPosition(extraRowsAbove) {
                var topOffset,
                    topGrid,
                    includeControlRow = presenter.haveMoreAbove() ? 1 : 0;
                
                if (extraRowsAbove - includeControlRow > 0) { 
                    topOffset = $(GRID).children().first().position().top; // offset of the first grid
                    topGrid = $(GRID).children()[extraRowsAbove]; //previous row top
                    manualScrollTo = $(topGrid).position().top - topOffset - $(topGrid).outerHeight(true);
                }
                clearSelection();
                setSelection(presenter.getSelection());
                if (manualScrollTo !== -1) {
                    $(GRID).animate({'scrollTop': manualScrollTo}, 20);
                }
            }
            
            function onItemClick(event) {
                // get index result from object and call presenter
                var item = $(event.target).closest('.hp-master-grid-item');
                var indexResult = $(item).data('hpIndexResult');
                disableSelectionScroll = true;
                presenter.select(indexResult,
                    (event.ctrlKey || event.metaKey), event.shiftKey);
                $(GRID).focus();

                alignSelectedItem();
                disableSelectionScroll = false;
            }
            
            function isEmptyMessageAvailable() {
                return ((addHelp && addHelp.length > 0) || (addActionLink && addActionLink.length > 0));
            }
            
            function setEmptyMessage(indexResults) {
                var item = $('<li></li>').addClass('hp-master-grid-control');
                var empty = $('<div></div>').addClass('hp-master-grid-empty').
                    text(presenter.getEmptyMessage(indexResults));
                item.append(empty);
                var helpText = "";
                var actionLink = "";
                var gridEmptyMessage = "";
                if (presenter.haveContacted() && 
                        ! presenter.haveSome() && 
                        isEmptyMessageAvailable() && 
                        presenter.isEmptyOrUnfiltered(indexResults)) {
                    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);
                    }
                    gridEmptyMessage = $('<div></div>').addClass('hp-help');
                    gridEmptyMessage.append(actionLink);
                    item.append(gridEmptyMessage);
                }
                $(GRID, pane).append(item);
            }
            
            function calcTargetHeight() {
                var result;
                // use all height available in the pane that isn't used by other
                // elements in the pane, 20 provides padding
                result = pane.height() - 20;
                $.each(pane.children(), function (index, child) {
                    if ($(child).is(':visible') &&
                        ! $(child).hasClass('hp-master-grid') &&
                        ! $(child).hasClass('ui-resizable-handle')) {
                       result -= $(child).outerHeight();
                    }
                });
                return result;
            }
            
            function layout() {
                var currentWidth = pane.width();
                var paddingWidth = (pane.outerWidth() - currentWidth);
                var targetHeight = calcTargetHeight();
                // what column sizes should we align to?
                var quanta = Math.floor($(window).width() / 12);
                var targetWidth, requestedItemWidth, itemWidth;
                
                // How much width do the items want?
                // Determine by shrinking the grid so the items can report what they need
                $(GRID, pane).css({'width': quanta + style.scrollBarWidth()});
                $(ITEM, pane).css({'width': ''});
                //widths = $(ITEM, pane).map(function (index, item) {
                //    return $(item).outerWidth();
                //}).get();
                // pick the largest
                //requestedItemWidth = Math.max.apply(null, widths);
                requestedItemWidth = $(ITEM, pane).first().outerWidth();
                var itemMarginWidth =
                    $(ITEM, pane).first().outerWidth(true) - requestedItemWidth;
                
                // modulate to discrete window column sizes
                itemWidth = quanta * Math.floor((requestedItemWidth + (quanta / 2)) / quanta);
                
                // figure out what to size the master pane width to
                if (pane.hasClass('hp-resized')) {
                    // user has set an explit size
                    targetWidth = currentWidth;
                    var availableTargetWidth = targetWidth - style.scrollBarWidth();
                    // fill available space
                    var delta = (availableTargetWidth / (itemWidth + itemMarginWidth));
                    itemWidth = Math.floor(availableTargetWidth / Math.floor(delta)) - itemMarginWidth;
                    // make sure the items aren't clipped
                    itemWidth = Math.min(itemWidth, availableTargetWidth);
                } else {
                    // one column and leave room for scrollbars
                    targetWidth = itemWidth + itemMarginWidth + style.scrollBarWidth();
                    // also check width of header items
                    $.each($('.hp-master-header h1', pane).children(), function (index, child) {
                        if ($(child).is(':visible')) {
                           targetWidth = Math.max($(child).outerWidth(true), targetWidth);
                        }
                    });
                }
                
                //console.log('!!! MGV', (new Date()).getTime(), currentWidth, '->', targetWidth, itemWidth);
                
                $(GRID, pane).css({'width': ''});
                $(ITEM, pane).css({'width': itemWidth});
                $(GRID).css('height', targetHeight);
                if (currentWidth !== targetWidth &&
                    ! pane.hasClass('hp-resized')) {
                    // adjust width since master pane is box sized for content-box
                    // align with container padding
                    pane.css({'height': '', 'width': targetWidth});
                    $(DETAILS_PANE, page).css('left', targetWidth + paddingWidth);
                    fireRelayout();                        
                }
            }

            function resetSort() {
                // reset sort indicator:
                // set the sorter to the presenter sort value if available, otherwise set the presenter
                // sort value to the sorter value
                if (sorter) {
                    var sort = presenter.getSort();
                    if (sort) {
                        $('.hp-sort', sorter).hpSelect('set', {ids: [sort.name], direction: sort.direction});
                    } else {
                        var selection = $('.hp-sort', sorter).hpSelect('get');
                        if (selection && selection.ids) {
                            presenter.setSort(selection.ids, selection.direction ? selection.direction : 'asc');
                        }
                    }
                }
            }
            
            function loadMore(listItemElement, fromAbove) {
                listItemElement.empty().append(LOADMORE_SPINNER);
                listItemElement.attr('align', 'center');
                $('.hp-spinner-small', listItemElement).show();
                presenter.loadMore(fromAbove);
            }
            
            function onLoadMoreAboveClick(ev) {
                loadMore($(ev.target.parentElement), true);
            }
            
            function onLoadMoreClick(ev) {
                loadMore($(ev.target.parentElement), false);
            }

            /**
             * @private
             * @param {Object} data the IndexResults
             */
            function onIndexResultsChange(indexResults) {
                var item;
                var extraRowsAbove = prevRowStart - indexResults.start;
                if (!indexResults.filter.hasReferenceUri()) {
                    // if the results is not generated by referenceUri, then the scroll
                    // reference is always 0.
                    extraRowsAbove = 0;
                }
                if (indexResults.start !== 0) {
                    // if there are more items above, then add one more row for
                    // "Load more" link on top.
                    extraRowsAbove++;
                }
                // store indexResults start location
                prevRowStart = indexResults.start;
                
                if (indexResults && renderer) {
                    var expandedUris = $(ITEM + '.hp-expanded', pane).map(function () {
                        return $(this).data('hpIndexResult').uri;
                    }).get();
                
                    $(GRID, pane).removeClass('hp-changing');
                    $(GRID, pane).empty();
                    $(GRID, pane).removeClass('hp-empty');
                    
                    $.each(indexResults.members, function (index, result) {
                        item = $(renderer(result));
                        $(GRID, pane).append(item);
                        item.data('hpIndexResult', result);
                        if (-1 !== $.inArray(result.uri, expandedUris)) {
                            item.addClass('hp-expanded');
                        }
                        item.click(onItemClick);
                        $('.hp-master-item-expander', item).click(function (ev) {
                            $(this).closest('.hp-master-grid-item').
                                toggleClass('hp-expanded');
                            ev.stopPropagation();
                        });
                    });
                    
                    if (presenter.haveMoreAbove()) {
                        $(GRID, pane).prepend(
                                '<li class="hp-master-grid-control">' +
                                '<a id="hp-master-load-more-above">' +
                                localizer.getString('core.master.showMore') + 
                                '</a></li>');
                        $('#hp-master-load-more-above', pane).click(onLoadMoreAboveClick);
                    }
                    
                    if (presenter.haveMore()) {
                        // we could show more. Add control to add more
                        $(GRID, pane).append(
                            '<li class="hp-master-grid-control">' +
                            '<a id="hp-master-load-more">' +
                            localizer.getString('core.master.showMore') +
                            '</a></li>');
                        $('#hp-master-load-more', pane).click(onLoadMoreClick);
                    } else if (indexResults.total === 0) {
                        setEmptyMessage(indexResults);
                    }

                    resetSort();
                    layout();
                    
                    if (indexResults.members.length > 0) {
                        if ((indexResults.__generatedBy === LOADMORE_ABOVE) ||
                            (indexResults.__generatedBy === REFRESH)) {
                            preserveScrollPosition(extraRowsAbove);
                        } else if (indexResults.__generatedBy === LOADMORE) {
                            preserveScrollPosition(-1);
                        } else { 
                            // indexResults.__generatedBy === RELOAD
                            resetSelection(presenter.getSelection());
                        }
                    }
                }
            }

            function onIndexResultsError(errorInfo) {
                if (pane) {
                    $(GRID, pane).removeClass('hp-changing');
                    $(GRID, pane).addClass('hp-empty');
                }
            }
            
            function onIndexResultsChanging() {
                if (pane) {
                    $(GRID, pane).addClass('hp-changing');
                }
            }
            
            // align selection classes with uris
            function onSelectionChange(selection) {
                resetSelection(selection);
            }

            // Invalidate current selection
            function onInvalidSelection(selection) {
                $(GRID, pane).removeClass('hp-changing');
                clearSelection();
            }

            function onKeyDown(event) {
                if ($(GRID).is(":focus")) {
                    var keyCode = (event.which ? event.which : event.keyCode);
                    if (keyCode === KEY_DOWN) {
                        manualScrollTo = -1;
                        $('.hp-master-grid-item.hp-selected').next().trigger('click');
                        event.stopPropagation();
                        return false;
                    } else if (keyCode === KEY_UP) {
                        manualScrollTo = -1;
                        $('.hp-master-grid-item.hp-selected').prev().trigger('click');
                        event.stopPropagation();
                        return false;
                    }
                }
            }
            
            function onResize(event) {
                if (event.target === window) {
                    clearTimeout(layoutTimer);
                    layoutTimer = setTimeout(layout, 50);
                } else if (event.target === page[0]) {
                    clearTimeout(layoutTimer);
                    layout();
                }
            }

            function onSortChange(event, sort) {
                presenter.setSort(sort.id, sort.direction);
            }

            /**
             * @public
             * Stop the timer polling of the index service.
             */
            this.pause = function () {
                // Atlas 2.1 Integration: Disabling onIndexResultsChanging to make master pane visible always
                //presenter.off("indexResultsChanging", onIndexResultsChanging);
                presenter.off("indexResultsChange", onIndexResultsChange);
                presenter.off("indexResultsError", onIndexResultsError);
                presenter.off("selectionChange", onSelectionChange);
                presenter.off("invalidSelection", onInvalidSelection);
                $(window).off('resize', onResize);
                page.off('relayout', layout);
                if (sorter) {
                    sorter.hide();
                }
            };

            /**
             * @public
             * Resume the timer polling of the index service.
             */
            this.resume = function () {
                resetSelection(presenter.getSelection(), true);
                manualScrollTo = -1;
                // Atlas 2.1 Integration: Disabling onIndexResultsChanging to make master pane visible always
                //presenter.on("indexResultsChanging", onIndexResultsChanging);
                presenter.on("indexResultsChange", onIndexResultsChange);
                presenter.on("indexResultsError", onIndexResultsError);
                presenter.on("selectionChange", onSelectionChange);
                presenter.on("invalidSelection", onInvalidSelection);
                $(window).on('resize', onResize);
                page.on('relayout', layout);
                if (sorter) {
                    resetSort();
                    sorter.show();
                }
            };
            
            /**
             * @public
             * Intialize the view.
             */
            this.init = function (presenterArg, args) {
                presenter = presenterArg;
                resource = args.resource;
                page = args.page;
                pane = $(MASTER_PANE, page);
                renderer = args.gridItemRenderer;
                addHelp = args.addHelp;
                addActionLink = args.addActionLink;
                sorter = $('.hp-sub-nav .hp-master-grid-sort', page);

                if (sorter && sorter.length > 0) {
                    sorter.hide();
                    $('.hp-master-add', pane).after(sorter);
                    $('.hp-sort', sorter).hpSelect({sorting: true}).change(onSortChange);
                }
                
                $(GRID).scroll(function () {
                    // stop auto scrolling if the user scrolls
                    manualScrollTo = $(GRID).scrollTop();
                });
                $(GRID).on('focus', function (event) {
                    if (event.target === $(GRID)[0]) {
                        $(document).off('keydown', onKeyDown);  
                        $(document).on('keydown', onKeyDown);
                    }
                });
                $(GRID).on('blur', function (event) {
                    if (event.target === $(GRID)[0]) {
                        $(document).off('keydown', onKeyDown);  
                    }
                });

            };
        }

        return MasterGridView;

    }());

    return MasterGridView;
});
