/**
 * Create an image slideshow
 *
 * <h4>Usage</h4>
 * <pre>slides = [
 *   { 'image': 'foobar.png', 'link': 'www.foo.com' },
 *   { 'image': 'bar.jpg', 'link': 'www.bar.com.au' }
 * ];
 * container = $('#mycontainer');
 * s = slideshow(container, slides);
 * s.showNavigation();
 * s.start();</pre>
 *
 * <h4>Slides</h4>
 * <p>The slides param must be an array of either img links, or objects with image and link properties.</p>
 *
 * @class An image slideshow displayed on a webpage
 * @param {Element} container The html element that will hold the slideshow
 * @param {Object[]} slides An array of slides
 */
function Slideshow(container, slides) {

    // Convert slides to a standard format
    for (var i = 0; i < slides.length; i++) {
        if (typeof slides[i] == 'string') {
            slides[i] = {
                "image": slides[i],
                "link": ""
            };
        }
    }

    /**
     * Reference to this object
     * @type Slideshow
     */
    var self = this;

	/**
     * Time taken to fully display a new slide, in milliseconds
     * @type Integer
     */
    var fadeDuration = 200;

    /**
     * Time between slides when transitioning automatically, in milliseconds
     * @type Integer
     */
    var slideDuration = 1800;

    /**
     * Part of a two image system used to transition between slide images
     * @type Img Element
     */
    var imageTop = null;

    /**
     * Part of a two image system used to transition between slide images
     * @type Img Element
     */
    var imageBottom = null;

    /**
     * The index of the slide that is currently displayed
     * @type Integer
     */
    var currentSlideIndex = 0;

    /**
     * A timer used to control automatic slide transitions
     * @type Integer
     */
    var timerID = null;

    /**
     * A navigation panel users can control the slideshow with
     * @type SlideshowNavPanel
     */
    var navPanel = null;

    /**
     * The 'a' html tag that allows slides to link to websites
     */
    var anchor = null;

    /**
     * Height of this slideshow. If null, then change size with each image.
     */
    var height = null;

    /**
     * Move to a new slide and update the navigation panel
     * @param {Integer} slideIndex The index of the slide to move to
     */
    this.setSlide = function (slideIndex) {
		// Set up the new slide as a hidden image
		imageTop.hide();
		imageTop.attr("src", slides[slideIndex].image);

		// From this point we consider the slide to have changed.
		currentSlideIndex = slideIndex;

		// Update the image link
		self.setLink(slides[slideIndex].link);

        // If no slideshow height specified, set to that of the current image
        if (this.height === null) {
            container.css('height', imageBottom.height());
        }

        // Transition to the new slide with a cross fade
        imageTop.fadeIn(fadeDuration, function() {
			/*
			 * After the transition has completed, set the now obscured bottom
			 * image to the same as the top
			 */
            imageBottom.attr("src", imageTop.attr("src"));
        });

		// Notify the nav panel that the slideshow has updated so it can redraw the buttons
        if (navPanel) {
            navPanel.update();
        }
    };

	this.setLink = function(link) {
        if (typeof link == "string" && link.length > 0) {
            anchor.attr("href", link);
        }
        else {
            anchor.removeAttr("href");
        }
	}

    /**
     * Move to next slide in the slideshow, if the end is reached move to the first slide
     */
    this.nextSlide = function() {
        var nextSlideIndex = (currentSlideIndex + 1) % slides.length;
        this.setSlide(nextSlideIndex);
    }

    /**
     * Move to a specific slide
     * <p>This function handles manually changing slides while a slideshow has started.</p>
     * @param {Integer} slideIndex The index of the slide to move to
     */
    this.moveToSlide = function(slideIndex) {
        if (this.isRunning()) {
            this.stop();
            this.setSlide(slideIndex);
            this.start();
        }
        else {
            this.setSlide(slideIndex);
        }
    };

    /**
     * Return the container element that this slideshow is using
     * @return This slideshow's container
     * @type Element
     */
    this.getContainer = function() {
        return container;
    }

    /**
     * Start the slideshow
	 *
	 * @param {Integer} slideDurationMS Time between slides in milliseconds
	 * @param {Integer} fadeDurationMS Length of transition between slides in milliseconds
     */
    this.start = function(slideDurationMS, fadeDurationMS) {
		// Use params if provided, otherwise use current settings
		if (slideDurationMS != undefined) slideDuration = slideDurationMS;
		if (fadeDurationMS != undefined) fadeDuration = fadeDurationMS;

        this.stop();
        var self = this;
        var callback = function() {
            self.nextSlide()
        };
        timerID = setInterval(callback, slideDuration + fadeDuration);
    }

    /**
     * Stop the slideshow
     */
    this.stop = function() {
        if (timerID != null) {
            clearInterval(timerID);
        }
    }

    /**
     * Return true if the slideshow is currently started
     */
    this.isRunning = function() {
        return (timerID != null);
    }

    /**
     * Return the number of slides
     * @return Number of slides in this slideshow
     * @type Integer
     */
    this.slideCount = function() {
        return slides.length;
    }

    /**
     * Return the index slide currently being displayed
     * @return Index of current slide
     * @type Integer
     */
    this.getCurrentSlideIndex = function() {
        return currentSlideIndex;
    }

    /**
     * Create and display a navigation panel for this slideshow
     *
     * <p>Opacity ranges from 0.0 for fully transparent, to 1.0 for fully opaque.
     * Defaults to 1.0</p>
     *
     * @param {Float} opacity Opacity level for navigation control
     */
    this.showNavigation = function(opacity) {
        if (opacity === undefined) {
            opacity = 1.0;
        }
        if (navPanel == null) {
            navPanel = new SlideshowNavPanel(this, opacity);
        }
        else {
            navPanel.setOpacity(opacity);
        }
    }

    /**
     * Change the height of this slideshow.
     * <p>Examples:<br />
     * setHeight('300px'); // Set slideshow height to a fixed 300 pixels
     * setHeight(null); // Resize slideshow with each image
     * </p>
     *
     * @param {String} height Slideshow height, in css format
     */
    this.setHeight = function(height) {
		height = '280px';
        this.height = height;
        if (height !== null) {
            container.css('height', height);
        }
        else {
            container.css('height', '');
        }
    }

    /**
     * Duration of fade effect when transitioning between slides
     * @param {Integer} duration Time in milleseconds
     */
    this.setFadeDuration = function(duration) {
        fadeDuration = duration
    }

    /**
     * Time to show a slide before automatically moving to the next.
     * @param {Integer} duration Time in milleseconds
     */
    this.setSlideDuration = function(duration) {
        slideDuration = duration
    }

    // Clear container of existing content and insert the slideshow
    container.html('<img class="slideshowImage bottom" /><img class="slideshowImage top"/>');
    container.children(".slideshowImage").css('position', 'absolute');

	// Grab references to the two images used for the slideshow
    imageTop = container.children(".slideshowImage.top");
    imageBottom = container.children(".slideshowImage.bottom");

    // Set up the anchor used for slide links
    container.children().wrapAll('<a class="slideshowLink"></a>');
    anchor = container.children(".slideshowLink");

    // Set up initial state
    imageTop.attr("src", slides[0].image);
	imageBottom.attr("src", slides[0].image);

}

