// make sure jquery is defined
if (typeof('$') == 'undefined') throw new Error('JQuery must be included properly in order for the application to work.');

/**
 * Class-like function to create an interface for the site.
 *
 * @author Peter Kruithof
 */
function BookInterface(hrefprefix)
{
    this.urlExtension = '.json';
    this.dataProvider = new DataProvider();
    this.currentHrefPrefix = hrefprefix || '';
    this.sheet;
    this.leftPage;
    this.rightPage;
    this.leftPageHandler;
    this.rightPageHandler;
    this.scrollContentUpHandler;
    this.scrollContentDownHandler;
    this.chapterCoverHTML = '';
    this.chapterHTML = '';

    /**
     * Loads content from an url. {@link BookInterface.update} is called after contents are loaded.
     */
    this.loadContents = function(uri, type)
    {
        if (uri && (uri != '')) {

            // strip trailing slash
            uri = uri.replace(/\/$/, '');

            this.currentHrefPrefix = uri;

            // fetch the uri
            this.dataProvider.fetch(uri + this.urlExtension + '?direction=' + type);
        }
    }

    /**
     *
     */
    this.clickHandler = function(e)
    {
        this.cancelEvent(e);

        var target = $(getTarget(e));
        if (!target.is('a')) {
            // find nearest anchor
            target = target.parents('a');
        }
        this.loadContents(target.attr('href'), '');
    }

    /**
     * Turns the left page by loading contents of the previous url
     */
    this.turnLeftPage = function(e)
    {
        this.cancelEvent(e);

        this.loadContents(this.leftPageHandler.attr('href'), 'back');
    }

    /**
     * Turns the right page by loading contents of the next url
     */
    this.turnRightPage = function(e)
    {
        this.cancelEvent(e);

        this.loadContents(this.rightPageHandler.attr('href'), 'forward');
    }

    /**
     * Event handler invoked when scrolling mouse wheel
     */
    this.onMouseWheel = function(e)
    {
        if (!e) e = window.event;

        if (e) {

            // see if scrolled area is in fact the page content, if not, return now
            var target = $(getTarget(e));
            if ((target.attr('id') !== 'page-content') && !(target.parents('#page-content').size() > 0)) return;

        }

        if (e.wheelDelta) { // IE/Opera

            delta = e.wheelDelta / 120;

            // In Opera 9, delta differs in sign as compared to IE
            if (window.opera) delta = -delta;

        } else if (e.detail) { // Mozilla

            // In Mozilla, sign of delta is different than in IE. Also, delta is multiple of 3.
            delta = -e.detail / 3;
        }

        /**
         * If delta is nonzero, handle it.
         * Basically, delta is now positive if wheel was scrolled up,
         * and negative, if wheel was scrolled down.
         */
        if (delta < 0) {
            this.scrollContentDown();
        } else if (delta > 0) {
            this.scrollContentUp();
        }

        if (e) cancelEvent(e);
    }

    /**
     * Start to scroll up
     */
    this.startScrollUp = function(e)
    {
        this.cancelEvent(e);

        scrollUp = setInterval(this.scrollContentUp, 50);
    }

    /**
     * Stop scrolling up
     */
    this.stopScrollUp = function(e)
    {
        this.cancelEvent(e);

        scrollUp = clearInterval(scrollUp);
    }

    /**
     * Scrolls the page up and checks if it should go further
     */
    this.scrollContentUp = function(e)
    {
        // get the position of the content
        var position = $('#page-content').css('top');
        var numericPosition = parseInt(position.substr(0, (position.length-2)));

        // change the position of the content if it's not at the end yet
        if ((numericPosition + 20) <= 0) {
            var newPosition = numericPosition + 20;
            $('#page-content').css('top', newPosition + 'px');
        }
    }

    /**
     * Start to scroll down
     */
    this.startScrollDown = function(e)
    {
        this.cancelEvent(e);

        scrollDown = setInterval(this.scrollContentDown, 50);
    }

    /**
     * Stop scrolling down
     */
    this.stopScrollDown = function(e)
    {
        this.cancelEvent(e);

        scrollDown = clearInterval(scrollDown);
    }

    /**
     * Scrolls the page down and checks if it should go further
     */
    this.scrollContentDown = function(e)
    {
        // get the height and position of the content
        var height = $('#page-content').height();
        var position = $('#page-content').css('top');
        var numericPosition = parseInt(position.substr(0, (position.length-2)));

        // change the position of the content if it's not at the end yet
        if ((numericPosition-40) > -height) {
            var newPosition = numericPosition - 20;
            $('#page-content').css('top', newPosition+'px');
        }
    }

    /**
     * Opens chapter by hiding the chapter cover
     */
    this.openChapter = function(e)
    {
        this.cancelEvent(e);

        this.leftPage.html(this.chapterHTML);
        this.sheet.addClass('nodisplay');

        // re-bind page handler
        this.rightPageHandler.unbind('click');
        this.rightPageHandler.bind('click', Delegate.create(this, this.turnRightPage));
    }

    /**
     * Closes chapter by showing the chapter cover
     */
    this.closeChapter = function(e)
    {
        this.cancelEvent(e);

        this.leftPage.html(this.chapterCoverHTML);
        this.sheet.removeClass('nodisplay');

        // re-bind page handler
        this.leftPageHandler.unbind('click');
        this.leftPageHandler.bind('click', Delegate.create(this, this.turnLeftPage));

        // clicking the right handle opens the chapter
        this.rightPageHandler.unbind('click');
        this.rightPageHandler.bind('click', Delegate.create(this, this.openChapter));
    }

    /**
     * Executes an update from the dataprovider. It processes the dataprovider's current section,
     * and puts the contents in dialog windows. If the fetched content is a subpage, it loads
     * that content into the existing dialog window.
     */
    this.update = function()
    {
        var section = this.dataProvider.getSection(this.currentHrefPrefix);

        if (section) {

            var lefthtml = '';
            var righthtml = '';

            document.title = section.title;

            // set containertype as classname
            this.book.removeClass('no-overflow');

            if (section.container_type == 'menu') {

                this.book.removeClass('content-page');
                // see which type of menu
                var urlparts = this.dataProvider.getUrlPartsFromPath(this.currentHrefPrefix);

                righthtml = '<h1 class="pagetitle"><span>' + section.title + '</span></h1>';
                var menu = '';
                if ('children' in section) {
                    for (var a in section.children) {
                        menu += '<dt><a href="' + section.children[a].url + '" title="' + section.children[a].title + '">' + section.children[a].menutitle + '</a></dt>';
                        menu += '<dd><div>' + section.children[a].description + '</div></dd>';
                    }
                }

                lefthtml = '';
                righthtml += '<dl class="dossier">' + menu + '</dl>';


                // 1 urlpart means we're opening a new chapter, don't display the contents just yet, but display a sheet
                if (urlparts.length == 1) {

                    // overwrite with chapter cover
                    lefthtml = '<h1 class="chaptertitle"><span>' + section.title + '</span></h1>';

                    if (section.visual) {
                        lefthtml += section.body;
                        lefthtml += '<div class="chaptervisual"><img src="' + section.visual.uri + '" alt="' + section.visual.description + '" title="' + section.visual.title + '" width="' + section.visual.width + '" height="' + section.visual.height + '"/></div>';
                    }

                    var menu = '';
                    if ('children' in section) {
                        for (var a in section.children) {
                            menu += '<dt>' + section.children[a].menutitle + '</dt>';

                            if ('children' in section.children[a]) {
                                for (var b in section.children[a].children) {
                                    var link = section.children[a].children[b];
                                    menu += '<dd><div><a href="' + link.url + '" title="' + link.title + '">' + link.menutitle + '</a><p>' + (link.description || '') + '</p></div></dd>';
                                }
                            }
                        }
                    }

                    righthtml = '<dl class="dossier">' + menu + '</dl>';

                    this.sheet.attr('id', 'sheet' + section.index);
                    this.sheet.html(righthtml);
                    this.sheet.removeClass('nodisplay');


                } else {

                    // clear the sheet
                    this.sheet.addClass('nodisplay');

                }

            } else if (section.container_type == 'page') {

                this.book.addClass('content-page');
                var index = '';
                if ('children' in section) {

                    index = '<div class="subpages">';
                    index += '<h2 class="pngbg">' + getText('Chapters') + '</h2>';
                    index += '<ul>';

                    for (var a in section.children) {
                        index += '<li><a href="' + this.currentHrefPrefix + '/' + a + '/' + '" title="' + section.children[a].title + '"><span>' + section.children[a].menutitle + '</span></a></li>';
                    }

                    index += '</ul>';
                    index += '</div>';

                }

                righthtml = '<h1 class="pagetitle"><span>' + section.title + '</span></h1><div id="page-content-block"><div id="page-content">' + (section['body'] + index + '</div></div>'|| '');

                // right page
                if ('visual' in section) {
                    lefthtml += '<img class="pagevisual" src="' + section.visual.uri + '" alt="' + section.visual.description + '" title="' + section.visual.title + '"/>';
                    lefthtml += '<h4><span>' + section.visual.title + '</span></h4>';
                    lefthtml += '<p>' + section.visual.description + '</p>';
                }

                // clear the sheet
                this.sheet.addClass('nodisplay');
                this.book.addClass('no-overflow');

            } else if (section.container_type == 'list') {

                this.book.removeClass('content-page');
                lefthtml = '<h1 class="pagetitle"><span>' + section.title + '</span></h1><div id="page-content-block"><div id="page-content">' + (section['body'] + '</div></div>'|| '');

                if ('children' in section) {

                    lefthtml += '<div class="photolist">';

                    for (var a in section.children) {
                        if ('thumbnail' in section.children[a]) {

                            var href = this.currentHrefPrefix + '/' + a + '/';

                            lefthtml += '<div class="photo">';
                            lefthtml += '<div class="holder"><a href="' + href + '"><img src="' + section.children[a].thumbnail.uri + '" alt="' + section.children[a].thumbnail.description + '" title="' + section.children[a].thumbnail.title + '"/></a></div>';
                            lefthtml += '<p><a href="' + href + '">' + section.children[a].thumbnail.title + '</a></p>';
                            lefthtml += '</div>';
                        }
                    }

                    lefthtml += '</div>';
                }

            } else if (section.container_type == 'visual') {

                this.book.removeClass('content-page');
                // keep left html the same
                delete lefthtml;

                righthtml = '<div class="photo"><img src="' + section.visual.uri + '" alt="' + section.visual.description + '" title="' + section.visual.title + '"/></div>';

            } else if (section.container_type == 'subpage') {

                if (section.parent_view == 'paired_subpages') {
                    this.book.addClass('content-subpage');
                    lefthtml = '<h1 class="pagetitle"><span>' + section.title + '</span></h1>';

                    // right page
                    if ('visual' in section) {
                        lefthtml += '<img class="pagevisual" src="' + section.visual.uri + '" alt="' + section.visual.description + '" title="' + section.visual.title + '"/>';
                        lefthtml += '<h4><span>' + section.visual.title + '</span></h4>';
                        lefthtml += '<p>' + section.visual.description + '</p>';
                    }

                    if ('next_subpage' in section) {
                        righthtml = '<h2 class="pagetitle"><span>' + section.next_subpage.title + '</span></h2>';

                        // right page
                        if ('visual' in section.next_subpage) {
                            righthtml += '<img class="pagevisual" src="' + section.next_subpage.visual.uri + '" alt="' + section.next_subpage.visual.description + '" title="' + section.next_subpage.visual.title + '"/>';
                            righthtml += '<h4><span>' + section.next_subpage.visual.title + '</span></h4>';
                            righthtml += '<p>' + section.next_subpage.visual.description + '</p>';
                        }
                    }

                    // clear the sheet
                    this.sheet.addClass('nodisplay');
                    this.book.addClass('no-overflow');
                } else {
                    this.book.addClass('content-page');
                    righthtml = '<h1 class="pagetitle"><span>' + section.title + '</span></h1><div id="page-content-block"><div id="page-content">' + (section['body'] + '</div></div>'|| '');

                    // right page
                    if ('visual' in section) {
                        lefthtml += '<img class="pagevisual" src="' + section.visual.uri + '" alt="' + section.visual.description + '" title="' + section.visual.title + '"/>';
                        lefthtml += '<h4><span>' + section.visual.title + '</span></h4>';
                        lefthtml += '<p>' + section.visual.description + '</p>';
                    }

                    // clear the sheet
                    this.sheet.addClass('nodisplay');
                    this.book.addClass('no-overflow');
                }

            } else {

                this.book.removeClass('content-page');
                lefthtml = '<h1 class="pagetitle"><span>' + section.title + '</span></h1><div id="page-content-block"><div id="page-content">' + (section['body'] + '</div></div>'|| '');

                // clear the sheet
                this.sheet.addClass('nodisplay');
            }

            // set right classname
            this.book.addClass('chapter' + section.index);

            this.leftPage.html(lefthtml);
            this.rightPage.html(righthtml);

            // set title background
            if (section.title_background) {
                this.leftPage.find('h1').css({'height' : section.title_background_height + 'px', 'background-image' : 'url(' + section.title_background + ')'});
                this.rightPage.find('h1').css({'height' : section.title_background_height + 'px', 'background-image' : 'url(' + section.title_background + ')'});
            }

            // set title background
            if ('next_subpage' in section) {
                if (section.next_subpage.title_background) {
                    this.rightPage.find('h2').css({'height' : section.next_subpage.title_background_height + 'px', 'background-image' : 'url(' + section.next_subpage.title_background + ')'});
                }
            }

            if (section.container_type == 'list') {
                this.leftPage.find('a').bind('click', Delegate.create(this, this.clickHandler));
            }

            // update the page turners
            this.leftPageHandler.attr('href', (section.previous_page || ''));
            this.rightPageHandler.attr('href', (section.next_page || ''));
        }
    }

    /**
     * Initializes the caseinterface:
     * - sets up the accordion menu
     * - binds click handlers to navigational elements
     * - adds event listener for the dataprovider
     */
    this.init = function()
    {
        this.book = $('#book');
        this.sheet = $('.sheet');
        this.leftPage = $('.page.left');
        this.rightPage = $('.page.right');

        // get the page handlers
        var handler = $('.page-turn-left');
        if (handler.size() > 0) {
            if (handler.find('a').size() == 0) {

                // add the link manually
                handler.html('<a title="' + getText('PreviousPage') + '" href="/" rel="prev"><span>' + getText('PreviousPage') + '</span></a>');
            }
        }

        handler = $('.page-turn-right');
        if (handler.size() > 0) {
            if (handler.find('a').size() == 0) {

                // add the link manually
                handler.html('<a title="' + getText('NextPage') + '" href="/" rel="next"><span>' + getText('NextPage') + '</span></a>');
            }
        }

        this.leftPageHandler = $('.page-turn-left a');
        this.rightPageHandler = $('.page-turn-right a');

        if ($.browser.msie && (Number($.browser.version) < 7)) {

            // place another link as handler on top of this one
            // we have to do this because the anchor's background is a png file
            // thus making the clickable region disappear

            var leftimg = /url\("?(.*)"?\).*/.exec(this.leftPageHandler.parent().css('background-image'));
            var rightimg = /url\("?(.*)"?\).*/.exec(this.rightPageHandler.parent().css('background-image'));

            this.leftPageHandler.before('<img src="' + leftimg[1] + '" class="pngbg ie-page-turn"/>');
            this.rightPageHandler.before('<img src="' + rightimg[1] + '" class="pngbg ie-page-turn"/>');

            this.leftPageHandler.parent().css('background-image', 'none');
            this.rightPageHandler.parent().css('background-image', 'none');
        }

        this.leftPageHandler.bind('click', Delegate.create(this, this.turnLeftPage));
        this.rightPageHandler.bind('click', Delegate.create(this, this.turnRightPage));

        // set the scroller-html
        this.rightPage.after('<a id="scroll-up"><span>'+getText('ScrollUp')+'</span></a>');
        this.rightPage.after('<a id="scroll-down"><span>'+getText('ScrollDown')+'</span></a>');
        this.rightPage.after('<a class="order-book" href="/bestel_het_boek/"><span>'+getText('OrderBook')+'</span></a>');

        this.scrollContentUpHandler = $('#scroll-up');
        this.scrollContentDownHandler = $('#scroll-down');

        this.scrollContentUpHandler.bind('mousedown', Delegate.create(this, this.startScrollUp));
        this.scrollContentUpHandler.bind('mouseup', Delegate.create(this, this.stopScrollUp));
        this.scrollContentDownHandler.bind('mousedown', Delegate.create(this, this.startScrollDown));
        this.scrollContentDownHandler.bind('mouseup', Delegate.create(this, this.stopScrollDown));


        /*
        if (window.addEventListener) {
            window.addEventListener('DOMMouseScroll', Delegate.create(this, this.onMouseWheel), false);
        } else {
            window.onmousewheel = document.onmousewheel = Delegate.create(this, this.onMouseWheel);
        }
        */

        // listen to updates
        this.dataProvider.addEventListener('update', this.update, this);


        // load the contents
        if (this.currentHrefPrefix != '/') {
            this.loadContents(this.currentHrefPrefix, 'forward');
        }
    }

    this.cancelEvent = function(e)
    {
        if (e) {
            if ('preventDefault' in e) e.preventDefault();
            if ('stopPropagation' in e) e.stopPropagation();
        }
    }
}

