/*****************************************************************************
 *
 * Ticker Class
 *
 * @file: js/general/ticker/ticker.js
 * @author: Michael Conroy (mconroy@buffalo.edu)
 * @lang: Javascript
 * 
 * @description: A class for creating a ticker that automatically moves or has user defined
 * 	handles for operation.
 *
 * @required: Prototype Javascript Framework, Scriptaculous Effect Library,
 * 	prototype/ext/element_animation_methods.js
 *
 ****************************************************************************/


/*== Class ===========================================================*/

var ticker = Class.create({
    
    /*-- Variables ---------------------------------------------------------------------------------*/
    
    _ticker: null, // ticker object element
    _children: null, // ticker children
    _container: null, // container object element
    _tickerWidth : 0, // width of ticker object
    _tickerHeight : 0, // height of the ticker object
    _containerWidth: 0, // width of container object
    _containerHeight: 0, // height of container object
    _pe: null, // periodical executer
    
    _tally: 0, // used by internal animation functions
    _num: 0, // used by internal animation functions
    
    _options: {
	container: 'ticker_container', // id of container
	containerClass: 'ticker_container_class', // class of ticker container
	ticker: 'info', // id of ticker
	delay: 5, // number of seconds to delay start of animation iteration
	type: 'rotate block', // type of ticker animation
	startup: true, // start the ticker right away?
	mouseovers: false, // set up interaction with mouse
	controls: true // set up interaction with controls
    },
    _controls: { // images to use as controls for ticker operations
	first: null, // left or top control
	last: null, // right or bottom control
	fControl: null, // first control object
	lControl: null // last control object
    },
    
    
    /*-- Constructor ------------------------------------------------------------------------------*/
    
    initialize: function(opts,controls){
	this.setOptions(opts);
	this.setControls(controls);
	
	this._ticker = $(this._options.ticker);
	// this is a work-around to get the ticker's true  width as the browser width is interfering with querying the ticker object itself for the width
	this._children = this._ticker.childElements();
	this._children.each(function(el){
	    this._tickerWidth = (this._tickerWidth + el.getWidth());
	}.bind(this));
	this._tickerHeight = this._ticker.getHeight();
	
	// wrap the ticker with a container element, the ticker will move the container will stay stationary
	this._container = new Element('div',{'id':this._options.container,'class':this._options.containerClass});
	this._ticker.wrap(this._container);
	
	this._containerWidth = this._container.getWidth();
	this._containerHeight = this._container.getHeight();
	this._ticker.makePositioned(); // make relatively positioned
	
	// mouseovers ?
	if(this._options.mouseovers){ // setup mouse iteraction observers on the ticker
	    this._container.observe('mouseover',function(){
		if(this._pe != null){ this._pe.stop(); }
	    }.bind(this));
	    
	    this._container.observe('mouseout',function(){
		if(this._pe != null){ this._pe.registerCallback(); }
	    }.bind(this));
	}
	
	// ticker controls ?
	if(this._options.controls && (this._controls != null)){ // setup controls for ticker operations
	    if(this._controls.last != null){
		var controlDiv = new Element('div',{'id':'lControl','class':'control'});
		this._controls.lControl = new Element('img',{'src':this._controls.last}).wrap(controlDiv);
		this._container.insert({before : this._controls.lControl});
	    }
	    if(this._controls.first != null){
		var controlDiv = new Element('div',{'id':'fControl','class':'control'});
		this._controls.fControl = new Element('img',{'src':this._controls.first}).wrap(controlDiv);
		this._container.insert({before : this._controls.fControl});
	    }
	}
	
	if(this._options.startup){ this.go(); } // automatically start the ticker?
    }, // end initialize
    
    
    /*-- Methods ----------------------------------------------------------------------------------*/
    
     /**
     * Sets object options.
     *
     * @param		opts			object
     * @return		none
     */
    setOptions: function(opts){
        if(opts != null){
            this._options = opts;
        }
    }, // end setOptions
    
    /**
     * Sets up controls for the ticker object.
     *
     * @param		controls		object
     * @return		none
     */
    setControls: function(controls){
	if(controls != null){
	    this._controls = controls;
	}
    }, // end setControls
    
    /**
     * Starts the ticker animation.
     *
     * @param		type			string, type of animation to inact
     * @return		none
     */
    go: function(type){
	// if container is bigger than the ticker we don't need to do anything, else animate ticker
	if(this._tickerWidth > this._containerWidth){
	    // set type of ticker animation
	    if(type != null){
	        this._options.type = type;
	    }
	
	    switch(this._options.type){
		case 'faze':
		    this._fazer();
		    break;
	        case 'rotate':
		    this._rotate_children();
		    break;
		case 'rotate block':
		    this._rotate_children_block();
		    break;
		case 'default': // default: scroll animation forward the size of the ticker till the end then scroll back
		default:
		    this._go_forward_scroll_back();
		    break;
	    }
	}
    }, // end go
    
    /*-- Animations --------------------------------------------------------------------------------*/
    
    /**
     * Scrolls ticker to the left progressively till the ticker's right edge is visible then the
     * ticker is scrolled to the right all the way back to the starting point. Works best with
     * inline tickers, if controls are available scrolling left and right can be user controlled.
     *
     * @param			none
     * @return			none
     */
    _go_forward_scroll_back: function(){
	if(this._options.controls){ // if we have ticker controls
	    this._controls.fControl.observe('click',function(){
		var l = this._ticker.getStyle('left');
		l = l ? parseInt(l.truncate((l.length - (l.length - 2)))).abs() : 0;
		if(l >= this._containerWidth){ // if we are left of the ticker container
		    this._ticker.od_move('right',this._containerWidth,true); // move ticker right
		}
	    }.bind(this));
	    this._controls.lControl.observe('click',function(){
		var l = this._ticker.getStyle('left');
		l = l ? parseInt(l.truncate((l.length - (l.length - 2)))).abs() : 0; // l is the number of pixels to the left
		if((l + this._containerWidth) < this._tickerWidth){ // if the amount left plus the container width is less than ticker width
		    this._ticker.od_move('left',this._containerWidth,true); // move ticker left
		}
	    }.bind(this));
	}else{ // just run the scroll automatically
	    this._pe = new PeriodicalExecuter(function(){
	        var l = this._ticker.getStyle('left');
	        l = l ? parseInt(l.truncate((l.length - (l.length - 2)))).abs() : 0;
		    
	        if((l + this._containerWidth) > this._tickerWidth){
		    this._ticker.od_move('right',l,true); // scroll ticker all the way back
		}else{
		    this._ticker.od_move('left',this._containerWidth,true); // scroll ticker left
		}
	    }.bind(this),this._options.delay); // end PeriodicalExecuter
	}
    }, // end _go_forward_scroll_back
    
    /**
     * Scrolls ticker to the left one child at a time and removes the child and places it at the
     * end of the ticker.  Ticker continuously moves left. Works best with inline tickers.
     *
     * @param			none
     * @return 			none
     */
    _rotate_children: function(){
	this._pe = new PeriodicalExecuter(function(){
	    var child = this._ticker.childElements()[0];
	    this._tally = child.getWidth();
	    
	    this._ticker.od_move('left',this._tally,true,function(){
		this._ticker.insert(this._ticker.childElements()[0].remove());
		this._ticker.setStyle({left: '0px'});
		this._tally = 0;
	    }.bind(this));
	    
	}.bind(this),this._options.delay); // end PeriodicalExecuter
    }, // end _rotate_children
    
    /**
     * Same as _rotate_children except that its does more than one child at a time.  All the
     * completely visible children get removed and placed at the end of the ticker while the
     * ticker moves left continuously. Works best with inline tickers.  The action can be
     * user controlled if controls are available, then the action can go forward and backward
     * as the user sees fit.
     *
     * @param			none
     * @return			none
     */
    _rotate_children_block: function(){
	if(this._options.controls){
	    this._controls.fControl.observe('click',function(){ // first control
		this._children = this._ticker.childElements();
		var ubound = this._children.size();
		for(i=(ubound-1);i>=0;i--){
		    this._tally = this._tally + this._children[i].getWidth();
		    this._num++;
		    if(this._tally > this._containerWidth){
			this._num--;
			this._tally = this._tally - this._children[i].getWidth();
			break;
		    }
		}
		//remove children off the back and add them to the front
		this._ticker.setStyle({left: '-' + this._tally + 'px'});
		for(i=(ubound-1);i>=(ubound - this._num);i--){
		    var first = this._ticker.childElements()[0];
		    var last = this._ticker.childElements()[(ubound-1)].remove();
		    first.insert({before:last});
		}
		this._ticker.od_move('right',this._tally,true,function(){
		    this._ticker.setStyle({left: '0px'});
		    this._tally = 0;
		    this._num = 0;
		}.bind(this));
	    }.bind(this));
	    this._controls.lControl.observe('click',function(){ // last control, see the periodical executer below, same thing
	        this._children = this._ticker.childElements();
	        var ubound = this._children.size();
	        for(i=0;i<ubound;i++){
		    this._tally = this._tally + this._children[i].getWidth();
		    this._num++;
		    if(this._tally > this._containerWidth){
			this._num--;
			this._tally = this._tally - this._children[i].getWidth();
			break;
		    }
		}
		this._ticker.od_move('left',this._tally,true,function(){
		    for(i=0;i<this._num;i++){
			this._ticker.insert(this._ticker.childElements()[0].remove());
		    }
		    this._ticker.setStyle({left: '0px'});
		    this._tally = 0;
		    this._num = 0;
		}.bind(this));
	    }.bind(this));
	}else{
	    this._pe = new PeriodicalExecuter(function(){
	        // find the number of children that currently fit in the viewable area of the container
	        this._children = this._ticker.childElements();
	        var ubound = this._children.size();
	        for(i=0;i<ubound;i++){
		    this._tally = this._tally + this._children[i].getWidth();
		    this._num++;
		    if(this._tally > this._containerWidth){
			this._num--;
			this._tally = this._tally - this._children[i].getWidth();
			break;
		    }
		}
		// move the ticker the width of the number of children that are viewable then remove the children and append them to the end
		this._ticker.od_move('left',this._tally,true,function(){
		    for(i=0;i<this._num;i++){
			this._ticker.insert(this._ticker.childElements()[0].remove());
		    }
		    this._ticker.setStyle({left: '0px'});
		    this._tally = 0;
		    this._num = 0;
		}.bind(this));
	    }.bind(this),this._options.delay);
	}
    }, // end _rotate_children_block

    /**
     * Fades out the visible elements the fades in the next set of elements.
     *
     * @param			none
     * @return			none
     */
    _fazer: function(){
	this._fazer_helper();
	
	this._pe = new PeriodicalExecuter(function(){
	    var resetTicker = function(){
		this._fazer_helper();
		new Effect.Appear(this._ticker);
	    }.bind(this);
	    
	    new Effect.Parallel([new Effect.Fade(this._ticker)],{afterFinish: resetTicker});
	}.bind(this),this._options.delay);
    }, // end _fazer
    
    _fazer_helper: function(){
	// set up ticker, remove all children
	this._ticker.childElements().each(function(child){ child.remove(); });
	
	// find out how many children will fit within the container's width
	while(this._tally < this._containerWidth){
	    var el = this._children.shift()
	    this._ticker.insert(el);
	    this._children.push(el);
	    this._tally = this._tally + el.getWidth();
	}
	this._tally = 0;
    }, // end _fazer_helper
    
    /**
     * Ticker moves upward.
     *
     * @param			none
     * @return			none
     */
    _rise_up: function(){
	this._tally = this._ticker.childElements()[0].getHeight();
	this._pe = new PeriodicalExecuter(function(){
	    var t = this._ticker.getStyle('top');
	    t = t ? parseInt(t.truncate((t.length - (t.length - 2)))).abs() : 0;
	    
	    if((t + this._containerHeight) > this._tickerHeight){
		this._ticker.od_move('down',t,true);
	    }else{
		this._ticker.od_move('up',this._tally,true);
	    }
	}.bind(this),this._options.delay);
    } // end _rise_up
    
}); // end class ticker