﻿/**
 *  This class controls scrolling through the images in the images array
 *  within the config object provided on instantiation, and uses various
 *  other configuration properties to handle dynamic creation control and
 *  dynamic element creation.
 */
function ImageCarouselListener(config)
{
    var self = this;
    var dom = new DomHelper();

    // Prefixing the required elements with a number to allow for multiple
    // instances of ImageCarouselListeners.
    config.prefix = config.paging_prefix || 
        String( Math.floor( Math.random() * 10000 ));

    //  Sets default maximum dimension.
    config.max_dimension = config.max_dimension || 75;

    //  Set default paging_template: uses paging_html if present 
    //  in the configuration.
    config.paging_template_html = config.paging_html ||
        '<span class="#{paging_class}" id="#{paging_id}">' +
        '#{paging_number}</span>' + '&nbsp;&nbsp;' ;

    //  Used internally if no paging classes are provided.
    config.no_paging_class_default_html = 
        '<span id="#{paging_id}">' +
        '#{paging_number}</span>' + '&nbsp;&nbsp;' ;

    //  HTML templating for each of the new images placed in the carousel.
    //  Uses item_html if present in the item_html.
    config.item_html = config.item_html ||
        '<a href="#{url}">' +
        '<img id="#{image_id}" src="#{file}"/>#{caption}</a>' +
        '<p>#{text}</p>';
        
    /**
     * Custom inital load handler. Called when the carousel loads the initial
     * set of data items. Specified to the carousel as the configuration
     * parameter: loadInitHandler
     */
    this.initialize = function(type, args) 
    {
        //  Turn off pagination if paging is explicitly set to false,
        //  or not present in the configuration, or if the paging_area_id
        //  is not present in the configuration.
        if (!config.paging_on || !paging_area_id)
        {
            self.disabled_initializePaging = self.initializePaging;
            self.disabled_scrollPaging = self.scrollPaging;
            self.initializePaging = function() { };
                self.scrollPaging = function() { };
        }
        //  If no on_paging_class or no off_paging_class is provided
        //  then use for the paging_template_html use cofiguration
        //  paging_html (if provided), but if paging_html is not provided
        //  then use the default which is no_paging_class_default.
        if (!config.on_paging_class || !config.off_paging_class)
        {
            config.on_paging_class = config.off_paging_class = "";
            config.paging_template_html = config.paging_html ||
                config.no_paging_class_default_html;
        }

        var start = args[0];
        var last = args[1];

        //  On initialization of the YUI carousel the context is the
        //  YUI carousel, so at that time 'this' refers to the YUI
        //  carousel context, and NOT to an instance of 
        //  ImageCarouselListener.
        self.initializeCarousel( this, start, last );
        self.initializePaging( this, start, last );
    };
    /**
     *  This function adds the images to the carousel that are required
     *  when the carousel is first viewed, and varies based on the
     *  configuration passed to the YUI carousel.
     */
    this.initializeCarousel = function( carousel, start, last )
    {
        for(var i = start - 1; i < last; i++)
        {
            var carousel_index = i+1;
            self.addItem( carousel, i, carousel_index );
            self.setDimensions( config.images[i] );
        }
    }
    /**
     *  Creates the area where the pagination will reside by creating
     *  copies of the paging HTML providing the correct substitutions
     *  to the template HTML:
     *
     *  '<span class="#{paging_class}" id="#{paging_id}">'
     *      + '#{paging_number}</span>' + '&nbsp;&nbsp;' ;
     *
     *  All of the substutions are generated by this function, such
     *  as paging_id, and paging_number.  However, if the paging
     *  classes are not set this function will not change or set the
     *  element's, either here or during pagination.  Both
     *  on_paging_class and off_paging_class must be specified in the
     *  config.
     */
    this.initializePaging = function( carousel, start, last )
    {
        for (var i = 0; i < carousel.size; i++)
        {
            var item = config.images[i];
            var carousel_index = (i+1);

            item.paging_id = config.prefix + '-paging-item-' + carousel_index;
            item.paging_number = carousel_index;
            
            item.paging_class = 
                ( carousel_index >= start && carousel_index <= last ) ?
                config.on_paging_class :
                config.off_paging_class ;

            YAHOO.util.Dom.get( config.paging_area_id ).innerHTML += 
                ( config.paging_template_html.substitute( item ) );
        }
    };
    /**
     *  This method is responsible for generating XHTML for changing out
     *  the larger image, updating a link for that image, and maintaining
     *  an ID to the element for which the small image is bound, so that
     *  event handlers to that image can be added.
     */
    this.formatItem = function(item)
    {
        item.caption = item.caption || "";
        item.url = item.url || "#";
        item.text = item.text || "";

        return config.item_html.substitute(item);
    }
    /**
     * To simulate out of synch loading, we load these items backwards.
     * This exercises the ability to load items out of sequence.
     */
    this.load = function( carousel, start, last )
    {
        for (var i = start - 1; i < last; i++)
        {
            var carousel_index = (i+1);

            if( !carousel.isItemLoaded( carousel_index ) )
            {
                self.addItem( carousel, i, carousel_index );
                self.setDimensions( config.images[i] );
            }
        }
    };
    /**
     *  This method scrolls the highlighted, or on, paging elements
     *  so that they match the images that are currently viewable.
     */
    this.scrollPaging = function( start, last )
    {
        for ( var i = 0; i < config.images.length; i++)
        {
            var carousel_index = (i+1);
            var item = config.images[i];

            item.paging_element = item.paging_element
                || YAHOO.util.Dom.get( item.paging_id );

            ( carousel_index >= start && carousel_index <= last ) ?
                dom.setClass( item.paging_element, config.on_paging_class ) :
                dom.setClass( item.paging_element, config.off_paging_class );
        }
    }
    /**
     *  This function takes as a parameter an element from the
     *  configuration and then adjusts either the width or the
     *  height to be equal to the configuration setting max_dimension.
     */
    this.setDimensions = function( image )
    {
        var w = image.width, h = image.height , ratio = 1.0;
        var max_w = config.max_width / 1.0; // turns it into a float
        var max_h = config.max_height / 1.0; // turns it into a float

        if (w > max_w && h > max_h) // case 1: both w & h too large
        {
            ratio = ((w / max_w) > (h / max_h)) ?
                (max_w / w) :
                (max_h / h);
        }
        else if (w > max_w && h <= max_h) // case 2: w too large h ok
        {
            ratio = max_w / w;
        }
        else if (w <= max_w && h > max_h) // case 3: h too large w ok
        {
            ratio = max_h / h;
        }
        // else case 4: w & h are both ok. ratio = 1.0 does nothing below.

        image.width = (w * ratio);
        image.height = (h * ratio);
    }
    /**
     *  This method adds one of the image items from the config image list
     *  to the carousel parameter.  The offest is used to determine which 
     *  image item to add, and the carousel index parameter is used to 
     *  tell the carousel which item position it should track load 
     *  previous/next position for.
     */
    this.addItem = function( carousel, offset, carousel_index )
    {
        var item = config.images[ offset ];

        item.carousel_id = config.prefix + "-carousel-item-" + carousel_index;
        item.image_id = config.prefix + "-image-id-" + carousel_index;

        carousel.addItem( carousel_index, self.formatItem( item ) );

        item.carousel_element = YAHOO.util.Dom.get( item.carousel_id );
        item.image_element = YAHOO.util.Dom.get( item.image_id );
        
        YAHOO.util.Event.addListener(item.image_element, 
            'load', function() { self.setDimensions(item.image_element); },
            false);
    }
    /**
     * Custom load next handler. Called when the carousel loads the next
     * set of data items. Specified to the carousel as the configuration
     * parameter: loadNextHandler
     */
    this.loadNextItems = function(type, args) {

        var start = args[0];
        var last = args[1];
        var already_cached = args[2];

        if( !already_cached )
        {
            self.load(self.carousel, start, last);
        }
        self.scrollPaging( start, last );
    };
    /**
     *  Custom load previous handler. Called when the carousel loads the
     *  previous set of data items. Specified to the carousel as the 
     *  configuration parameter: loadPrevHandler
     */
    this.loadPrevItems = function(type, args)
    {
        var start = args[0];
        var last = args[1];
        var already_cached = args[2];

        if( !already_cached )
        {
            self.load(self.carousel, start, last);
        }
        self.scrollPaging( start, last );
    };
    /**
     * Custom button state handler for enabling/disabling button state.
     * Called when the carousel has determined that the previous button
     * state should be changed.
     * Specified to the carousel as the configuration
     * parameter: prevButtonStateHandler
     */
    this.handlePrevButtonState = function(type, args)
    {
        var enabling = args[0];
        var leftImage = args[1];

        leftImage.src = ( enabling ) ?
            "/graphics/left-enabled.gif"  :
            "/graphics/left-disabled.gif" ;
    };
    /**
     * Custom button state handler for enabling/disabling button state.
     * Called when the carousel has determined that the next button
     * state should be changed.
     * Specified to the carousel as the configuration
     * parameter: nextButtonStateHandler
     */
    this.handleNextButtonState = function(type, args)
    {
        var enabling = args[0];
        var rightImage = args[1];

        rightImage.src = ( enabling ) ?
            "/graphics/right-enabled.gif"  :
            "/graphics/right-disabled.gif" ;
    };
}; // end image carousel controller

