//------------------------------------------------------------------------------------------
// LIBRARY: 
// Veer.Ticker
//
// DESCRIPTION:
// Implements a fully JavaScript based ticker that can show items from an Atom feed.
// The items feature animation from one element to another, and are clickable.
//
// REQUIRED HEAD TAGS: 	
// <%= IncludeUri.MochiKit %>
 
// <script language="javascript" type="text/javascript" src="<%=IncludeUri.Path/veer.animation.js"></script>
 
// <script language="javascript" type="text/javascript" src="<%=IncludeUri.Path/veer.ticker.js"></script>
 
//
// DEPENDENCIES:
// MochiKit.js.
// http://flont.veer.com to provide rendering for the ticker items.
// 
//
// NOTES:
// 1) The ticker will not run if MochiKit.js has not been loaded. It will make the ticker
//    invisible if MochiKit is not present.
// 2) The ticker will log errors to the JavaScript console if the browser supports it.
// 3) Older browsers DO NOT support MochiKit.js, it is up to the author to detect that
//    and not load MochiKit.js in that case. However, this script can still be loaded and
//    the ticker will auto hide if it does not find MochiKit. The first tag in the
//    required tags section takes care of this conditional for you.
// 4) The veer.animation.js is only required if you want to make use of animation easing
//    other than linear.
// 5) The XmlHttpRequestObject **REQUIRES** that the feed come from the same domain
//    as the page hosting the ticker. That means that www.veer.com DOES NOT 
//    EQUAL ideas.veer.com. 
//------------------------------------------------------------------------------------------
 
