/*
 * MsgScroller
 * ===========
 * Author:	Marius Kruger
 * Date:	October 20004
 * 
 * DESCRIPTION:
 * MsgScroller allows you to create multiple message scrollers on the same page.
 * It creates a div, which contains an html table with one row, in which child divs
 *  (created from the messages added to MsgScroller) is displayed and scrolled. The
 *  table
 * 
 * USAGE:
 * - construct any number of MsgScroller instances by calling it's constructor with the 
 *    following arguments:
 *    -- maxWidth: the scroller's required width (default=200)
 *    -- maxHeight: the scroller's required height (default=200)
 *    -- displayDelay: the number of milliseconds the scroller should pause the message before scrolling (default=1000)
 *    -- scrollPixels: the number of pixels to move when scrolling - determines speed of scrolling (default=1)
 * - call MsScroller.addMessages([message to display]) any number of times, on a constructed instance, to add messages.
 * - call MsScroller.createScroller(), on a constructed instance, where the scroller needs to be displayed.
 * - call MsScroller.startScrolling(), when scrolling needs to start.
 
 * DEVELOPMENT NOTES:
 * - "_scr" is prepended to publically accessible properties to avoid duplicate names
 * - setTimeout() causes memory leaks -> this has been resolved by scheduling forced garbage collection
 */
 
var _SCR_PARENT_DIV = "_scrParent";
var _SCR_CHILD_DIV = "_scrChild";
var _scrScrollNum = 0; // no of scrollers on page
var _scrAllScrollers = new Array();
var _gcTimer = null;
function MsgScroller(maxWidth, maxHeight, displayDelay, scrollPixels)
{
	// width of message
	this.maxWidth = !parseInt(maxWidth).isNaN ? parseInt(maxWidth) : 200;
	// height of message
	this.maxHeight = !parseInt(maxHeight).isNaN ? parseInt(maxHeight) : 200;
	// the number of milliseconds from when a message reach the top, 'till it starts scrolling again
	this.displayDelay = !parseInt(displayDelay).isNaN ? parseInt(displayDelay) : 1000;
	// determines the no of pixels to move when scrolling the div
	this.scrollPixels = !parseInt(scrollPixels).isNaN ? parseInt(scrollPixels) : 1;
	
	this.msgArr = new Array(); // array of messages to display
	this.divArr = new Array(); // array of created divs for messages - not the actual div objects but rather their html
	this.scrollIdx = _scrScrollNum++; // this scroller's index in the scroller array
	this.currMsgIdx = -1; // current displayed msg's index in msga array
	this.nextMsgIdx = 0; // next displayed msg's index in msg array
	this.timer = null; // create timer on same object each time to avoid memory hogging
	
	// MsgScroller functions
	this.addMessage = _scrAddMsg; // add messages to msgArr
	this.createScroller = _scrCreate; // creates div html from msgArr
	this.startScrolling = _scrStart; // start scrolling this scroller
	this.stopScrolling = _scrStop; // stops scrolling this scroller
	this._continueScrolling = _scrContinue; // continues scrolling when stopped
	this._keepScrolling = _scrScroll; // continues the scrolling
	this._forwardScroller = _scrForward; // moves the scroller's messages forward
	
	// flags to determine if certain methods has been called
	this.createCalled = false; // _create called?
	this.startCalled = false; // _start called?
	
	// make this scroller externally accessible
	_scrAllScrollers[_scrAllScrollers.length] = this;
}

/*
 *  Adds a message to the message array of the MsgBuilder
 */
function _scrAddMsg(msg)
{
	this.msgArr[this.msgArr.length] = msg;
}

/*
 *  Creates the main div in which the scroller executes
 */
