// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
define(['jquery'], function($){
    "use strict";

    var ScrollSelector = function(element, notebook) {
        // Public constructor.
        this.notebook = notebook;
        $('<span />')
            .addClass('nabar-text')
            .text('Scrolling Mode:')
            .appendTo(element);
        this._combo = $('<select />')
            .addClass('form-control select-xs')
            .appendTo(element);

        // Redirect class level manager registration to this instance.
        this._registered = {};
        ScrollSelector.register = $.proxy(this.register, this);

        // Register cached managers.
        for (var i =0; i < ScrollSelector.registered.length; i++) {
            this.register.apply(this, ScrollSelector.registered[i]);
        }

        // Listen for scroll manager change, apply it to the notebook.
        var that = this;
        this._combo.change(function(){
            var manager = that._registered[$(this).find("option:selected").val()];
            that.notebook.scrollmanager = manager;
        });
    };

    // Cache scroll managers registered before the construction of a scroll 
    // manager.
    ScrollSelector.registered = [];
    ScrollSelector.register = function(name, manager) {
        ScrollSelector.registered.push(arguments);
    };
    ScrollSelector.prototype.register = function(name, manager) {
        this._registered[name] = manager;
        this._combo.append($('<option />')
            .val(name)
            .text(name));
    };


    var ScrollManager = function(notebook) {
        // Public constructor.
        this.notebook = notebook;
        this.animation_speed = 250; //ms
    };

    ScrollManager.prototype.scroll = function (delta) {
        // Scroll the document.
        //
        // Parameters
        // ----------
        // delta: integer
        //  direction to scroll the document.  Positive is downwards.
        this.scroll_some(delta);
        return false;
    };

    ScrollManager.prototype.scroll_to = function(destination) {
        // Scroll to an element in the notebook.
        $('#notebook').animate({'scrollTop': $(destination).offset().top + $('#notebook').scrollTop() - $('#notebook').offset().top}, this.animation_speed);
    };

    ScrollManager.prototype.scroll_some = function(pages) {
        // Scroll up or down a given number of pages.
        $('#notebook').animate({'scrollTop': $('#notebook').scrollTop() + pages * $('#notebook').height()}, this.animation_speed);
    };

    ScrollManager.prototype.get_first_visible_cell = function() {
        // Gets the index of the first visible cell in the document.

        // First, attempt to be smart by guessing the index of the cell we are
        // scrolled to.  Then, walk from there up or down until the right cell 
        // is found.  To guess the index, get the top of the last cell, and
        // divide that by the number of cells to get an average cell height.  
        // Then divide the scroll height by the average cell height.
        var cell_count = this.notebook.ncells();
        var first_cell_top = this.notebook.get_cell(0).element.offset().top;
        var last_cell_top = this.notebook.get_cell(cell_count-1).element.offset().top;
        var avg_cell_height = (last_cell_top - first_cell_top) / cell_count;
        var notebook = $('#notebook');
        var i = Math.ceil(notebook.scrollTop() / avg_cell_height);
        i = Math.min(Math.max(i , 0), cell_count - 1);

        while (this.notebook.get_cell(i).element.offset().top - first_cell_top < notebook.scrollTop() && i < cell_count - 1) {
            i += 1;
        } 

        while (this.notebook.get_cell(i).element.offset().top - first_cell_top > notebook.scrollTop() - 50 && i >= 0) {
            i -= 1;
        } 
        return Math.min(i + 1, cell_count - 1);
    };


    var TargetScrollManager = function(notebook) {
        // Public constructor.
        ScrollManager.apply(this, [notebook]);
    };
    TargetScrollManager.prototype = new ScrollManager();

    TargetScrollManager.prototype.is_target = function (index) {
        return false;
    };

    TargetScrollManager.prototype.scroll = function (delta) {
        // Scroll the document.
        //
        // Parameters
        // ----------
        // delta: integer
        //  direction to scroll the document.  Positive is downwards.

        // Try to scroll to the next slide.
        var cell_count = this.notebook.ncells();
        var selected_index = this.get_first_visible_cell() + delta;
        while (0 <= selected_index && selected_index < cell_count && !this.is_target(selected_index)) {
            selected_index += delta;
        }

        if (selected_index < 0 || cell_count <= selected_index) {
            return ScrollManager.prototype.scroll.apply(this, [delta]);
        } else {
            this.scroll_to(this.notebook.get_cell(selected_index).element);
            
            // Cancel browser keyboard scroll.
            return false;
        }
    };


    var SlideScrollManager = function(notebook) {
        // Public constructor.
        TargetScrollManager.apply(this, [notebook]);
    };
    SlideScrollManager.prototype = new TargetScrollManager();

    SlideScrollManager.prototype.is_target = function (index) {
        var cell = this.notebook.get_cell(index);
        return cell.metadata && cell.metadata.slideshow && 
            cell.metadata.slideshow.slide_type && 
            cell.metadata.slideshow.slide_type === "slide";
    };


    var HeadingScrollManager = function(notebook, heading_level) {
        // Public constructor.
        TargetScrollManager.apply(this, [notebook]);
        this._level = heading_level;
    };
    HeadingScrollManager.prototype = new TargetScrollManager();

    HeadingScrollManager.prototype.is_target = function (index) {
        var cell = this.notebook.get_cell(index);
        return cell.cell_type === "heading" && cell.level == this._level;
    };


    // Return naemspace for require.js loads
    return {
        'ScrollSelector': ScrollSelector,
        'ScrollManager': ScrollManager,
        'SlideScrollManager': SlideScrollManager,
        'HeadingScrollManager': HeadingScrollManager,
        'TargetScrollManager': TargetScrollManager
    };
});