/*
 * Navigation helper for the table widget.
 * Implements the keyboard navigation within the current page
 * and pagination.
 */
+function ($) { "use strict";

    // NAMESPACE CHECK
    // ============================

    if ($.wn.table === undefined)
        throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");

    if ($.wn.table.helper === undefined)
        $.wn.table.helper = {}

    // NAVIGATION CLASS DEFINITION
    // ============================

    var Navigation = function(tableObj) {
        // Reference to the table object
        this.tableObj = tableObj

        // The current page index
        this.pageIndex = 0

        // Event handlers

        // Number of pages in the pagination
        this.pageCount = 0

        this.init()
    };

    Navigation.prototype.init = function() {
    }

    Navigation.prototype.dispose = function() {
        // Remove the reference to the table object
        this.tableObj = null
    }

    // PAGINATION
    // ============================

    Navigation.prototype.paginationEnabled = function() {
        return this.tableObj.options.recordsPerPage != null &&
            this.tableObj.options.recordsPerPage != false
    }

    Navigation.prototype.getPageFirstRowOffset = function() {
        return this.pageIndex * this.tableObj.options.recordsPerPage
    }

    Navigation.prototype.buildPagination = function(recordCount) {
        if (!this.paginationEnabled())
            return

        var paginationContainer = this.tableObj.getElement().querySelector('.pagination'),
            newPaginationContainer = false,
            curRecordCount = 0

        this.pageCount = this.calculatePageCount(recordCount, this.tableObj.options.recordsPerPage)

        if (!paginationContainer) {
            paginationContainer = document.createElement('div')
            paginationContainer.setAttribute('class', 'pagination')
            newPaginationContainer = true
        }
        else {
            curRecordCount = this.getRecordCount(paginationContainer)
        }

        // Generate the new page list only if the record count has changed
        if (newPaginationContainer || curRecordCount != recordCount) {
            paginationContainer.setAttribute('data-record-count', recordCount)

            var pageList = this.buildPaginationLinkList(
                recordCount,
                this.tableObj.options.recordsPerPage,
                this.pageIndex
            )

            if (!newPaginationContainer) {
                paginationContainer.replaceChild(pageList, paginationContainer.children[0])
            }
            else {
                paginationContainer.appendChild(pageList)
                this.tableObj.getElement().appendChild(paginationContainer)
            }
        }
        else {
            // Do not re-generate the pages if the record count hasn't changed,
            // but mark the new active item in the pagination list
            this.markActiveLinkItem(paginationContainer, this.pageIndex)
        }
    }

    Navigation.prototype.calculatePageCount = function(recordCount, recordsPerPage) {
        var pageCount = Math.ceil(recordCount/recordsPerPage)

        if (!pageCount)
            pageCount = 1

        return pageCount
    }

    Navigation.prototype.getRecordCount = function(paginationContainer) {
        var container = paginationContainer ? paginationContainer : this.tableObj.getElement().querySelector('.pagination')

        return parseInt(container.getAttribute('data-record-count'))
    }

    Navigation.prototype.buildPaginationLinkList = function(recordCount, recordsPerPage, pageIndex) {
        // This method could be refactored and moved to a pagination
        // helper if we want to support other pagination markup options.

        var pageCount = this.calculatePageCount(recordCount, recordsPerPage),
            pageList = document.createElement('ul')

        for (var i=0; i < pageCount; i++) {
            var item = document.createElement('li'),
                link = document.createElement('a')

            if (i == pageIndex)
                item.setAttribute('class', 'active')


            link.innerText = i+1
            link.setAttribute('data-page-index', i)
            link.setAttribute('href', '#')

            item.appendChild(link)
            pageList.appendChild(item)

            $(link).addClass('pagination-link')
        }

        return pageList
    }

    Navigation.prototype.markActiveLinkItem = function(paginationContainer, pageIndex) {
        // This method could be refactored and moved to a pagination
        // helper if we want to support other pagination markup options.

        var activeItem = paginationContainer.querySelector('.active'),
            list = paginationContainer.children[0]

        activeItem.setAttribute('class', '')

        for (var i=0, len = list.children.length; i < len; i++) {
            if (i == pageIndex) {
                list.children[i].setAttribute('class', 'active')
            }
        }
    }

    Navigation.prototype.gotoPage = function(pageIndex, onSuccess) {
        this.tableObj.unfocusTable()

        if (!this.tableObj.validate())
            return

        this.pageIndex = pageIndex

        this.tableObj.updateDataTable(onSuccess)
    }

    Navigation.prototype.getRowCountOnPage = function(cellElement) {
        return this.tableObj.getDataTableBody().children.length
    }

    Navigation.prototype.getNewRowPage = function(placement, currentRowIndex) {
        var curRecordCount = this.getRecordCount()

        if (placement === 'bottom')
            return this.calculatePageCount(curRecordCount + 1, this.tableObj.options.recordsPerPage) - 1

        // When a row is added above a current row, the current row just moves down,
        // so it's safe to return the current page index
        if (placement == 'above')
            return this.pageIndex

        if (placement == 'below') {
            if (currentRowIndex == (this.tableObj.options.recordsPerPage - 1))
                return this.pageIndex + 1

            return this.pageIndex
        }

        return this.pageIndex
    }

    Navigation.prototype.getPageAfterDeletion = function(currentRowIndex) {
        if (currentRowIndex == 0 && this.getRowCountOnPage() == 1)
            return this.pageIndex == 0 ? 0 : this.pageIndex - 1

        return this.pageIndex
    }

    // KEYBOARD NAVIGATION
    // ============================

    Navigation.prototype.navigateDown = function(ev, forceCellIndex) {
        if (!this.tableObj.activeCell)
            return

        if (this.tableObj.activeCellProcessor && !this.tableObj.activeCellProcessor.keyNavigationAllowed(ev, 'down'))
            return

        var row = this.tableObj.activeCell.parentNode,
            newRow = !ev.shiftKey
                ? row.nextElementSibling
                : row.parentNode.children[row.parentNode.children.length - 1],
            cellIndex = forceCellIndex !== undefined
                ? forceCellIndex
                : this.tableObj.activeCell.cellIndex

        if (newRow) {
            var cell = newRow.children[cellIndex]

            if (cell)
                this.tableObj.focusCell(cell)
        }
        else {
            // Try to switch to the next page if that's possible

            if (!this.paginationEnabled())
                return

            if (this.pageIndex < this.pageCount - 1) {
                var self = this

                this.gotoPage(this.pageIndex + 1, function navDownPageSuccess() {
                    self.focusCell('top', cellIndex)
                    self = null
                })
            }
        }
    }

    Navigation.prototype.navigateUp = function(ev, forceCellIndex, isTab) {
        if (!this.tableObj.activeCell)
            return

        if (this.tableObj.activeCellProcessor && !this.tableObj.activeCellProcessor.keyNavigationAllowed(ev, 'up'))
            return

        var row = this.tableObj.activeCell.parentNode,
            newRow = (!ev.shiftKey || isTab)
                ? row.previousElementSibling
                : row.parentNode.children[0],
            cellIndex = forceCellIndex !== undefined
                ? forceCellIndex
                : this.tableObj.activeCell.cellIndex

        if (newRow) {
            var cell = newRow.children[cellIndex]

            if (cell)
                this.tableObj.focusCell(cell)
        }
        else {
            // Try to switch to the previous page if that's possible

            if (!this.paginationEnabled())
                return

            if (this.pageIndex > 0) {
                var self = this

                this.gotoPage(this.pageIndex - 1, function navUpPageSuccess(){
                    self.focusCell('bottom', cellIndex)
                    self = null
                })
            }
        }
    }

    Navigation.prototype.navigateLeft = function(ev, isTab) {
        if (!this.tableObj.activeCell)
            return

        if (!isTab && this.tableObj.activeCellProcessor && !this.tableObj.activeCellProcessor.keyNavigationAllowed(ev, 'left'))
            return

        var row = this.tableObj.activeCell.parentNode,
            newIndex = (!ev.shiftKey || isTab)
                ? this.tableObj.activeCell.cellIndex - 1
                : 0

        var cell = row.children[newIndex]

        if (cell) {
            this.tableObj.focusCell(cell)
        }
        else {
            // Try to navigate up if that's possible
            this.navigateUp(ev, row.children.length - 1, isTab)
        }
    }

    Navigation.prototype.navigateRight = function(ev, isTab) {
        if (!this.tableObj.activeCell)
            return

        if (!isTab && this.tableObj.activeCellProcessor && !this.tableObj.activeCellProcessor.keyNavigationAllowed(ev, 'right'))
            return

        var row = this.tableObj.activeCell.parentNode,
            newIndex = !ev.shiftKey
                ? this.tableObj.activeCell.cellIndex + 1
                : row.children.length - 1

        var cell = row.children[newIndex]

        if (cell) {
            this.tableObj.focusCell(cell)
        }
        else {
            // Try to navigate down if that's possible
            this.navigateDown(ev, 0)
        }
    }

    Navigation.prototype.navigateNext = function(ev) {
        if (!this.tableObj.activeCell)
            return

        if (this.tableObj.activeCellProcessor && !this.tableObj.activeCellProcessor.keyNavigationAllowed(ev, 'tab'))
            return

        if (!ev.shiftKey)
            this.navigateRight(ev, true)
        else
            this.navigateLeft(ev, true)

        this.tableObj.stopEvent(ev)
    }

    Navigation.prototype.focusCell = function(rowReference, cellIndex) {
        var row = null,
            tbody = this.tableObj.getDataTableBody()

        if (typeof rowReference === 'object') {
            row = rowReference
        }
        else {
            if (rowReference == 'bottom') {
                row = tbody.children[tbody.children.length-1]
            }
            else if (rowReference == 'top') {
                row = tbody.children[0]
            }
        }

        if (!row)
            return

        var cell = row.children[cellIndex]
        if (cell)
            this.tableObj.focusCell(cell)
    }

    Navigation.prototype.focusCellInReplacedRow = function(rowIndex, cellIndex) {
        if (rowIndex == 0) {
           this.focusCell('top', cellIndex)
        }
        else {
            var focusRow = this.tableObj.findRowByIndex(rowIndex)

            if (!focusRow)
                focusRow = this.tableObj.findRowByIndex(rowIndex-1)

            if (focusRow)
                this.focusCell(focusRow, cellIndex)
            else
                this.focusCell('top', cellIndex)
        }
    }

    // EVENT HANDLERS
    // ============================

    Navigation.prototype.onKeydown = function(ev) {
        // The navigation object uses the table's keydown handler
        // and doesn't register own handler.

        if (ev.key === 'ArrowDown')
            return this.navigateDown(ev)
        else if (ev.key === 'ArrowUp')
            return this.navigateUp(ev)
        else if (ev.key === 'ArrowLeft')
            return this.navigateLeft(ev)

        if (ev.key === 'ArrowRight')
            return this.navigateRight(ev)

        if (ev.key === 'Tab')
            return this.navigateNext(ev)
    }

    Navigation.prototype.onClick = function(ev) {
        // The navigation object uses the table's click handler
        // and doesn't register own click handler.

        var target = this.tableObj.getEventTarget(ev, 'A')

        if (!target || !$(target).hasClass('pagination-link'))
            return

        var pageIndex = parseInt(target.getAttribute('data-page-index'))

        if (pageIndex === null)
            return

        this.gotoPage(pageIndex)
        this.tableObj.stopEvent(ev)

        return false
    }

    $.wn.table.helper.navigation = Navigation;

}(window.jQuery);
