/**
 * Scope overrides copied from baseJS/prototype.
 * Useful in writing class-base JavaScript for keeping the parent Object as the scope.
 */
$.extend(Function.prototype, {
    /**
     * Override scope of a function.
     * @param oScope     {Object}      Object to override the scope of the function.
     * @param arguments  {*}           Any additional arguments to pass.
     */
    use: function() {
        var method = this, args = Array.prototype.slice.call(arguments), object = args.shift();
        return function() {
            return method.apply(object, args.concat(Array.prototype.slice.call(arguments)));
        }
    },
    /**
     * Override scope of a callback function on an event.
     * @param oScope     {Object}      Object to override the scope of the function.
     * @param arguments  {*}           Any additional arguments to pass.
     */
    useEL: function() {
        var method = this, args = Array.prototype.slice.call(arguments), object = args.shift();
        return function(event) {
            return method.apply(object, [event || window.event].concat(Array.prototype.slice.call(arguments)));
        }
    }
});

/**
 * Human-readable Date strings.
 */
$.extend(Date, {
    format: { 
        months: { 
            full: [
                'دی',
                'بهمن',
                'اسفند',
                'فروردین',
                'اردیبهشت',
                'خرداد',
                'تیر',
                'مرداد',
                'شهریور',
                'مهر',
                'آبان',
                'آذر'
            ]
        }
    }
});

/**
 * The main class for running a WebSlide Show.
 * This is initialized once on DOM load via WebSlide.init();
 */
var WebSlide = function() {
    // jQuery caches elements, but these are handier.
    this.els = {};
    for(var a in WebSlide.elements) {
        this.els[a] = $(WebSlide.elements[a]);
    }
    
    // this.slides will be reused to hold the actual Slide objects.
    this.data = this.slides;
    this.slides = [];
    
    this.alignment = this.alignment.split(/\s/);
    
    // Set the text of the intro slide per the config file
    this.els.client.text(this.client);
    this.els.project.text(this.project);
    this.els.date.text(Date.format.months.full[this.date.getMonth()]+' '+/*this.date.getDate()*/' '+', '+/*this.date.getFullYear()*/'1388');
    
    // Hide the details so that we can fade it in nicely after showIntro is called.
    this.els.details.addClass('intro').hide();
    
    // Check to see if the config requires a logo.
    // Attempt to load and display the intro slide.
    if(!(/^\x*$/.test(this.logo))) {
        var img = new Image();
        
        img.onload = function() {
            this.els.logo.append(img);
            this.showIntro();
        }.use(this);
        
        img.onerror = function() {
            this.showIntro();
        }.use(this);
        
        img.src = this.server+this.logo;
    } else {
        this.showIntro();
    }
    
    // without a bg color, the overlay won't respond to events, as it would allow click-through instead.
    this.els.overlay.css({opacity: 0});
    
    $(document).bind('loadingStart', this.showLoading.useEL(this));
    $(document).bind('loadingComplete loadingError', this.hideLoading.useEL(this));
};

/**
 * Static variables and methods for WebSlide.
 */
