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

    var IndexTableView = (function () {

        var TABLE = '.hp-index-table';
        var TABLE_BODY = '> tbody';
        var ROW = TABLE_BODY + ' > tr';
        var ROW_COLLAPSER = '> td > .hp-collapser';
        var SCROLL_ROW = ROW + '.hp-scroll-first';
        var SCROLLER = '.dataTables_scrollBody';
        var SELECTED = 'hp-selected';
        var ACTIVE = 'hp-active';
        var EXPANDED = 'hp-expanded';
        var SCROLL_FIRST = 'hp-scroll-first';
        var EXPAND_ROW_ON_CLICK = false;
        var TABLE_NO_CHANGE = "TABLE_NO_CHANGE";
        var TABLE_NEEDS_RELOAD = "TABLE_NEEDS_RELOAD";
        var TABLE_CHANGED = "TABLE_CHANGED";

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

            var presenter = null;
            var container = null;
            var table = null;
            var dataTable = null;
            var scroller = null;
            var resource = null;
            var detailsRenderer = null;
            var detailsCollapsed = null;
            var layoutTimer = null;
            var scrollUri;
            var manualScrollTo = -1;
            var scrollRowOffset = -2;
            var scrollRowTopDelta = -1;
            // this flag is set to true as soon as the first details of the active URI row are rendered
            // it ensures that the initial scroll takes into account the changed size of the active URi row
            
            var manualScrollEnabled = true;   
            var scrollAfterNewDetailsAdded;  // forward function declaration


  
            function expandRow(row, userToggle, details) {
                if (! $(ROW_COLLAPSER, row).hasClass(ACTIVE)) {
                    $(ROW_COLLAPSER, row).addClass(ACTIVE);
                    row.addClass(EXPANDED);
                    if (! details) {                      
                         details = detailsRenderer(dataTable.fnGetData(row[0]), userToggle, scrollAfterNewDetailsAdded);                        
                         details.show();                       
                    }
                    var detailsRow = dataTable.fnOpen(row[0], details[0],
                        'hp-row-details-cell');
                    $(detailsRow).addClass('hp-row-details-row');
                    if (row.hasClass(SELECTED)) {
                        $(detailsRow).addClass(SELECTED);
                    }
                }
            }

            function collapseRow(row) {
                if ($(ROW_COLLAPSER, row).hasClass(ACTIVE)) {
                    $(ROW_COLLAPSER, row).removeClass(ACTIVE);
                    if (detailsCollapsed) {
                        detailsCollapsed(dataTable.fnGetData(row[0]));
                    }
                    row.removeClass(EXPANDED);
                    dataTable.fnClose(row[0]);
                }
            }

            function toggleExpansion(row, userToggle) {
                if (row.hasClass(EXPANDED)) {
                    collapseRow(row);
                } else {
                    expandRow(row, userToggle);
                }
            }
            
            function getRowByUri(uri) {
                var tableNodes = dataTable.fnGetNodes();
                var row, indexResult;
                for (var i = tableNodes.length; i--;) {
                    row = tableNodes[i];
                    indexResult = dataTable.fnGetData(row);
                    if (indexResult && indexResult.uri === uri) {
                        row = $(row);
                        break;
                    }
                }
                return row;
            }
            
            function getScrollRowOffset() {
                var scrollRow = $(SCROLL_ROW, table);
                var result = 0;
                if (scrollRow.length > 0) {
                    result = scrollRow.offset().top - table.offset().top;
                }
                return result;
            }
            
            function setScrollReference() {
                if (scrollRowOffset >= 0) {
                    scrollRowOffset = getScrollRowOffset();
                    scrollRowTopDelta = scrollRowOffset - scroller.scrollTop();
                }
            }
            
            function updateScrollPosition() {
                if (-1 === scrollRowOffset) {
                    // We reset scrollRowOffset to -1 when we process an index results
                    // change. When the eventual layout() is done, we will call
                    // this function which will use the current hp-scroll-row
                    // to determine the offset.
                    scrollRowOffset = getScrollRowOffset();
                }
                if (scrollRowOffset >= 0) {
                    var newTop = (scrollRowOffset - scrollRowTopDelta);
                    scroller.scrollTop(newTop);
                }
            }
            
            function firstScrolledRow() {
                var rows = $(ROW, table);
                var length = rows.length;
                var scrollerTop = scroller.offset().top;
                var rowTop;
                var result;
                
                for (var i=0; i<length; i++) {
                    result = $(rows[i]);
                    rowTop = result.offset().top;
                    if (rowTop >= scrollerTop) {
                        var indexResult = dataTable.fnGetData(result[0]);
                        if (! indexResult) {
                            // must be an expanded row, choose prior row
                            result = $(rows[i-1]);
                        }
                        break;
                    }
                }
                return result;
            }
            
            /**
             * Returns the URI of the current scroll row.
             * If the scroll row has no data, return 'top'
             */
            function rememberScroll() {
                var scrollRow = $(SCROLL_ROW, table);
                var scrollRowUri;
                var indexResult;
                
                if (scrollRow.length > 0) {
                    indexResult = dataTable.fnGetData(scrollRow[0]);
                    if (indexResult) {
                        scrollRowUri = indexResult.uri;
                    } else {
                        scrollRowUri = 'top';
                    }
                }
                
                // don't react to scrolling while reloading
                scrollRowOffset = -1;
                              
                return scrollRowUri;
            }
            
            /**
             * Restore the scroll position to the URI remembered via rememberScroll().
             */
            function restoreScroll(scrollRowUri) {
                var row;
                
                if ('top' === scrollRowUri) {
                    row = $(FIRST_ROW, table);
                } else if (scrollRowUri) {
                    row = getRowByUri(scrollRowUri);
                }
            
                if (row && row.length > 0) {
                    $(SCROLL_ROW, table).removeClass(SCROLL_FIRST);
                    row.addClass(SCROLL_FIRST);
                    // we will scroll as part of the eventual layout()
                }
                               
            }

            /**
             * Adds the "show more" row at the end.
             */
            function addControls() {
                if (presenter.haveMore()) {
                    // add "show more" row
                    $(TABLE_BODY, table).append(
                        '<tr class="hp-index-table-control">' +
                        '<td colspan="10">' +
                        '<a class="hp-index-show-more">' +
                        localizer.getString('core.master.showMore') +
                        '</a>' +
                        '</td></tr>');
                }
            }

            function removeControls() {
                $('.hp-index-table-control', table).remove();
            }

            function refreshTable(indexResults, expandedUris) {
                var start = indexResults.start;
                var length = indexResults.count;
                var tableLength = dataTable.fnGetData().length;
                var indexResult, tableData;
                var result = TABLE_NO_CHANGE;
                var tableDataIndex, expandedRow;
                
                for (var i=0; i<length; i++) {
                    tableDataIndex = start + i;
                    indexResult = indexResults.members[i];
                    tableData = dataTable.fnGetData(tableDataIndex);                    

                    if (! tableData) {
                        dataTable.fnAddData(indexResult, false);
                        result = TABLE_CHANGED;
                    } else if (indexResult.uri !== tableData.uri) {
                        // if tasks UIRs don't match, discard and reload table
                        result = TABLE_NEEDS_RELOAD;
                        break;
                    } else {
                        if ( (indexResult.modified !== tableData.modified) || 
                                    ( indexResult.__updatedTimestamp)) {
                            // either the data has changed or a result has been added
                            // or removed such that the uris don't line up
                            dataTable.fnUpdate(indexResult, tableDataIndex, undefined, false);
                            result = TABLE_CHANGED;
                        }
                        
                        expandedRow = dataTable.fnGetNodes(tableDataIndex);
                        if ($.inArray(indexResult.uri, expandedUris) !== -1) {
                            $(ROW_COLLAPSER, expandedRow).addClass(ACTIVE);
                        }
                    }
                } 

                if (result !== TABLE_NEEDS_RELOAD) {
                    // remove extra rows
                    for (i=(tableLength-1); i>=length; i--) {
                        dataTable.fnDeleteRow(i, undefined, false);
                        result = TABLE_CHANGED;
                    }
                }

                return result;
            }
            
            function calcTargetHeight() {
                var result;
                // use all height available in the container that isn't used by other
                // elements in the container
                result = container.height();
                // don't count table headers
                result -= $('thead', table).outerHeight(true);
                result -= $('.dataTables_scrollHead', container).outerHeight(true);
                $.each(container.children(), function (index, child) {
                    if ($(child).is(':visible') &&
                        ! $(child).hasClass('hp-index-table') &&
                        ! $(child).hasClass('dataTables_wrapper')) {
                        result -= $(child).outerHeight(true);
                    }
                });
                return result;
            }

            function layout() {
                updateScrollPosition();
                var oSettings = dataTable.fnSettings();
                var targetHeight = calcTargetHeight();
                oSettings.oScroll.sY = targetHeight;
                $(SCROLLER, container).css({'height': targetHeight});
                dataTable.fnAdjustColumnSizing(false);
            }

            function doScrollAndSetScrollRow(row) {
                scrollUri = undefined;  
                                  
                var scroller = $(SCROLLER, container);
                var current = scroller.scrollTop();
                var rowTop = row.position().top - scroller.position().top;
                var target = current + rowTop;
                scroller.scrollTop(target); 
               
            }
            
            function scrollToUriAndExpand(uri) {
                var row = getRowByUri(uri);  
                if (row && row.length > 0) {
                    expandRow(row);  
                    doScrollAndSetScrollRow(row);
                    // since URI was supplied , disable manual scroll till we get details
                    manualScrollEnabled = false;
                    
                    // remove the previous scroll row in case we re-enter index table where there was a previous
                    // scroll row set
                    
                    $(SCROLL_ROW, table).removeClass(SCROLL_FIRST);
                    var scrollRow = firstScrolledRow();
                    scrollRow.addClass(SCROLL_FIRST);
                } else {
                    // in case the row hasn't arrived yet, remember so we'll scroll later
                    scrollUri = uri;
                } 
            }
            
            function scrollToUri(uri) {
                var row = getRowByUri(uri);
                doScrollAndSetScrollRow(row);
            }
            
            function scrollAfterNewDetailsAdded(uri) {
            
                // check to see whether the maunalScrolling is disabled at the moment and 
                // the sourceUri is in fact the current  uri
                if (!manualScrollEnabled) {                            
                    scrollToUri(uri);
                    // the first details information is rendered we can allow manual scroll
                    manualScrollEnabled = true;
                }
            
            }

            // Presenter events

            /**
             * @private
             * @param {Object} data the IndexResults
             */
            function onIndexResultsChange(indexResults) {
                var reAddControls = false;
                var updateTable = TABLE_NO_CHANGE;
                
                if (indexResults) {
                    var expandedDetails = {};
                    var expandedUris;
                    var scrollRowUri;
                    
                    //$(TABLE_WRAPPER, page).removeClass('hp-empty');
                    if (0 === indexResults.total) {
                      
                        var oSettings = dataTable.fnSettings();
                        oSettings.oLanguage.sEmptyTable = presenter.getEmptyMessage(indexResults);
                        dataTable.fnClearTable(true);
                        
                    } else {
                    
                        // remember which rows were expanded
                        expandedUris = $(ROW + '.' + EXPANDED, table).map(
                            function (index, row) {
                                var uri = dataTable.fnGetData(row).uri;
                                expandedDetails[uri] =
                                    $('.hp-row-details-cell', $(row).next()).children();
                                return uri;
                            }).get();
                        
                        // remember where we were scrolled to
                        scrollRowUri = rememberScroll();
                    
                        if (indexResults.refreshed) {
                            updateTable = refreshTable(indexResults, expandedUris);
                            if (updateTable === TABLE_CHANGED ) { // table was updated
                                reAddControls = true;
                            }
                        } else {
                            updateTable = TABLE_NEEDS_RELOAD;
                            reAddControls = true;
                        }

                        if (updateTable === TABLE_NEEDS_RELOAD) {  // need to reload the whole table
                            // detach expanded rows
                            var length = expandedUris.length;
                            for (var i=0; i<length; i++) {
                                expandedDetails[expandedUris[i]].detach();
                            }
                        
                            dataTable.fnClearTable(false);
                            dataTable.fnAddData(indexResults.members, false);
                            reAddControls = true;
                        }

                        // if we added/updated/deleted anything, redraw
                        if (updateTable !== TABLE_NO_CHANGE) {
                            dataTable.fnDraw(false);
                        }
                        
                        if (reAddControls) {
                            addControls();
                        }
                        
                        if (updateTable === TABLE_NEEDS_RELOAD) {
                            
                            // have to do this after redrawing or expansion is lost
                            $(ROW, table).each(function (index, row) {
                                var indexResult = dataTable.fnGetData(row);
                                if (indexResult &&
                                    ($.inArray(indexResult.uri, expandedUris) !== -1)) {
                                    expandRow($(row), false, expandedDetails[indexResult.uri]);
                                }
                            });
                            
                        }
                    
                        restoreScroll(scrollRowUri);

                        // see if we were waiting for data to scroll
                        if (scrollUri) {                        
                            scrollToUriAndExpand(scrollUri);
                        }
                    }
                    
                    table.trigger('relayout');
                }
            }

            function onIndexResultsError(errorInfo) {
                // TBD
            }

            // DOM Events

            /**
             * @private
             * Called when a row is clicked on in the table
             * @param {Event} event The javascript click event
             */
            function onRowClick(event) {
                // get index result from object and call presenter
                var row = $(event.currentTarget);
                // since a user started interacting with the page, disable manual scrolling
                manualScrollEnabled = true;
                if (row.hasClass('hp-row-details-row')) {
                    // click in details row, treat as selection of main row
                    row = row.prev();
                } else if (EXPAND_ROW_ON_CLICK) {
                    if (detailsRenderer) {
                        toggleExpansion(row, true); //user click is true
                    }
                }
            }

            function onShowMoreClick() {
                $(this).replaceWith('…');
                presenter.showMore();
            }
            
            function onScroll(event) {
                if (scrollRowOffset >= 0) {
                    // stop auto scrolling if the user scrolls
                    manualScrollTo = scroller.scrollTop();
                    $(SCROLL_ROW, table).removeClass(SCROLL_FIRST);
                    // if at top, turn off manual scroll
                    if (manualScrollTo === 0) {
                        manualScrollTo = -1;
                    } else {
                        var row = firstScrolledRow();
                        row.addClass(SCROLL_FIRST);
                    }
                    setScrollReference();
                }
            }

            function onResize() {
                clearTimeout(layoutTimer);
                layoutTimer = setTimeout(layout, 50);
            }

            // Initialization

            /**
             * @private
             * Initialize the master table and attach event handlers from the presenter
             * @param {object} optionsArg Any options to pass to jQuery.datatables.
             */
            function dataTableInit(optionsArg) {
                var options = {
                    "bPaginate" : false,
                    "bFilter" : false,
                    "bInfo" : false,
                    "bDeferRender": true,
                    "sScrollY" : calcTargetHeight(),
                    //"sScrollX" : '100%',
                    "bAutoWidth": false,
                    "bSort": false,
                    "aaData" : []
                };
                $.extend(options, optionsArg);

                var oLanguage = $.extend(true, {}, localizer.getString('core.dataTables.oLanguage'));
                if (oLanguage) {
                    options.oLanguage = oLanguage;
                    oLanguage.sEmptyTable = presenter.getEmptyMessage();
                }

                // Initialize dataTable
                dataTable = table.dataTable(options);
                
                scroller = $(SCROLLER, container);
                scroller.scroll(onScroll);

                table.on('click', 'tbody tr', onRowClick);
                table.on('click', '.hp-index-show-more', onShowMoreClick);

                if (detailsRenderer && ! EXPAND_ROW_ON_CLICK) {
                    table.on('click', 'tbody tr td .hp-collapser',
                        function (ev) {
                            toggleExpansion($(this).parents('tr'), true); //user click is true
                        });
                }
            }

            function unregisterListeners() {
                presenter.off("indexResultsChange", onIndexResultsChange);
                presenter.off("indexResultsError", onIndexResultsError);
                $(window).off('resize', onResize);
                $('.hp-page').off('relayout', layout);
            }

            function registerListeners() {
                presenter.on("indexResultsChange", onIndexResultsChange);
                presenter.on("indexResultsError", onIndexResultsError);
                $(window).on('resize', onResize);
                $('.hp-page').on('relayout', layout);
            }

            // Public

            this.pause = function () {
                unregisterListeners();                
            };

            this.resume = function () {
                scrollRowOffset = -2; 
                // by default enable manual scrolling
                manualScrollEnabled = true;
                registerListeners();
            };

            this.init = function (presenterArg, args) {
                presenter = presenterArg;
                resource = args.resource;
                container = args.container;
                table = $(TABLE, container);
                detailsRenderer = args.detailsRenderer;
                detailsCollapsed = args.detailsCollapsed;

                dataTableInit(args.dataTableOptions);
                // by default enable manual scrolling
                manualScrollEnabled = true; 

                registerListeners();
            };

            this.scrollToUriAndExpand = scrollToUriAndExpand;
        }

        return IndexTableView;

    }());

    return IndexTableView;
});