if (typeof(Veer) == 'undefined') {
    Veer = {};
}
 
 
// Public - Constructor - Creates a new instance of a Veer.Ticker according to settings. When
// settings is not provided, the new instance will have suitable defaults. Settings is an
// anonymous object that has one or mre of the following properties:
//
// transition[=Veer.Ticker.TRANSITION_SLIDEUP]
//		Specifies how to transition from one ticker item to another.
//
// transitionDuration[=Veer.Ticker.DEFAULT_DURATION_TRANSITION]
//		Specifies how many seconds it takes to make the transition from one ticker item to
//		another.
//
// holdDuration[=Veer.Ticker.DEFAULT_DURATION_HOLD]
//		Specifies the number of seconds that a ticker item will hold still before transitioning
//		to the next item.
//
// tickerFeedUrl[=Veer.Ticker.DEFAULT_FEED]
//		Specifies the Atom feed that will provide ticker titles and links.
//
// backgroundColor[=Veer.Ticker.DEFAULT_COLOR_BACKGROUND]
//		Specifies the background color of the ticker text and the element that contains the ticker.
//      Must be specified as a hexadecimal string of the form RRGGBB.
//
// foregroundColor[=Veer.Ticker.DEFAULT_COLOR_FOREGROUND]
//		Specifies the foreground color of the ticker text that contains the ticker.
//      Must be specified as a hexadecimal string of the form RRGGBB.
//
// mouseOverColor=[Veer.Ticker.DEFAULT_COLOR_MOUSEOVER]
//      Specifies the color of the ticker text when the mouse moves over the ticker item.
//      Must be specified as a hexadecimal string of the form RRGGBB.
//
// tickerId
//		Required. Either the string id, or the element itself, that contains the ticker. When
//		the nameis provided, the actual element will be looked up.
//
// tickerFeedUrl[=Veer.Ticker.DEFAULT_FEED]
//      The full URL to the Atom feed that will supply the ticker with items.
//
// easingFunction[=Veer.Ticker.DEFAULT_EASING]
//      The function to use for easing the ticker ticker text into visibility. When left
//      unspecified, defaults to linear easing that simply moves the item at a fixed rate 
//      over time.
//
// To make the ticker actually do it's thing, you must call the start() method on the instance.
// The ticker will begin to show items as soon as the feed is loaded.
//
Veer.Ticker = function(settings) {
	// Prerequisite checks - assume we can run but degrade gracefully
	this.isRunnable = (typeof(MochiKit) != 'undefined');
	
	// Obtain the ticker element and container in a Veer-free and MochiKit-free way
	this.tickerDom = document.getElementById(settings.tickerId);
    if (settings.tickerContainerId) this.tickerContainerDom = document.getElementById(settings.tickerContainerId);
 
	if (this.isRunnable) {
		
		// Preserve our Object Oriented sanity by binding 'this' to all of our methods.
		MochiKit.Base.bindMethods(this);
 
        // Enable logging using MochiKit console logging
		this.log = MochiKit.Logging.log;
	
		if (!this.tickerDom) {
			this.isRunnable = false;
			this.log('settings.tickerId does not exist', settings.tickerId);
		} else {
			var style = this.tickerDom.style;
			if (!style.height) {
				this.isRunnable = false;
				this.log('settings.tickerId must have style height specified');
			}
			if (!style.width) {
				this.isRunnable = false;
				this.log('settings.tickerId must have style width specified');
			}
		}
	}
	
	if (this.isRunnable) {
	    // Are we Windoze Internet Exploder 5 or 6?	
        this.isWinIe = Veer.Ticker.IsThisInternetExplorerPc55to6x();
		
		// obtain settings
		this.transitionDuration = settings.transitionDuration || Veer.Ticker.DEFAULT_DURATION_TRANSITION;
		this.holdDuration = settings.holdDuration || 4.0;
		this.tickerFeedUrl = settings.tickerFeedUrl || Veer.Ticker.DEFAULT_FEED;
		this.backgroundColor = settings.backgroundColor || Veer.Ticker.DEFAULT_COLOR_BACKGROUND;
		this.foregroundColor = settings.foregroundColor || Veer.Ticker.DEFAULT_COLOR_FOREGROUND;
		this.mouseOverColor = settings.mouseOverColor || Veer.Ticker.DEFAULT_COLOR_MOUSEOVER;
		this.easing = settings.easing || Veer.Ticker.DEFAULT_EASING;
		
		// Validate transition function
		switch (settings.transition || Veer.Ticker.TRANSITION_SLIDEUP) {
		    case Veer.Ticker.TRANSITION_SLIDEDOWN:
		        this.transitionInit = this.scrollDownInit;
		        break;
 
		    default:
		        // Slide up
		        this.transitionInit = this.scrollUpInit;
		        break;
		}
		
        // Validate durations
		if (this.transitionDuration < 0) this.transitionDuration = Veer.Ticker.DEFAULT_DURATION_TRANSITION;
		if (this.holdDuration < 0) this.holdDuration = Veer.Ticker.DEFAULT_DURATION_HOLD;
		
		// set ticker style programatically
		style.position = "relative";
		style.overflow = "hidden";
		style.backgroundColor = this.backgroundColor;
		
		// information to be retrieved from the feed
		this.tickerItemText = [];
		this.tickerItemUrl = [];
		this.tickerItemDom = [];
		this.tickerItemMaxIndex = 0;
		
		// calculated information follows
		
		// absolute top position of a tickerItemDom to throw it out of the viewable tickerDom window
		this.tickerHeight = parseInt(this.tickerDom.style.height, 10);
		this.tickerWidth = parseInt(this.tickerDom.style.width, 10);
		
		// Coordinate objects for positioning the type sample prior to display
		this.belowTicker = new MochiKit.DOM.Coordinates(0, this.tickerHeight + 1);
		this.aboveTicker = new MochiKit.DOM.Coordinates(0, -this.tickerHeight);
		this.withinTicker = new MochiKit.DOM.Coordinates(0, 0);
	
	    // Animation frame count
	    this.durationFrameCount = this.transitionDuration * Veer.Ticker.FRAME_RATE;
	}
	
	this.setVisible(this.isRunnable);
};
 
 
//----- PUBLIC CONSTANTS ------------------------------------------------------------------
 
 
// Transition constants.
Veer.Ticker.TRANSITION_SLIDEUP = 1;
Veer.Ticker.TRANSITION_SLIDEDOWN = 2;
 