/**
 * Dataprovider class. Handles the fetching of contents by loading url's.
 *
 * @author Peter Kruithof
 */
function DataProvider()
{
    // constants for the modal
    this.LOADING = 'loading';
    this.IDLE = 'idle';
    this.ERROR = 'err';

    this.lastResult = null;
    this.currentSection = null;
    this.state = null;
    this.loadingIcon = null;
    this.listeners = {};
    this.timeoutID = null;
    this.structure = {};

    this.getUrlPartsFromPath = function(path)
    {
        path = path.replace(/^\//, '');
        path = path.replace(/\/$/, '');
        return path.split('/');
    }

    this.setState = function(state)
    {
        this.state = state;
        this.dispatchEvent('state');
    }

    this.setTimeout = function(miliseconds)
    {
        this.clearTimeout();

        var self = this;
        this.timeoutID = setTimeout(function() { self.timeout(); }, miliseconds);
    }

    this.clearTimeout = function()
    {
        if (this.timeoutID) {
            clearTimeout(this.timeoutID);
        }
    }

    this.timeout = function()
    {
        this.setState(this.ERROR);
//        Modal.display(getText('TimeoutError'), 'error');
    }

    /**
     * Registers an event listener for this class.
     *
     * @param {String}      event   The event to listen to.
     * @param {Function}    func    The function to call when event is broadcasted.
     * @param {Object}      scope   The scope to preserve while applying the callback.
     */
    this.addEventListener = function(event, func, scope)
    {
        if (!this.listeners[event]) this.listeners[event] = [];

        for (var i = 0; i < this.listeners[event].length; i++) {
            if (this.listeners[event][i].callback === func) return;
        }
        this.listeners[event].push({'callback' : func, 'scope' : scope});
    }

    /**
     * Dispatches an event to all registered listeners.
     *
     * @param {String} event The event to dispatch
     */
    this.dispatchEvent = function(event)
    {
        if (this.listeners[event] && (this.listeners[event].length > 0)) {
            for (var i = 0; i < this.listeners[event].length; i++) {
                this.listeners[event][i].callback.call(this.listeners['update'][i].scope);
            }
        }
    }

    /**
     * Executes an XmlHttpRequest, and delegates the returned JSON result to {@link parseResult}
     */
    this.fetch = function(uri)
    {
        if (!uri) throw new Error(';DataProvider.fetch : argument \'uri\' missing or invalid');

        // set timer for possible timeout
        this.setTimeout(30000); // 30 seconds should be more than enough

        this.setState(this.LOADING);
        $.getJSON(uri, Delegate.create(this, this.parseResult));
    }

    /**
     * Event handler called from {@link fetch}.
     * Adds all new points and children and dispatches an update event.
     *
     * @param {Object} result The result object returned by the JSON request
     */
    this.parseResult = function(result)
    {
        this.clearTimeout();

        this.setState(this.IDLE);

        // everything ok?
        if (result && !result.isError) {

            // store the result
            this.lastResult = result;

            // parse the result
            if (result.page_data) {

                // get urlparts
                var urlparts = this.getUrlPartsFromPath(result.page_data.path) || '';

                // update structure
                this.structure = this.updateStructure(this.structure, urlparts, result.page_data);
            }

            // let listeners know we've been updated
            this.dispatchEvent('update');

        } else {

            // TODO something went wrong, display something on screen?
            throw new Error('Invalid result returned.');
        }
    }

    this.updateStructure = function(structure, urlparts, page_data)
    {
        if (urlparts.length > 0) {
            var urlpart = urlparts.shift();

            if (!(urlpart in structure)) {
                structure[urlpart] = {};
            }

            if (urlparts.length > 0) {
                structure[urlpart] = this.updateStructure(structure[urlpart], urlparts, page_data);
            } else {
                structure[urlpart] = page_data;
            }
        }

        return structure;

    }

    this.getSection = function(path)
    {
        // get urlparts
        var urlparts = this.getUrlPartsFromPath(path);

        if (urlparts.length > 0) {
            var section = this.structure;
            for (var i = 0, j = urlparts.length; i < j; i++) {

                if (!(urlparts[i] in section)) return false;

                section = section[urlparts[i]];
            }

            return section;
        }

        return false;
    }
}

/**
 * Function for a clock :~
 */
function updateClock ( )
{
    var currentTime = new Date ( );

    var currentHours = currentTime.getHours ( );
    var currentMinutes = currentTime.getMinutes ( );
    var currentSeconds = currentTime.getSeconds ( );

    // Pad the minutes and seconds with leading zeros, if required
    currentHours = ( currentHours < 10 ? "0" : "" ) + currentHours;
    currentMinutes = ( currentMinutes < 10 ? "0" : "" ) + currentMinutes;

    // Compose the string for display
    var currentTimeString = currentHours + ":" + currentMinutes;

    var currentDay = currentTime.getDate ( );
    var currentMonth = currentTime.getMonth ( ) + 1;
    var currentYear = currentTime.getFullYear ( );

    // Pad the minutes and seconds with leading zeros, if required
    currentDay = ( currentDay < 10 ? "0" : "" ) + currentDay;
    currentMonth = ( currentMonth < 10 ? "0" : "" ) + currentMonth;

    // Compose the string for display
    var currentDateString = currentDay + "-" + currentMonth + "-" + currentYear;

    // Update the time display
    document.getElementById("clock").firstChild.nodeValue = currentTimeString + " - " + currentDateString;
}
