//<!--

/*
 * ScrollPane class
 * 
 * @require     prototype.js, scriptaculous.js. effects.js
 * @author      Blair D. Patterson (http://www.essgeebee.ca)
 * @copyright   2010 Blair D. Patterson
 * @package     SGB UI
 * @license     MIT
 * @TODO
 *      vertical application
 *      IE tests (horrifically broken in ALL - fun!)
 *      Max width of photos (default 1280) - don't allow items to exceed that width somehow?
 * 
 * @param element                       (required) : String, HTMLElement ID
 * @param options: object hash of the following options
 *      - scale_amount                  (optional) : Float                          default: 0.8
 *      - duration                      (optional) : Float, effect duration         default: 1
 *      - item_focused_opacity          (optional) : Float, opacity during morph    default: 1.0
 *      - item_unfocused_opacity        (optional) : Float, opacity during morph    default: 0.5
 *      - transition                    (optional) : String, Effect.Transition      default: Effect.Transitions.sinoidal
 *      - scaled_element                (optional) : String, HTMLElmment ID         default: null                           Pass ID to have that photo enlarged by default
 *      - navigation                    (optional) : Hash
 *          - next                      (optional) : String, HTMLElement ID         default: null
 *          - previous                  (optional) : String, HTMLElement ID         default: null
 *          - disabled_css_classname    (optional) : String                         default: ''
 *
 * @usage
 * 
    <div id="photoContainer">
        <!-- this div will be inserted by the class, if not present - not best practice for non-JS -->
        <div id="photos" style="width:{SUM(width of childElements)}"> 
            <div></div>
            <div></div>
            <div></div>
            <div></div>
            <div></div>
            
            <img id="photo_1" src="images/icons/fugue/icons/address_book__minus.png" width="500" height="500" />
            <img id="photo_2" src="images/icons/fugue/icons/address_book__plus.png" width="500" height="500" />
            <img id="photo_3" src="images/icons/fugue/icons/address_book__pencil.png" width="500" height="500" />
            <img id="photo_4" src="images/icons/fugue/icons/address_book__exclamation.png" width="500" height="500" />
            <img id="photo_5" src="images/icons/fugue/icons/address_book__arrow.png" width="500" height="500" />
        </div>
    </div>

    div#photoContainer {
        overflow-y:hidden; / *important for non-js * /
        overflow-x:scroll; / * important for non-js * /
        width:80%;
        float:left;
        background:url(crosshair.png) no-repeat center center;}
        
    #photos {
        width:10000px; / * this should be written dynamically to the style tag for non-js users * /
        height:600px; / * this is overridden in setup(), and set to the largest scaled height * /}
        
    #photos img {
        margin:0 20px;
        padding: 0 10px; / * the class handles border and padding issues internally * /
        border:10px solid #fff;
        background:#45eef2;
        height:100%;}
        
        #photos div {
            display:inline-block;
            width:500px;
            height:500px;
            padding:0 20px; / * the class handles border and padding issues internally * /
            border:10px solid #fff;}
 *
 * 
 * 
 * 
    Event.observe
    (
        window, 
        'load', 
        function() 
        { 
            window.ScrollPane1 = new ScrollPane
            (
                'photoContainer', 
                { 
                    duration:                   0.4, 
                    scale_amount:               0.8, 
                    item_opacity:               0.5,
                    scaled_element:             $('photo_5'),
                    transition:                 Effect.Transitions.sinoidal,
                    navigation:
                    {
                        next:                   'scrollPaneNext',
                        previous:               'scrollPanePrevious',
                        disabled_css_classname: 'disabled'
                    }
                    
                }
            ); 
        }
    );
 *   
 * 
 */