// Constants for defaults when one or more settings are invalid
Veer.Ticker.DEFAULT_DURATION_TRANSITION = 1.0;
Veer.Ticker.DEFAULT_DURATION_HOLD = 4.0;
Veer.Ticker.DEFAULT_TRANSITION = Veer.Ticker.TRANSITION_SLIDEUP;
Veer.Ticker.DEFAULT_FEED = 'http://ideas.veer.com/atom/skinny';
Veer.Ticker.DEFAULT_COLOR_FOREGROUND = "333333";
Veer.Ticker.DEFAULT_COLOR_BACKGROUND = "FFFFFF";
Veer.Ticker.DEFAULT_COLOR_MOUSEOVER = "FF6600";
 
Veer.Ticker.DEFAULT_URL_FLONT = "http://flont.veer.com/header.aspx?";
 
Veer.Ticker.DEFAULT_EASING = function(t, b, c, d) {
 
    // No easing - equivalent to Veer.Animation.Easing.Linear.easeNone.
 
	return c*t/d + b;
 
};
 
 
//----- PUBLIC METHODS --------------------------------------------------------------------
 
 
// Public - Starts the ticker with settings provided in the constructor. Returns true if the
// ticker has been started, or false when the ticker is not runnable.
Veer.Ticker.prototype.start = function() {
	if (this.isRunnable) this.getTickerFeed();
	return this.isRunnable;
};
 
// Public - Sets the visibility of the ticker.
Veer.Ticker.prototype.setVisible = function(isVisible) {
    // This method has to be MochiKit free because it gets called even when MochiKit is unavailable.
    var display = isVisible ? "block" : "none";
    if (this.tickerDom) this.tickerDom.style.display = display;
    if (this.tickerContainerDom) this.tickerContainerDom.style.display = display;
};
 
 
//----- PRIVATE METHODS -- FEED PROCESSING ------------------------------------------------
 
 
// Private - makes a request to obtain the ticker feed, fires tickerFeedReady or tickerFeedFailed
Veer.Ticker.prototype.getTickerFeed = function() {
	var deferred = MochiKit.Async.doSimpleXMLHttpRequest(this.tickerFeedUrl);
	
	deferred.addCallback(this.tickerFeedReady);
	deferred.addErrback(this.tickerFeedFailed);
};
 
// Private - call back when getTickerFeed completes, prepares the feed info and starts the ticker
Veer.Ticker.prototype.tickerFeedReady = function(request) {
	this.parseFeed(request);
	this.createTickerDom();
	Veer.Ticker.registerForAnimation(this);
	var dom = this.getNextTickerItem();
	this.setVisible(true);
	this.showFirstTickerItem(dom);
};
 
// Private - Callback when getTickerFeed fails to complete, hides the ticker, logs error
Veer.Ticker.prototype.tickerFeedFailed = function(error) {
	this.setVisible(false);
	this.log('Failed to get feed', this.tickerFeedUrl);
};
 