/**
 * Add a navigation panel to an existing slideshow
 * @param {Slideshow} slideshowInstance The slideshow to control
 * @param {Float} opacity The opacity of this navigation panel
 */
function SlideshowNavPanel(slideshowInstance, opacity) {
    var container = slideshowInstance.getContainer();
    var slideCount = slideshowInstance.slideCount();
    var buttons = null;
    var navPanel = null;

    /**
     * Reference to this object
     * @type SlideshowNavPanel
     */
    var self = this;

    /**
     * Update the navigation panel to reflect the slideshow
     * <p>This is called by the slideshow whenever it moves to a new slide</p>
     */
    this.update = function() {
        var index = slideshowInstance.getCurrentSlideIndex();
        buttons.removeClass("current");
        $(buttons[index]).addClass("current");
    }

   /**
    * Set the opacity for this navigation panel
    * @param {Float} opacity Opacity for this navigation panel
    */
    this.setOpacity = function(opacity) {
        // Use jQuery to handle cross browser compatibility when setting nav panel opacity
        navPanel.fadeTo(0, opacity);
    }

    /**
     * Attach this navigation panel to an existing slideshow
     * @param {Slideshow} slideshow An instance of a slideshow to add navigation to
     */
    var attach = function(slideshow) {
        // Insert the nav panel after the slideshow
        container.append('<div class="slideshowNavPanel"></div>');
        navPanel = container.children('.slideshowNavPanel');

        // Add a button for each slide in the slideshow
        for (var i = 0; i < slideCount; i++) {
            navPanel.append('<span class="navButton"></span');
        }
        buttons = navPanel.children('.navButton');

        // Set up each button to move to a specific slide when clicked
        buttons.each(function(i) {
            $(this).bind('click', {
                index: i
            }, function(event) {
                slideshow.moveToSlide(event.data.index);
            });
        });

        // Set up extra spans for styling this navigation panel
        navPanel.children().first().wrap('<span class="leftborder"></span>');
        navPanel.children().not(navPanel.children().first()).wrapAll('<span class="rightborder"></span>');

        self.setOpacity(opacity)
    }

    attach(slideshowInstance);
    this.update();
}