$.extend(WebSlide, {
    elements: {
        container: '#wsContainer',
        details: '#wsDetails',
        logo: '#wsLogo',
        client: '#wsClient',
        project: '#wsProject',
        date: '#wsDate',
        authForm: '#wsAuth',
        password: '#wsPassword',
        instructions: '#wsInstructions',
        slideContent: '#wsContent',
        loading: '#wsLoading',
        overlay: '#wsOverlay',
        navbar: '#wsNavbar',
        navtab: '#wsNavtab',
        navtablink: '#wsNavtabLink',
        count: '#wsCount',
        title: '#wsSlideTitle',
        desc: '#wsSlideDesc',
        list: '#wsList',
        print: '#wsPrintLink',
        help: '#wsHelpLink',
        helpDialog: '#wsHelpDialog',
        helpClose: '#wsHelpClose',
		about: '#wsAboutLink',
        aboutDialog: '#wsAboutDialog',
        aboutClose: '#wsAboutClose'
    },
    text: {
        passwordFailure: 'رمز عبور صحیح نمی باشد. لطفاٌ دوباره سعی کنید.',
        passwordInstructions: 'برای ادامه لطفاٌ رمز عبور را وارد کنید.',
        navigateUp: 'منو &uarr;',
        navigateDown: 'منو &darr;'
    },
    /**
     * Initialize a new instance of WebSlide.
     */
    init: function() {
        if(!WebSlide.instance) {
            WebSlide.instance = new WebSlide();
        }
    }
});
$.extend(WebSlide.prototype, {
    /**
     * Set the position of and show the introduction slide
     */
    showIntro: function() {
        $(window).bind('resize', this.setPosition.useEL(this));
        this.setPosition(true);
        this.hideLoading();
        
        this.els.navbar.css({visibility: 'hidden'});
        this.hideNav(null, 1, 1);
        this.els.details.fadeIn(1000);
        
        // Add all of the slide objects before progressing onward.
        this.addSlides();
        
        // Require password if demanded.
        if(this.needsAuth) {
            this.els.instructions.text(WebSlide.text.passwordInstructions);
            this.els.password.focus();
            
            // Use lazy auth on form submission.
            this.els.authForm.bind('submit', this.authenticate.useEL(this));
        } else {
            this.els.authForm.hide();
            
            // Prepend the server location and assume the directory for slides is 'images/'.
            var selfServer = this.server;
            $.each(this.slides, function() {
                this.src = selfServer+'images/'+this.file;
            });
            
            // Allow any key or click to hide the intro screen.
            $(document).bind('keyup.intro click.intro', this.hideIntro.useEL(this));
        }
    },
    /**
     * Hide the introduction slide and advance on to the first slide in the show.
     */
    hideIntro: function() { 
        $(document).unbind('keyup.intro click.intro')
        
        this.els.navbar.css({visibility: 'visible'});
        
        var finishHide = function() {
            // Advance to the first slide, show (and hide) the nav in succession.
            var go = function() {
                this.advance(null, 0, function() { 
                    this.showNav(null, 1200);
                    
                    if(this.autoHideNav) {
                        this.hideNav(null, 1000, 1800);
                    }
            
                    // Wait until everything is done before appending the main WebSlide events.
                    this.appendEvents();
                }.use(this));
            }.use(this);
            
            // Wait for the first slide to be preloaded before displaying it.
            this.preload(0, go);
        }.use(this);
        
        this.els.details.effect('puff', {}, 500, function() {
            $('body').animate({ backgroundColor: this.background }, 500, 'linear', finishHide);
        }.use(this));
    },
    /**
     * Lazy Authentication. Preload returns an error if it couldn't find the image.
     * @param event     {Event}         Used to stop the form from actually submitting.
     */
    authenticate: function(event) {
        event.stopPropagation();
        event.preventDefault();
        
        // Check for the image at a URL based on the password.
        // Make the src object for each slide the location+file.
        var slideLocation = this.server+this.els.password.attr('value')+'/';
        $.each(this.slides, function() {
            this.src = slideLocation+this.file;
        });
        
        // On error, report the failure and give it a little shake.
        var authError = function() {
            this.els.instructions.addClass('error');
            this.els.instructions.effect('shake', {times: 2, distance: 5}, 50);
            this.els.instructions.text(WebSlide.text.passwordFailure);
        }.use(this);
        
        // Attempt preloading with the hideIntro, authError callbacks.
        this.preload(0, this.hideIntro.use(this), authError.use(this))
    },
    /**
     * Add the slides from the config as Slide objects into a new array of objects.
     */
    addSlides: function() {
        // Can't employ .use() here because of jQuery's internal scoping on .each()
        var self = this;
        $.each(this.data, function() {
            self.slides.push(new Slide(this));
            self.els.list.append('<li><a href="#'+self.slides.length+'">'+self.slides.length+'</a></li>');
        });
        this.els.slideTiles = $(WebSlide.elements.list+' li');
    },
    /**
     * Advance the show to a new slide (forward or backwards).
     * @param event     {Event}         If a link was clicked or left/right arrow key was pressed.
     * @param slide     {Number}        Force to an exact slide in the array.
     * @param callback  {Function}      Run a function after the slide is finished showing.
     */
    advance: function(event, slide, callback) {
        // Make sure someone isn't dragging around while trying to switch slides.
        if(this.dragging) { return; }
        
        var next = slide+1;
        var slide = slide;
        // Dummy out the callback if it doesn't exist.
        var callback = (callback) ? callback : function() {};

        if(event) {
            // Check for left/right arrow key presses.
            switch(event.keyCode) {
                case 37: // Right (But left in Persian)
                case 63234: // Safari 2 Right (And also)
				case 32: // Spacebar
                    slide = this.currentSlide+1;
                    if(slide < 0) { slide = this.slides.length-1; }
                    if(slide >= this.slides.length) { slide = 0; }
                    next = slide+1;
                    break;
                case 39: // Left (But Right in Persian)
                case 63235: // Safari 2 Left (And also)
                    slide = this.currentSlide-1;
                    if(slide < 0) { slide = this.slides.length-1; }
                    if(slide >= this.slides.length) { slide = 0; }
                    next = slide-1; 
                    break;
            }
            // Kill the default if user clicked a slide tile button.
            if(event.target.nodeName.toLowerCase() === 'a' && event.type == 'click') {
                event.stopPropagation();
                event.preventDefault();
                slide = event.target.hash.replace('#', '')-1;
                next = slide+1;
            }
        }
        // Kill out if there the slide doesn't exist.
        if(typeof slide != 'number') { return; }
        
        // Figure out if the next slide is out of the scope of the array.
        if(next < 0) { next = this.slides.length-1; }
        if(next >= this.slides.length) { next = 0; }
        
        $(document).trigger('slidechange', { slide: next });
 
        var go = function(rebind) {
            if(rebind) {
                // Rebind the key listeners if they aren't already bound.
                $(document).bind('keyup.advance', this.advance.use(this));
            }
            if(typeof this.currentSlide == 'number') {
                this.slides[this.currentSlide].hide();
            }
            this.appendSlide(this.slides[slide]);
            this.currentSlide = slide;
            this.els.count.text(this.currentSlide+1+' از '+this.slides.length);
            this.setPosition();
            
            // Run the callback before showing the slide and preloading the next.
            callback();

            this.currentSlideInfo();
            this.slides[slide].show();
            
            this.preload(next);
        }.use(this);
        
        // Preload the slide if it is not yet cached.
        // Otherwise, as long as it's valid and not the current slide, push it up.
        if(this.slides[slide] != 'undefined' && !this.slides[slide].cache) {
            // destroy the ability to keep moving forward if this slide hasn't been loaded
            $(document).unbind('keyup.advance');
            this.preload(slide, go.use(this, true))
        } else if(this.currentSlide == 'undefined' || slide != this.currentSlide) {
            go(); 
        }
    },
    /**
     * Bind the main navigation and UI events for the show.
     */
    appendEvents: function() {
        // Prevent the default window actions of scrolling left/right
        $(window).bind('keyup keypress keydown', this.preventArrows);
        
        // Left and right navigation keys
        $(document).bind('keyup.advance', this.advance.use(this));
        
        // Show and hide the nav bar
        $(document).bind('keyup.nav', this.toggleNav.use(this));
        this.els.navtab.bind('click', this.toggleNav.useEL(this));
        
        // Make the slides draggable
        this.els.overlay.addClass('draggable').bind('mousedown', this.startDrag.useEL(this));
        $(document).bind('mouseup', this.endDrag.useEL(this));
        
        // Slide button numbers
        this.els.list.find('li').bind('click', this.advance.useEL(this));
        this.els.list.find('a').bind('mouseover', this.previewSlide.use(this)).bind('mouseout', this.unpreviewSlide.use(this));
        
        // Print link
        this.els.print.bind('click', this.readyPrint.useEL(this));
        
        // Help Dialog
        this.els.help.bind('click', this.toggleHelpDialog.useEL(this));
        this.els.helpClose.bind('click', this.hideHelpDialog.useEL(this));
        $(document).bind('keyup', this.toggleHelpDialog.use(this));
		
		// About Dialog
        this.els.about.bind('click', this.toggleAboutDialog.useEL(this));
        this.els.aboutClose.bind('click', this.hideAboutDialog.useEL(this));
        $(document).bind('keyup', this.toggleAboutDialog.use(this));
    },
    /**
     * Stop the default browser behaviors for the left/right arrows so that they can be used for keyboard navigation.
     * @param event     {Event}
     */
    preventArrows: function(event) {
        switch(event.keyCode) {
            case 39: // Right
            case 63235: // Safari 2 Right
            case 37: // Left
            case 63234: // Safari 2 Left
            case 32: // Spacebar
                event.stopPropagation();
                event.preventDefault();
                return false;
                break;
        }
    },
    /**
     * Preview the slide title in the designated slide title area.
     * @param event     {Event}     Used to find the correct slide element to preview.
     * @param slide     {String}    The key number of the slide in the slides array. Overrides event.
     */
    previewSlide: function(event, slide) {
        clearTimeout(this.previewTimeout);
        slide = (slide && slide != 'undefined') ? slide : $(event.target).get(0).hash.replace('#', '')-1;
        this.changeSlideInfo(slide);
    },
    /**
     * Revert to the current slide title and description.
     */
    unpreviewSlide: function() {
        this.previewTimeout = setTimeout(function() {
            this.currentSlideInfo();  
        }.use(this), 300);
    },
    /**
     * Reset the slide info elements to the current slide.
     */
    currentSlideInfo: function() {
        this.els.title.text(this.slides[this.currentSlide].title);
        this.els.desc.text(this.slides[this.currentSlide].description);
        
        // Add a class the the current slide for CSS themes.
        this.els.slideTiles.removeClass('current');
        $(this.els.slideTiles[this.currentSlide]).addClass('current');
    },
    /**
     * Set the slide info elements to a particular slide.
     * @param slide     {Number}        Number key of the slide to set as the slide info.
     */
    changeSlideInfo: function(slide) {
        this.els.title.text(this.slides[slide].title);
        this.els.desc.text(this.slides[slide].description);
    },
    /**
     * Tell the slide object to preload its image.
     */
    preload: function(key, callback, error) {
        this.slides[key].preload(callback, error);
    },
    /**
     * Add the slide to the current stack of slides actually in the HTML display.
     * @param slide     {String}        HTML of the slide.
     */
    appendSlide: function(slide) {
        // Make sure it's not already on the stage.
        if(!slide.cache.parentNode) {
            this.els.slideContent.prepend(slide.cache);
            // Titles are added for printing.
            this.els.slideContent.prepend('<h2>'+slide.title+'</h2>');
        }
    },
    /**
     * Report the window size and positioning instructions to the slide object.
     * Center the details information for in the window.
     */
    setPosition: function(forceDetails) {
        var docWidth = $(window).width();
        var docHeight = $(window).height();
        var topPos = Math.round((docHeight/2)-($(this.els.details).height()/2));
        var leftPos = Math.round((docWidth/2)-($(this.els.details).width()/2));
        
        // Tell the current slide to position itself.
        if(this.slides[this.currentSlide]) {
            var slide = this.slides[this.currentSlide];
            slide.setPosition(this.alignment, [docWidth, docHeight]);
        }
        
        // Center the intro slide if it's visible or forced.
        if(this.els.details.css('display') != 'none' || forceDetails == true) {
            this.els.details.css({ top: topPos+'px', left: leftPos+'px' });
        }
    },
    /**
     * Show the loading element.
     */
    showLoading: function() {
        this.els.loading.stop().fadeIn(200, function() {
            this.loading = true;
        }.use(this));
    },
    /**
     * Hide the loading element.
     */
    hideLoading: function() {
        this.els.loading.stop().fadeOut(200, function() {
            this.loading = false;
        }.use(this));
    },
    /**
     * Hide the Navigation display.
     * @param event     {Event}         Unused.
     * @param timing    {Number}        Override the default time for the animation.
     * @param wait      {Number}        Time to wait before running the animation.
     */
    hideNav: function(event, timing, wait) {
        if(this.timer) { clearTimeout(this.timer); }
   
        var docHeight = $(window).height();
        wait = (typeof wait != 'number') ? 1000 : wait;
        timing = (typeof timing != 'number') ? 400 : timing;
        
        this.timer = setTimeout(function() {
            this.els.navtablink.html(WebSlide.text.navigateUp);
            // TODO calculate what edge to hide the navbar off of
            this.els.navbar.stop().animate({bottom: -this.els.navbar.outerHeight()+'px'}, timing, 'easeInOutCubic', function() {
                this.navOpen = false;
            }.use(this));
        }.use(this), wait);
    },
    /**
     * Show the Navigation display.
     * @param event     {Event}         Unused.
     * @param timing    {Number}        Override the default time for the animation.
     */
    showNav: function(event, timing) {
        if(this.currentSlide) {
            this.currentSlideInfo();
        }
        if(this.timer) { clearTimeout(this.timer); }
        
        timing = (typeof timing != 'number') ? 400 : timing;
        
        this.els.navtablink.html(WebSlide.text.navigateDown);
        // TODO calculate what edge to show the navbar off of
        this.els.navbar.stop().animate({bottom: '0px'}, timing, 'easeInOutCubic', function() {
            this.navOpen = true;
        }.use(this));
    },
    /**
     * Toggle the Navigation display.
     * Checks for N keyCode and determines whether to show or hide.
     * @param event     {Event}         Unused.
     */
    toggleNav: function(event) {
        // If key is not 'N', do nothing.
        if(event.keyCode && event.keyCode != 78) { return; }
        if(event) { event.preventDefault(); event.stopPropagation(); }
        
        if(this.navOpen) {
            this.hideNav(null, false, 50);
        } else {
            this.showNav();
        }
    },
    /**
     * Start the dragging function on mousedown.
     * @param event     {Event}     For finding the mouse position and determining movement later.
     */
    startDrag: function(event) {
        this.dragging = true;
        this.mouseDragPos = { x: event.screenX, y: event.screenY };
        this.els.overlay.bind('mousemove.drag', this.drag.useEL(this));
    },
    /**
     * Calculate the movement on mousemove for repositioning the screen during dragging.
     * @param event     {Event}     For finding the mouse position and determining movement.
     */
    drag: function(event) {
        $(document).scrollLeft($(document).scrollLeft()+(this.mouseDragPos.x-event.screenX));
        this.mouseDragPos.x = event.screenX;
        
        $(document).scrollTop($(document).scrollTop()+(this.mouseDragPos.y-event.screenY));
        this.mouseDragPos.y = event.screenY;
    },
    /**
     * Kill the dragging function on mouseup.
     * @param event     {Event}     Unused.
     */
    endDrag: function(event) {
        this.dragging = false;
        this.els.overlay.unbind('mousemove.drag');
    },
    /**
     * Load and show all images and pop up print dialog.
     * @param event     {Event}     For stopping the link action.
     */
    readyPrint: function(event) {
        event.stopPropagation();
        event.preventDefault();
        
        $(document).trigger('print');
  
        this.clearAll();
        this.loadAll(this.showAllAndPrint.use(this));
    },
    /**
     * Clear out all of the current slide HTML so that they will appear in order.
     */
    clearAll: function() {
        this.els.slideContent.text('');
    },
    /**
     * Load all slides.
     * @param callback      {Function}      Return function after all slides are loaded.
     */
    loadAll: function(callback) {
        var callback = (callback) ? callback : function() {};
        var i = this.slides.length;
        while(i--) {
            if(i == 0) {
                this.preload(i, callback);
            } else {
                this.preload(i);
            }
        }
    },
    /**
     * Show all slides and print the page. Set to last slide.
     */
    showAllAndPrint: function() {
        var i = this.slides.length;
        var j = i-1;
        while(i--) {
            this.appendSlide(this.slides[i]);
        }
        
        // Set the current slide to the last slide
        this.advance(null, j);
        
        // Adding large images to the DOM continues running as JavaScript is still running.
        // Because of that, we have to wait about a second.
        setTimeout(function() {
            $(this.slides).each(function() { this.show(0); });
            window.print();
        }.use(this), 1000);
    },
    /**
     * Toggle the help dialog.
     * Checks for H keyCode and determines whether to show or hide.
     * @param event     {Event}     Used for keyboard detection.
     */
    toggleHelpDialog: function(event) {
        if(event) {
            event.stopPropagation();
            event.preventDefault();
            if(event.keyCode && event.keyCode != 72) {
                return;
            }
        }
        if(this.els.aboutDialog.hasClass('visible'))
			this.hideAboutDialog();
			
        if(!this.els.helpDialog.hasClass('visible')) {
            this.showHelpDialog();
        } else {
            this.hideHelpDialog();
        }
    },
	/**
     * Toggle the about dialog.
     * Checks for A keyCode and determines whether to show or hide.
     * @param event     {Event}     Used for keyboard detection.
     */
    toggleAboutDialog: function(event) {
        if(event) {
            event.stopPropagation();
            event.preventDefault();
            if(event.keyCode && event.keyCode != 65) {
                return;
            }
        }
		if(this.els.helpDialog.hasClass('visible'))
			this.hideHelpDialog();
        
        if(!this.els.aboutDialog.hasClass('visible')) {
            this.showAboutDialog();
        } else {
            this.hideAboutDialog();
        }
    },
    /**
     * Show the help dialog.
     * Centers the help dialog in the window and fades in.
     * @param event     {Event}     Unused.
     */
    showHelpDialog: function(event) {			
        if(event) {
            event.stopPropagation();
            event.preventDefault();
        }

        var maxWidth = $(window).width() - Math.round($(window).width() / 2);
        this.els.helpDialog.width(maxWidth);
        
        var maxHeight = $(window).height() - Math.round(this.els.navbar.outerHeight() * 3);
        if(this.els.helpDialog.height() > maxHeight) {
            this.els.helpDialog.height(maxHeight);
        }
        
        var leftPos = $(document).scrollLeft() + (($(window).width() / 2) - (this.els.helpDialog.width() / 2));
        var topPos = $(document).scrollTop() + (($(window).height() / 2) - (this.els.helpDialog.height() / 2));
        
        this.els.helpDialog.hide().css({ left: leftPos+'px', top: topPos+'px' }).fadeIn(600).addClass('visible');
        
        $(document).bind('slidechange zoom print', this.hideHelpDialog.useEL(this));
    },
	/**
     * Show the about dialog.
     * Centers the about dialog in the window and fades in.
     * @param event     {Event}     Unused.
     */
    showAboutDialog: function(event) {
        if(event) {
            event.stopPropagation();
            event.preventDefault();
        }

        var maxWidth = $(window).width() - Math.round($(window).width() / 2);
        this.els.aboutDialog.width(maxWidth);
        
        var maxHeight = $(window).height() - Math.round(this.els.navbar.outerHeight() * 3);
        if(this.els.aboutDialog.height() > maxHeight) {
            this.els.aboutDialog.height(maxHeight);
        }
        
        var leftPos = $(document).scrollLeft() + (($(window).width() / 2) - (this.els.aboutDialog.width() / 2));
        var topPos = $(document).scrollTop() + (($(window).height() / 2) - (this.els.aboutDialog.height() / 2));
        
        this.els.aboutDialog.hide().css({ left: leftPos+'px', top: topPos+'px' }).fadeIn(600).addClass('visible');
        
        $(document).bind('slidechange zoom print', this.hideAboutDialog.useEL(this));
    },
    /**
     * Hide the help dialog.
     * @param event     {Event}     Unused.
     */
    hideHelpDialog: function(event, data) {
        console.log(data)
        if(event) {
            event.stopPropagation();
            event.preventDefault();
        }
        
        this.els.helpDialog.fadeOut(500).removeClass('visible');
    },
	/**
     * Hide the about dialog.
     * @param event     {Event}     Unused.
     */
    hideAboutDialog: function(event, data) {
        console.log(data)
        if(event) {
            event.stopPropagation();
            event.preventDefault();
        }
        
        this.els.aboutDialog.fadeOut(500).removeClass('visible');
    }
});
$.extend(WebSlide.prototype, wsConfig);