// Private - Populates the tickerItemText and tickerItemUrl arrays from an Atom feed.
Veer.Ticker.prototype.parseFeed = function(request) {
	// we use Regular Expressions for parsing the feed because it's faster than using 
	// either the XML DOM or a JavaScript XSLT implementation
	var feed = request.responseText;
	var titleRe = /<title>(<!\[CDATA\[)?(.*?)(]]>|<\/title>)/gi;
	var urlRe = /<link\s+.*\s+href="(.*)"\s+.*\/>/gi;
	var match;
 
    var feedFirstEntry = feed.indexOf("<entry");
    if (feedFirstEntry != -1) feed = feed.slice(feedFirstEntry);
    
	// Find the ticker titles - note that we decode extraneously encoded ampersands
 	while ((match = titleRe.exec(feed)) !== null) {
 	    var titleText = match[2];
 	    titleText = titleText.replace("&amp;", "&");
		this.tickerItemText.push(titleText);
	}
	
	// Find the ticker title urls
	while ((match = urlRe.exec(feed)) !== null) {
		this.tickerItemUrl.push(match[1]);	
	}
	
	// Sanity check, in case there is a mismatch in length
	this.tickerItemMaxIndex = Math.min(this.tickerItemText.length, this.tickerItemUrl.length);
};
 
 
//----- PRIVATE METHODS -- DOM PROCESSING -------------------------------------------------
 
 
// Private - Creates the tickerItemDom from the tickerItemText and the tickerItemUrl arrays.
// In this case uses flont to create images of the tickerItemText.
Veer.Ticker.prototype.createTickerDom = function() {
	// style used to throw the tickerItemDom out of the viewable window of the tickerDom
	var imgStyle = "position: absolute; top: " + this.tickerHeight + 1 + "px; cursor: pointer; background-color: #" + this.foregroundColor + ";";
 
    // Partial flont style.
	var flontQuery = {
		size: 14,
		maxWidth: this.tickerWidth,
		fontId: 9802, // Avenir
		padding: 0,
		"500": true,
		ligature: true,
		spacing: 0,
		ellipses: true, // truncates the string at a word boundary and appends ellipses (...)
		forecolor: "01" + this.backgroundColor, // Creates punch-through (transparent) text.
		backcolor: "FF" + this.backgroundColor
	};
 
    // Event handlers for mousing over a text sample. Since the text is transparent, the background
    // colour of the IMG (or DIV for Win IE) is altered on a mouse over to provide highlighting.
    // That way, we only need one set of images for the two colours. 
	var mouseOutHandler = "this.style.backgroundColor='#" + this.foregroundColor + "';";
	var mouseOverHandler = "this.style.backgroundColor='#" + this.mouseOverColor + "';";
 
	// create one type sample for each ticker item
	for (var index = 0; index < this.tickerItemMaxIndex; index++) {
		flontQuery.text = this.tickerItemText[index];
		
		var tickerUrl = this.tickerItemUrl[index];
		var imageUrl = Veer.Ticker.DEFAULT_URL_FLONT + MochiKit.Base.queryString(flontQuery);
 
		var clickHandler = "Veer.Ticker.go('" + tickerUrl + "')";
        var dom;
		if (this.isWinIe) {
 
		    // Win IE is poo. Our normal image element becomes a DIV, and we attach an alpha filter that contains the PNG image.
 
		    dom = DIV({ style: imgStyle + "width: 530px; height: 38px;", onclick: clickHandler, onmousemove: mouseOverHandler, onmouseout: mouseOutHandler });
 
		    // Win IE AlphaImageLoader somehow decodes %xx encodings causing big poo, so we have to fix all such occurances
 
    		imageUrl = imageUrl.replace(/%/g, "%25");
		    dom.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod='image', src='" + imageUrl + "')"; 
 
		} else
 
		    // Every other modern browser uses a proper IMG tag.
 
		    dom = IMG({ src: imageUrl, style: imgStyle, onclick: clickHandler, onmousemove: mouseOverHandler, onmouseout: mouseOutHandler });
		
		this.tickerItemDom.push(dom);
		MochiKit.DOM.appendChildNodes(this.tickerDom, dom);
	}
};
 
// Private - Returns the next ticker item DOM to be shown.
Veer.Ticker.prototype.getNextTickerItem = function() {
	if (typeof(this.nowShowingTickerItemIndex) == 'undefined') {
		// the first call chooses a random item
		this.nowShowingTickerItemIndex = Math.floor(this.tickerItemMaxIndex * Math.random());
	} else {
		// subsequent calls return the next item in sequence
		this.nowShowingTickerItemIndex++;
		if (this.nowShowingTickerItemIndex >= this.tickerItemMaxIndex)
			this.nowShowingTickerItemIndex = 0;
	}
	
	return this.tickerItemDom[this.nowShowingTickerItemIndex];
};
 
 
// Private - Shows the first ticker item by popping into positionm rather than using an effect.
Veer.Ticker.prototype.showFirstTickerItem = function(dom) {
	this.setNowShowing(dom);
	MochiKit.DOM.setElementPosition(dom, this.withinTicker);
    this.frameAnimation = this.holdTickerItem;
};
 