function _scrCreate()
{
	if (this.createCalled) {
		// dissallow multiple calls to this method
		throw new Error("MsgScroller.createScroller can only be called once per instance.");
	} else {
		this.createCalled = true;
	}
	// display default message if no messages was added to the scroller
	if (this.msgArr.length < 1) {
		this.msgArr[0] = "<b>No messages to display</b>";
	}
	thisScroller = _scrAllScrollers[this.scrollIdx];
	document.write("<div id='"+ _SCR_PARENT_DIV + this.scrollIdx + "' style='position:relative;width:" + (this.maxWidth) + "px;height:" + this.maxHeight + "px;overflow:hidden;'>");
	document.write("</div>");
	
	for (var i = 0; i < this.msgArr.length; i++) {
		var msg = "";
		msg += "<div id='" + _SCR_CHILD_DIV + this.scrollIdx + i + "' style='position:absolute;width:" + this.maxWidth +
			"px;height:" + this.maxHeight + "px;left:1px;top: 1px; visibility: hidden'>";
		msg += "<table width='" + this.maxWidth + "px' height='" + this.maxHeight + "px'>";
		msg += "<tr><td width='" + this.maxWidth + "px' height='" + this.maxHeight + "px' valign='top'>";
		msg += this.msgArr[i];
		msg += "</td></tr>";
		msg += "</table>";
		msg += "</div>";
		this.divArr[i] = msg;
	}
}

/*
 *  Starts the scrolling
 */
function _scrStart()
{	
	if (!this.createCalled) {
		// cannot start scroller before it was created
		throw new Error("MsgScroller.createScroller should be called before calling MsgScroller.startScrolling.");
	} else if (this.startCalled) {
		// dissallow multiple calls to this method
		throw new Error("MsgScroller.startScrolling can only be called once per instance.");
	} else {
		this.startCalled = true;
	}
	
	this._forwardScroller();
	this._keepScrolling();
}

function _scrStop()
{
	clearTimeout(this.timer);
}
function _scrContinue()
{
	this._keepScrolling();
}

/*
 *  Manages the actual scrolling of the message divs
 */
function _scrScroll()
{
	var msg1 = document.getElementById(_SCR_CHILD_DIV + this.scrollIdx + this.currMsgIdx);
	var msg2 = document.getElementById(_SCR_CHILD_DIV + this.scrollIdx + this.nextMsgIdx);
	
	// get the top style with the "px"
	var msg1top = parseInt(msg1.style.top.substr(0, (msg1.style.top.length - 2)))
	var msg2top = parseInt(msg2.style.top.substr(0, (msg2.style.top.length - 2)))
	
	// check if 2nd msg reached top
	if (msg2top > 0) {
		// 2nd msg not at top yet, keep scrolling
		msg1.style.top = msg1top - this.scrollPixels + "px";
		msg2.style.top = msg2top - this.scrollPixels + "px";
		this.timer = null;
		this.timer = setTimeout("_scrAllScrollers[" + this.scrollIdx + "]._keepScrolling()", 1);
	} else {
		// 2nd msg at top -> need next 2 msg to scroll
		this._forwardScroller();
		this.timer = null;
		this.timer = setTimeout("_scrAllScrollers[" + this.scrollIdx + "]._keepScrolling()", this.displayDelay);
	}
	
	// ensure garbage collection of assigned variables
	msg1 = null;
	msg2 = null;
	msg1top = null;
	msg2top = null;
}

/*
 *  Moves the scroller forward to the next 2 messages
 */
function _scrForward()
{
	this.currMsgIdx++;
	if (this.currMsgIdx > this.msgArr.length - 1) {
		this.currMsgIdx = null;
		this.currMsgIdx = 0;
	}
	this.nextMsgIdx++;
	if (this.nextMsgIdx > this.msgArr.length - 1) {
		this.nextMsgIdx = null;
		this.nextMsgIdx = 0;
	}
	var scrollDiv = document.getElementById(_SCR_PARENT_DIV + this.scrollIdx);
	// hook 2 message divs to parent div
	scrollDiv.innerHTML = this.divArr[this.currMsgIdx] + this.divArr[this.nextMsgIdx];
	
	var msg1 = document.getElementById(_SCR_CHILD_DIV + this.scrollIdx + this.currMsgIdx);
	var msg2 = document.getElementById(_SCR_CHILD_DIV + this.scrollIdx + this.nextMsgIdx);
	
	// align 2 message divs, one in display and one outside
	msg1.style.top = "0px";
	msg1.style.visibility = "visible";
	msg2.style.top = this.maxHeight + "px";
	msg2.style.visibility = "visible";
	
	// ensure garbage collection of assigned variables
	scrollDiv = null;
	msg1 = null;
	msg2 = null;
}

/*
 * Stops all scrollers
 */
function _scrStopAllScrollers()
{
	for (var i = 0; i < _scrAllScrollers.length; i++) {
		var scroller = _scrAllScrollers[i];
		scroller.stopScrolling();
		scroller._forwardScroller();
	}
}