/**
 * Slide class that every slide/image instance is bound to.
 * @param data      {Object}        Object from the array of slides set in the user config.
 */
var Slide = function(data) {
    this.file = data.file;
    this.title = data.title;
    this.description = data.description;
    this.cache, this.src;
};
$.extend(Slide.prototype, {
    /**
     * Preload the image for the slide.
     * @param callback      {Function}      Optional function to run on image load success.
     * @param error         {Function}      Optional function to run on image load error.
     */
    preload: function(callback, error) {
        callback = (callback) ? callback : function() {};
        error = (error) ? error : function() {};
        
        // Check if the image is already loaded/cached.
        if(!this.cache) {
            this.cache = new Image();
            
            // Globally announce that we're loading something.
            $(document).trigger('loadingStart');
            
            this.cache.onload = function() {
                // Hold the dimensions in an array for referencing later during zooms.
                this.dimensions = {
                    height: this.cache.height,
                    width: this.cache.width
                };
                
                // Success! Fire the callback.
                callback();
                
                // Globally announce that loading finished.
                $(document).trigger('loadingComplete');
            }.use(this);
            
            this.cache.onerror = function() {
                // Globally announce that loading errored.
                $(document).trigger('loadingError');
                
                // Nullify the cache since it couldn't be loaded.
                this.cache = null;
                
                // Run the error callback.
                error();
            }.use(this);
            
            // create a new cache instance in this Slide object.
            this.cache.src = this.src;
            $(this.cache).hide();
        } else {
            callback();
        }
    },
    /**
     * Place the slide onto our document in the correct position.
     * @param position      {String}        Space-separated string of horizontal/vertical alignment.
     * @param context       {Array}         [Width,Height] of the context in which we are placing this slide.
     */        
    setPosition: function(position, context) {
        if(!this.dimensions) { return; }
        this.position = position || this.position;
        var dimensions = this.zoomDimensions || this.dimensions;
        
        var leftPos, topPos;
        switch(this.position[0].toLowerCase()) {
            case 'left':
                leftPos = '0';
                break;
            case 'right':
                leftPos = Math.abs(context[0]-dimensions.width);
                break;
            default:
                leftPos = Math.abs(Math.round(context[0]/2)-Math.round(dimensions.width/2));
                break;
        }
        
        switch(this.position[1].toLowerCase()) {
            case 'bottom':
                topPos = Math.abs(context[1]-dimensions.height);
                break;
            case 'center':
                topPos = Math.abs(Math.round(context[1]/2)-Math.round(dimensions.height/2));
                break;
            default:
                topPos = '0';
                break;
        }
        
        if(dimensions.height <= context[1] || dimensions.width <= context[0]) {
            if(dimensions.height <= context[1]) {
                $(this.cache).css({top: topPos+'px'});
                $('body,'+WebSlide.elements.overlay).css({height:context[1]+'px'});
            } else {
                $('body,'+WebSlide.elements.overlay).css({height:dimensions.height+'px'});
                $(document).scrollTop(topPos);
            }
            if(dimensions.width <= context[0]) {
                $(this.cache).css({left: leftPos+'px'});
                $('body,'+WebSlide.elements.overlay).css({width:context[0]+'px'});
            } else {
                $('body,'+WebSlide.elements.overlay).css({width:dimensions.width+'px'});
                $(document).scrollLeft(leftPos);
            }
        } else {
            $('body,'+WebSlide.elements.overlay).css({width:dimensions.width+'px', height:dimensions.height+'px'});
            $(document).scrollTop(topPos);
            $(document).scrollLeft(leftPos);
        }
        if(dimensions.width < $(window).width()) {
            $(WebSlide.elements.overlay).css({width:$(window).width()+'px'});
        }
        if(dimensions.height < $(window).height()) {
            $(WebSlide.elements.overlay).css({height:$(window).height()+'px'});
        }
        this.zoomDimensions = false;
    },
    /**
     * Animate the opacity out to hide the slide image.
     */
    hide: function(timing) {
        var timing = (timing != 'undefined') ? timing : 500;
        $(document).unbind('keyup', this.zoomFn);
        
        if(timing == 0) {
            $(this.cache).hide();
        } else {
            $(this.cache).fadeOut(timing);
        }
    },
    /**
     * Animate the opacity in to show the slide image.
     */
    show: function(timing) {
        var timing = (timing != 'undefined') ? timing : 500;
        if(this.dimensions) {
            $(this.cache).css({ width: this.dimensions.width+'px', height: this.dimensions.height+'px' });
        }
        
        this.zoomFn = this.zoom.use(this);
        $(document).bind('keyup', this.zoomFn);
        if(timing == 0) {
            $(this.cache).show();
        } else {
            $(this.cache).fadeIn(timing);
        }
    },
    /**
     * Zoomify the image in or out.
     * @param event         {Event}         Keycode event to determine zooming in or out.
     */
    zoom: function(event) {
        var finish = function() {
            this.zoomDimensions = {
                height: this.cache.offsetHeight,
                width: this.cache.offsetWidth
            };
            this.setPosition(null, [$(window).width(), $(window).height()]);
        };
        if(event.keyCode == 90 || event.keyCode == 88) {
            $(document).trigger('zoom');

            // Set the percent difference to scale the image to from its current size.
            var change = 80;
            if(event.keyCode == 90) { change = 120; } 
            
            var horizontalPos = this.position[0];
            var verticalPos = (this.position[1] == 'center') ? 'middle' : this.position[1];
            
            $(this.cache).effect('scale', { percent: change, origin: [verticalPos, horizontalPos] }, 400, finish.use(this));
        }
    }
});

$(document).ready(WebSlide.init);