// Private - Keeps track of the outgoing and icoming ticker item for the transition.
Veer.Ticker.prototype.setNowShowing = function(dom) {
	this.wasShowingDom = this.nowShowingDom;
	this.nowShowingDom = dom;
	
	// Sanity check
	if (this.wasShowingDom === this.nowShowingDom)
		this.wasShowingDom = null;
		
	this.startFrameNumber = Veer.Ticker.getFrameNumber();
	this.endFrameNumber = this.startFrameNumber + this.durationFrameCount;
};
 
 
//----- EASING SLIDE DOWN / SLIDE UP -------------------------------------------------------
 
 
// Private - Intializes scrolling a ticker item up through the ticker window.
Veer.Ticker.prototype.scrollUpInit = function(dom) {
	this.setNowShowing(dom);
	
	this.beginY = this.belowTicker.y;
	this.changeY = 0 - this.beginY;
	
	MochiKit.DOM.setElementPosition(dom, this.belowTicker);
	
	this.frameAnimation = this.scrollUpAnimate;
};
 
// Private - Event handler to perform scroll-up operation on wasShowing and nowShowing.
Veer.Ticker.prototype.scrollUpAnimate = function(frameNumber) {
    var dFrameNumber = frameNumber - this.startFrameNumber;
    if (dFrameNumber >= 0) {
        var y = this.easing(dFrameNumber, this.beginY, this.changeY, this.durationFrameCount);
 
        if (this.wasShowingDom)
            this.wasShowingDom.style.top = (y - this.tickerHeight) + "px";
            
        this.nowShowingDom.style.top = y + "px";
        
	    if (frameNumber == this.endFrameNumber)
		    this.frameAnimation = this.holdTickerItem;
	}
};
 
// Private - Intializes scrolling a ticker item down through the ticker window.
Veer.Ticker.prototype.scrollDownInit = function(dom) {
	this.setNowShowing(dom);
	
	this.beginY = this.aboveTicker.y;
	this.changeY = -this.beginY;
	
	MochiKit.DOM.setElementPosition(dom, this.aboveTicker);
	
	this.frameAnimation = this.scrollDownAnimate;
};
 
// Private - Event handler to perform scroll-down operation on wasShowing and nowShowing.
Veer.Ticker.prototype.scrollDownAnimate = function(frameNumber) {
    var dFrameNumber = frameNumber - this.startFrameNumber;
    if (dFrameNumber >= 0) {
        var y = this.easing(dFrameNumber, this.beginY, this.changeY, this.durationFrameCount);
 
        if (this.wasShowingDom)
            this.wasShowingDom.style.top = (y + this.tickerHeight) + "px";
            
        this.nowShowingDom.style.top = y + "px";
        
	    if (frameNumber == this.endFrameNumber)
		    this.frameAnimation = this.holdTickerItem;
	}
};
 
 
//----- TICKER HOLD -----------------------------------------------------------------------
 
 
// Private - Event handler that holds a ticker item visible for a given time. Schedules
// the next item to be shown when the hold times out.
Veer.Ticker.prototype.holdTickerItem = function() {
 
	if (!this.holdDurationFrameCount) 
		// Calculate number of hold frames on first call
		this.holdDurationFrameCount = this.holdDuration * Veer.Ticker.FRAME_RATE;
	else
		// Countdown on subsequent calls;
		--this.holdDurationFrameCount;
		
	if (this.holdDurationFrameCount === 0) {
		// Show the next ticker item when the hold is complete
		var dom = this.getNextTickerItem();
		this.transitionInit(dom);
	}
};
 
// Private - Event handler that deals with animation effects every frame.
Veer.Ticker.prototype.enterFrame = function(frameNumber) 
{
	if (this.frameAnimation)
		this.frameAnimation(frameNumber);
};
 
// Private - Null function. Gets replaced by MochiKit.Logging.log when MochiKit is available.
// When debugging code, using this.log(item, ...) will log to the JavaScript console on
// those browsers that have a console, when MochiKit is available.
Veer.Ticker.prototype.log = function() { };
 
//------------------------------------------------------------------------------------------
 
