/**
 * Slideshow.js
 *
 * A dojo-based widget component used to cycle display of a given
 * list of DOM nodes.
 */
dojo.provide("atemi.widget.Slideshow");



dojo.declare("atemi.widget.Slideshow", null, {
    /**
     * @param DOMNode|string slideshowContainer
     * @param args object
     */
    constructor: function(slideshow, args) 
    {
        // Keep a reference to the containing object so we can refer to its style properties
        // (width, height, position, etc)
        this.container = slideshow;

        // Default number of milliseconds for frame animations
        this.transitionInterval = 1000;
        
        // Default number of milliseconds to show the current frame for
        this.duration = 5000;

        // Default transition mode
        this.transitionMode = 'fade';

        // Mix in the given arguments, allowing default properties
        // to be overridden
        dojo.mixin(this, args);

        // Create the timer that'll be used for autoplay
        this.autoplayTimer = new atemi.util.Timer(this.duration, 1);
        dojo.connect(this.autoplayTimer, "timerDidComplete", dojo.hitch(this, this.autoplayTimerDidFire));

        // If the slideshow container node id was provided instead of the node
        // itself, fetch it now.
        if (dojo.isString(slideshow)) {
            slideshow = dojo.byId(slideshow);
        }

        // Listen for mouseover event to disable autoplay while mouse is over
        dojo.connect(slideshow, 'onmouseenter', dojo.hitch(this, function() {
            // Here we stop the timer, but don't call stop() because the slideshow is
            // effectively still playing but is being temporarily paused
            this.autoplayTimer.stop();
            this.paused = true;
        }));

        // Listen for mouseout event to re-enable autoplay when mouse leaves
        dojo.connect(slideshow, 'onmouseleave', dojo.hitch(this, function() {
            this.paused = false;
            if (this.playing) {
                this.play();
            }
        }));

        // If a child selector was provided as an argument,
        // use it to query for the slideshow frames.
        // Otherwise use the immediate children of the slideshow element.
        if (this.childSelector) {
         this.frames = dojo.query(this.childSelector);
        } else {
          this.frames = slideshow.children;
        }
              
        // Initially position and hide all slideshow frames
        dojo.forEach(this.frames, function(frame) {
            dojo.style(frame, 'position', 'absolute');
            dojo.style(frame, 'display', 'none');
        });

        // By default, set focus to the first frame
        this.setFocusIndex(0);
    },

    //-------------------------------------------------------------------------

    /**
     * @param DOMNode|string navigator
     */
    setNavigator: function(navigator) 
    {
        // If the slideshow container node id was provided instead of the node
        // itself, fetch it now.
        if (dojo.isString(navigator)) {
            navigator = dojo.byId(navigator);
        }

        // Set up a click handler on each navigation item to control
        // the slideshow's focusIndex
        dojo.forEach(navigator.children, dojo.hitch(this, function(navigationItem, i) {
            dojo.connect(navigationItem, 'onclick', dojo.hitch(this, function(event) {
                this.setFocusIndex(i, true);
            }));
        }));
    },

    //-------------------------------------------------------------------------

    width: function()
    {
        return dojo.contentBox(this.container).w;
    },


    height: function()
    {
        return dojo.contentBox(this.container).h;
    },

    paddingTop: function()
    {
        return dojo.style(this.container, "paddingTop");
    },

    paddingLeft: function()
    {
        return dojo.style(this.container, "paddingLeft");
    },

    //-------------------------------------------------------------------------
    
    /**
     * @param value int Index of frame to display
     */
    setFocusIndex: function(value, animated) 
    {
        // Ensure that the given index is valid
        if (
            value < 0 || 
            value >= this.frames.length || 
            value == this.focusIndex ||
            this.transitionInAnimation ||
            this.transitionOutAnimation
        ) {
            return;
        }

        // Reset the playback timer since we want the newly
        // focused frame to be displayed for the full duration.
        this.autoplayTimer.reset();

        // Notify delegate that the focusIndex is going to change
        this.focusWillChange(value);

        // If the transition is to be animated, use dojo's convenient fade
        // facility to fade the current focused frame out and the
        // new focused frame in.
        if (animated) {
            this.transitionTo(value);
        } else {
            for (var i=0; i < this.frames.length; ++i) {
                var frame = this.frames[i];
                if (value == i) {
                    dojo.style(frame, 'display', 'block');
                } else {
                    dojo.style(frame, 'display', 'none');
                }
            }

            // Notify the connected listeners that the focus has changed.
            this.focusDidChange(value);
        }

        // Updated the stored focus index
        this.focusIndex = value;
    },


    /**
     * Called before focus changes 
     * @param int focusIndex Index of frame that will come into focus
     */
    focusWillChange: function(focusIndex)
    {
    },


    /**
     * Called after focus has changed 
     * @param int focusIndex Index of frame that came into focus
     */
    focusDidChange: function(focusIndex)
    {
    },


    //-------------------------------------------------------------------------
    // Transitions
    //-------------------------------------------------------------------------
    /**
     * Transition to the given frameIndex using the current transitionMode
     */ 
    transitionTo: function(frameIndex)
    {
        // If there is currently another frame in focus,
        // fade it out before letting the new one fade in.
        var currentFrame = this.getFrame(this.focusIndex);
        if (currentFrame) {
            this.transitionOut(currentFrame);
        } 

        var nextFrame = this.getFrame(frameIndex);
        this.transitionIn(nextFrame);
    },


    transitionOut: function(frame)
    {
        // TODO: Cancel the current animation if one is active
        
        var animation = dojo.animateProperty({
            node: frame,
            duration: this.transitionInterval,
            onEnd: dojo.hitch(this, function() {
                this.transitionOutDidComplete(frame);
            })
        });

        switch (this.transitionMode) {
            case 'fade':
                animation.properties = { opacity: 0 };
                break;
            case 'down':
                animation.properties = {
                    top: { start: 0 + this.paddingTop(), end: this.height(), units:'px' }
                }
                break;
            case 'left':
                animation.properties = {
                    left: { start: 0 + this.paddingLeft(), end: -this.width(), units:'px' }
                }
                break;
            case 'right':
                animation.properties = {
                    left: { start: 0 + this.paddingLeft(), end: this.width(), units: 'px' } 
                }
                break;
            default:
                console.log('Unrecognized transition mode');
        }

        this.transitionOutAnimation = animation;
        this.transitionOutAnimation.play();
    },


    transitionOutDidComplete: function(frame)
    {
        // Indicates that there is no longer an animation in progress
        this.transitionOutAnimation = null;

        // The frame has been transitioned out, so hide it.
        dojo.style(frame, 'display', 'none');
    },    


    transitionIn: function(frame)
    {
        // Let the frame being transitioned in be visible
        dojo.style(frame, 'display', 'block');
        
        var animation = dojo.animateProperty({
            node: frame,
            duration: this.transitionInterval,
            onEnd: dojo.hitch(this, this.transitionInDidComplete)
        });

        switch (this.transitionMode) {
            case 'fade':
                // Initialize the new frame's opacity to zero so it can fade in
                dojo.style(frame, 'opacity', 0);
                animation.properties = { opacity: 1 };
                break;
            case 'down':
                // Initialize the new frame's position to above the visible area
                dojo.style(frame, 'top', -this.height());
                animation.properties = {
                    top: { start: -this.height(), end: 0 + this.paddingTop(), units:'px' }
                }
                break;
            case 'left':
                // Initialize the new frame's position to the right of the visible area
                dojo.style(frame, 'left', this.width());
                animation.properties = {
                    left: { start: this.width(), end: 0 + this.paddingLeft(), units:'px' }
                }
                break;
            case 'right':
                // Initialize the new frame's position to the left of the visible area
                dojo.style(frame, 'left', -this.width());
                animation.properties = {
                    left: { start: -this.width(), end: 0 + this.paddingLeft(), units:'px' }
                }
                break;
            default:
                console.log('Unknown transition mode');
        }

        this.transitionInAnimation = animation;
        this.transitionInAnimation.play();
    },


    transitionInDidComplete: function(frame)
    {
        // Indicates that there is no longer an animation in progress
        this.transitionInAnimation = null;

        // Notify the connected listeners that the focus has changed.
        this.focusDidChange(this.focusIndex);

        // If autoplay is enabled, trigger load of next frame
        if (this.playing && !this.paused) {
            this.autoplayTimer.start();
        }
    },


    /**
     * Activate the timer that controls automatic playback.
     */
    play: function() 
    {
        this.autoplayTimer.start();
        this.playing = true;
    },



    /**
     * Called in response to the autoplay timer firing
     */
    autoplayTimerDidFire: function() 
    {
        this.next();
    },


    //-------------------------------------------------------------------------

    /**
     * Invalidates the timer that controls automatic playback.
     */
    stop: function() 
    {
        this.autoplayTimer.stop();
        this.playing = false;

    },

    //-------------------------------------------------------------------------

    /**
     * Advances slideshow to next frame.
     * If on the last frame, wraps to the beginning.
     */
    next: function() 
    {
        var nextFocusIndex = (this.focusIndex + 1) % this.frames.length;
        this.setFocusIndex(nextFocusIndex, true);
    },

    //-------------------------------------------------------------------------

    /**
     * Returns the frame with the given index, or null if the index
     * is out of bounds. 
     */
    getFrame: function(frameIndex) 
    {
        if (0 <= frameIndex && frameIndex < this.frames.length) {
            return this.frames[frameIndex];
        }

        return null;
    }
});