ScrollPane = Class.create
({
    initialize: function(element, options)
    {
        if (!$(element))
        {
            throw('PhotoScroller.initialize() : You must pass a container element. Refer to the PhotoScroller documentation.');
        }
        
        this.pane               = $(element);
        this.pane_content       = this.pane.down('div') || this.createPaneContent();
        this.options            = Object.extend
        (
            {
                scale_amount: 0.8,
                duration: 1,
                transition: Effect.Transitions.sinoidal,
                item_unfocused_opacity: 0.5,
                item_focused_opacity: 1.0,
                item_is_clickable: true,
                scaled_element: this.pane_content.firstDescendant(),
                navigation:
                {
                    next: null,
                    previous: null,
                    disabled_css_classname: ''
                }
            }, 
            options || {}
        );
        
        this.options.scale_amount   = (this.options.scale_amount > 1) ? 0.8 : this.options.scale_amount;  
        this.items                  = new Hash();
        this.pointer                = null;
        this.unique_id              = Math.round(Math.random()*100);
        this.effects                = [];
        this.next_link              = null;
        this.previous_link          = null;
        this.nav_disabled           = false;
        this.center_point           = 
        {
            horizontal: this.pane.getWidth() / 2,
            vertical: this.pane.getHeight() / 2
        };
        this.queue                  = 
        {
            scope: 'ScrollPane'+this.unique_id,
            position: 'end'
        };
        
        // Bound event handler references
        this.onNavigationNext       = this.next.bindAsEventListener(this);
        this.onNavigationPrevious   = this.previous.bindAsEventListener(this);
        this.onWindowResize         = this.catchWindowResize.bindAsEventListener(this);
        this.onWindowResized        = this.doWindowResize.bindAsEventListener(this);
        
        // Method aliases
        this.back                   = this.next;
        this.ahead                  = this.prev = this.previous;
        
        // Start it up
        this.setup();
    },


    /* 
     * setup()
     *
     * Initializes each image within the container and performs all initialization tasks
     * 
     * @return void
     *
     */
    setup: function()
    {
        // Position the pane
        this.pane.makePositioned();

        // Watch window for resize
        Event.observe(window, 'resize', this.onWindowResize);
        
        // Set overflow property of the container
        this.pane.setStyle({ overflow: 'hidden' });
        
        var sp              = this;
        
        // Set up each child element in the pane_content
        this.pane_content.childElements().each
        (
            function(item, index)
            {
                item.identify();
                var height = item.getHeight();
                
                item.setStyle
                ({ 
                    marginTop: ((height - (height*sp.options.scale_amount)) / 2) + 'px',
                    height: sp.options.scale_amount*100+'%'
                });
                
                
                // Add Opacity effect to the effects array - error if we pass a scaled_element ???
                if (item !== sp.options.scaled_element)
                {
                    sp.effects.push
                    (
                        new Effect.Opacity(item, { from: item.getOpacity(), to: sp.options.item_unfocused_opacity, sync: true })
                    );
                }
                
                // Observe click, if options allow it
                if (sp.options.item_is_clickable)
                {
                    Element.observe(item, 'click', sp.move.bindAsEventListener(sp, item));
                }

                // Add a custom event to allow use of fire() for next/prev links
                Element.observe(item, 'ScrollPane:move', sp.move.bindAsEventListener(sp, item));
            }
        );
        
        this.calculateItemInformation();
        
        this.initializeNavigation();
        
        new Effect.Parallel
        (
            $A(this.effects),
            {
                duration: this.options.duration,
                queue: this.queue,
                afterFinish: function()
                {
                    sp.clearEffectsCache();

                    // Fire the first element's custom event, by default, it's the first item; change with options.scaled_element
                    sp.options.scaled_element.fire('ScrollPane:move');
                }
            }
        );
    },

    
    /* 
     * createPaneContent()
     *
     * If there is no pane_content, this creates it, and moves the items from the main div inside it 
     * 
     * @return HTMLElement (pane_content)
     *
     */
    createPaneContent: function()
    {
        var e   = new Element('div', { id: 'ScrollPaneContent' });
        this.pane.insert({ top: e });
        
        this.pane.childElements().each
        (
            function(item)
            {
                if (item == e)
                {
                    return;
                }
                    
                var clone = item.cloneNode(false);
                item.remove();
                e.insert(clone);
            }
        );
        
        return e;
    },

    
    /* 
     * initializeNavigation()
     *
     * shows the next/previous links if passed in options - 
     * they should be display:none for non-JS users, as they
     * have no functionality without JS
     * 
     * @return void
     *
     */
    initializeNavigation: function()
    {
    
        if ($(this.options.navigation.next))
        {
            this.next_link = $(this.options.navigation.next);
            Element.observe(this.next_link, 'click', this.onNavigationNext);
            this.next_link.writeAttribute('href', 'javascript:void(0)');
            new Effect.Appear(this.next_link);
        }

        if ($(this.options.navigation.previous))
        {
            this.previous_link = $(this.options.navigation.previous);
            Element.observe(this.previous_link, 'click', this.onNavigationPrevious);
            this.previous_link.writeAttribute('href', 'javascript:void(0)');
            new Effect.Appear(this.previous_link);
        }        
    },
    
    
    /* 
     * toggleNavigation()
     *
     * Checks to see if the next/prev links should be disabled 
     * Temporarily disables the navigation while the class is animating the move 
     * 
     * @return void
     *
     */
    toggleNavigation: function()
    {
        if ($(this.previous_link))
        {
            if (this.pointer == this.pane_content.firstDescendant().identify())
            {
                Element.stopObserving(this.previous_link, 'click', this.onNavigationPrevious);
                this.previous_link.addClassName(this.options.navigation.disabled_css_classname);
            }
            else
            {
                this.previous_link.removeClassName(this.options.navigation.disabled_css_classname);
            }
        }

        if ($(this.next_link))
        {
            if (this.pane_content.childElements().last().identify() == this.pointer)
            {
                Element.stopObserving(this.next_link, 'click', this.onNavigationNext);
                this.next_link.addClassName(this.options.navigation.disabled_css_classname);
            }
            else
            {
                
                this.next_link.removeClassName(this.options.navigation.disabled_css_classname);
            }        
        }
        
        if (!this.nav_disabled)
        {
            if (this.previous_link)
            {
                this.nav_disabled = true;
                Element.stopObserving(this.previous_link, 'click', this.onNavigationPrevious);
            }
            
            if (this.next_link && !this.nav_disabled)
            {
                this.nav_disabled = true;
                Element.stopObserving(this.previous_link, 'click', this.onNavigationPrevious);
            }
            
        }
        else
        {
            if (this.previous_link)
            {
                this.nav_disabled = false;
                Element.observe(this.previous_link, 'click', this.onNavigationPrevious);
            }
            
            if (this.next_link)
            {
                this.nav_disabled = false;
                Element.observe(this.next_link, 'click', this.onNavigationNext);
            }
        }
         
    },
    
    
    /* 
     * move()
     *
     * Sets up the effects, and moves the clicked image to the center of the container 
     * 
     * @param event         - the mouse click event
     * @param recentering   - when called from catchWindowResize 
     * @return void
     *
     */
    move: function(event, recentering)
    {
        var sp = this;
        
        
        if (event.memo.recentering != undefined && event.memo.recentering)
        {
            var recentering         = true;
        }
        else
        {
            var old_element_id      = this.pointer;
            this.pointer            = Event.element(event).identify();
            var new_element_id      = this.pointer;
            var recentering         = false;
            
            this.toggleNavigation();
            
            if(new_element_id == old_element_id)
            {
                return;
            }
            
            if (old_element_id !== null && $(old_element_id))
            {
                this.effects.push
                (
                    new Effect.Morph
                    (
                        old_element_id,
                        {
                            style: 'height:'+(this.options.scale_amount*100)+'%; margin-top:'+this.items.get(old_element_id).get('vertical_centering')+'px;',
                            sync: true
                        }
                    ),
                        
                    new Effect.Opacity
                    (
                        old_element_id, 
                        { 
                            from: $(old_element_id).getOpacity(), 
                            to: this.options.item_unfocused_opacity,
                            sync: true
                        }
                    )
                );
            }
            
            // Add event element to effects array
            this.effects.push
            (
                new Effect.Morph
                (
                    new_element_id,
                    {
                        style: 'height:100%; margin-top:0px;',
                        sync: true
                    } 
                ),
    
                new Effect.Opacity
                (
                    new_element_id, 
                    { 
                        from: $(new_element_id).getOpacity(), 
                        to: this.options.item_focused_opacity,
                        sync: true
                    }
                )
            );
        }
        
        
        // Add centering effect
        new Effect.Move
        (
            this.pane_content, 
            { 
                x: this.getCenterPoint(this.pointer), 
                y: 0,
                afterFinish: function()
                {
                    sp.toggleNavigation();
                }
            }
        );
        
        // Do the effects, all in parallel
        new Effect.Parallel
        (
            $A(this.effects),
            {
                duration: ((recentering) ? 0.1 : this.options.duration),
                transition: ((recentering) ? Effect.Transitions.none : this.options.transition),
                queue: this.queue,
                afterFinish: function()
                {
                    sp.clearEffectsCache();
                }
            }
        );
     
    },


    /* 
     * next()
     *
     * Handles events from this.next_link element 
     * 
     * @return void
     *
     */
    next: function()
    {   
        if($(this.pointer).next())
        {
            $(this.pointer).next().fire('ScrollPane:move');
        }
    }, 


    /* 
     * previous()
     *
     * Handles events from this.previous_link element 
     * 
     * @return void
     *
     */
    previous: function()
    {
        if($(this.pointer).previous())
        {
            $(this.pointer).previous().fire('ScrollPane:move');
        }
    },


    /* 
     * scrollTo()
     *
     * Scrolls the pane to the specified element index (NOT id) 
     * 
     * @param index - the index of the element to scroll to (0-based, on this.pane_content.childElements())
     * @return void
     *
     */
    scrollTo: function(index)
    {
        if(this.pane_content.down(index))
        {
            this.pane_content.down(index).fire('ScrollPane:move');
        }
    },

    
    /* 
     * getCenterPoint
     *
     * Determines the center point of the photo container div, and centers the passed element within it. 
     * 
     * @param element_id    - id of element to center in the container
     * @return Number       - the center point of the container, relative to its parent
     *
     */
    getCenterPoint: function(element_id, old_element_id)
    {
        return this.center_point.horizontal - this.pane_content.positionedOffset().left - this.items.get(element_id).get('center').get('horizontal');
    },

    
    /* calculateItemInformation
     * 
     * 
     * 
     */
    calculateItemInformation: function()
    {
        var sp = this;
        
        this.pane_content.childElements().each
        (
            function(item, index)
            {
                var item_dims       = item.getDimensions();
                var item_pos        = item.positionedOffset();
                
                var item_margin     = 
                {
                    horizontal:             parseFloat(item.getStyle('marginLeft')) + parseFloat(item.getStyle('marginRight')),
                    vertical:               parseFloat(item.getStyle('marginTop')) + parseFloat(item.getStyle('marginBottom'))
                };
                
                var item_padding    = 
                {
                    horizontal:             parseFloat(item.getStyle('paddingLeft')) + parseFloat(item.getStyle('paddingRight')),
                    vertical:               parseFloat(item.getStyle('paddingTop')) + parseFloat(item.getStyle('paddingBottom'))
                };
                
                var item_border     =       
                {
                    horizontal:             ((item.getStyle('borderLeftStyle') == 'none') ? 0 : parseFloat(item.getStyle('borderLeftWidth'))) + ((item.getStyle('borderRightStyle') == 'none') ? 0 : parseFloat(item.getStyle('borderRightWidth'))),
                    vertical:               ((item.getStyle('borderTopStyle') == 'none') ? 0 : parseFloat(item.getStyle('borderTopWidth'))) + ((item.getStyle('borderBottomStyle') == 'none') ? 0 : parseFloat(item.getStyle('borderBottomWidth')))
                };
                
                var id          = item.identify();
                var scale       = sp.options.scale_amount;
                var current_id  = (sp.pointer != undefined) ? sp.pointer : null;
                
                // clear the old key
                sp.items.unset(id);
                
                // Store item information
                sp.items.set
                (
                    id,
                    new Hash
                    ({
                        width: item_dims.width + item_margin.horizontal + item_padding.horizontal + item_border.horizontal,
                        height: item_dims.height + item_margin.vertical + item_padding.vertical + item_border.vertical,
                        vertical_centering: (item_dims.height - (item_dims.height*scale)) / 2,
                        center: new Hash({
                            horizontal: parseFloat(item_pos.left) + (((current_id == id) ? item_dims.width : (item_dims.width/scale)) / 2) + (item_padding.horizontal / 2) + (item_border.horizontal / 2),
                            vertical: parseFloat(item_pos.top) + (((current_id == id) ? item_dims.height : (item_dims.height/scale)) / 2) + (item_padding.vertical / 2) + (item_border.vertical / 2)
                        })
                    })
                );
            }
        );
    },
    
    /*  
     * clearEffectsCache
     *
     * clears the internal effects array
     *
     * @return void
     *
     */
    clearEffectsCache: function()
    {
        this.effects.clear();
    },

    
    /*  
     * catchWindowResize()
     *
     * Watches for window resize; fades content, and sets window.mouseover listener to resize
     * the pane and recenter the current item
     *
     * @return void
     * 
     */
    catchWindowResize: function()
    {
        Event.observe(window, 'mouseover', this.onWindowResized);
    },
    
    /*
     * doWindowResize
     * 
     * recenters the current scaled element to the center of the container
     * 
     */
    doWindowResize: function()
    {
        Event.stopObserving(window, 'mouseover', this.onWindowResized);
        
        var dims                        = this.pane.getDimensions();
        this.center_point.horizontal    = dims.width / 2; 
        this.center_point.vertical      = dims.height / 2;
        this.calculateItemInformation();
        
        $(this.pointer).fire('ScrollPane:move', { recentering: true });
    }
    
    
});

//-->