// Private - Static - Registers an instance of Veer.Ticker (or any object that supports
// an enterFrame method) for animation. It is is up to the object being registered to
// perform it's own animation. This method merely provides the mechanism to callback
// to any number of objects at a periodical interval.
Veer.Ticker.registerForAnimation = function(instance)
{
    if (instance) 
    {
    	// Start a frame timer if one does not currently exist
        if (!Veer.Ticker.frameTimer) 
        	Veer.Ticker.frameTimer = window.setInterval(Veer.Ticker.enterFrame, Veer.Ticker.FRAME_PERIOD);
        
        // Create the registrants array if one does not exist
        if (!Veer.Ticker.frameRegistrants) 
        	Veer.Ticker.frameRegistrants = [];
 
		// Add the instance        
        Veer.Ticker.frameRegistrants.push(instance);
    }
};
 
// Private - Static - Event handler that calls enterFrame on all the instances registered
// for animation. The current frameCount is provided as a parameter to the enterFrame method.
Veer.Ticker.enterFrame = function() 
{
    for (var index = 0; index < Veer.Ticker.frameRegistrants.length; index++) 
    {
        var ticker = Veer.Ticker.frameRegistrants[index];
        Veer.Ticker.frameCount++;
        if (ticker && ticker.enterFrame) ticker.enterFrame(Veer.Ticker.frameCount);
    }
};
 
// Public - Static - Returns the current frame number from the animation system.
Veer.Ticker.getFrameNumber = function() {
    return Veer.Ticker.frameCount;
};
 
// The frame counter
Veer.Ticker.frameCount = 0;
 
// Number of frames per second.
Veer.Ticker.FRAME_RATE = 24;
// Calculated constants used in the code - do not modify
Veer.Ticker.FRAME_PERIOD = 1000 / Veer.Ticker.FRAME_RATE;
 
//------------------------------------------------------------------------------------------
 
// Private - Static - Event handler for the ticker item IMG onclick event.
Veer.Ticker.go = function(url) {
	// TODO: probably should be window.open
	if (url)
		window.location = url;
};
 
//------------------------------------------------------------------------------------------
 
// Public - Static - Returns the URL to flont for text in the optional foreground and
// background color.
Veer.Ticker.flontTextUrl = function(text, foregroundColor, backgroundColor) {
	var flontQuery = {
		size: 18,
		maxWidth: 530,
		fontId: 9802,	// Avenir
		padding: 0,
		"500": true,
		ligature: true,
		spacing: 0,
		forecolor: "FF333333",
		backcolor: "FFFFFFFF"
	};
 
    flontQuery.text = text;
    if (foregroundColor) flontQuery.forecolor = Veer.Ticker.normalizeColor(foregroundColor);
    if (backgroundColor) flontQuery.backgroundColor = Veer.Ticker.normalizeColor(backgroundColor);
    
    alert("http://flont.veer.com/flontpng.aspx?" + MochiKit.Base.queryString(flontQuery));
	return "http://flont.veer.com/flontpng.aspx?" + MochiKit.Base.queryString(flontQuery);
};
 
// Private - Static - Normalizes a he color code to include opacity.
Veer.Ticker.normalizeColor = function(colorString) {
  	return (colorString.length == 6) ? colorString = "FF" + colorString : colorString;
};
 
//------------------------------------------------------------------------------------------
 
// Private - Static - Returns true if this browser is Microsoft Internet Exploder for Windows
// with a version of 5 or 6. Copied verbatim from Veer.Browser.js to avoid a dependency on
// that file.
Veer.Ticker.IsThisInternetExplorerPc55to6x = function() {
 
	// Assume the best case.
 
	var result = false;
 
	// Ignore the browser check for now to enable ticker being visible on IE6, although it does not do the highlighting correctly, until we can figure out what's going on
	return false;
 
	if (navigator.platform == "Win32" && navigator.appName == "Microsoft Internet Explorer") {
 
		var rslt = navigator.appVersion.match(/MSIE (\d+\.\d+)/, '');
 
		if (rslt !== null && Number(rslt[1]) >= 5.5 && Number(rslt[1]) < 7.0) {
 
			result = true;
 
		}
 
	}
 
	
 
	return result;
 
};