
// colorflow.js - fade the background color of a given DOM element

// Tim Reeves, 2011-03-02

// The global RGB array values for menu links are now set in head.inc.php

// It would be cleaner to clear any outstanding timeouts, but in recursive
// calls they are difficult to track. So quick and dirty (but effective):
window.g_loading = true;	// Note that ajax.js has already done the same

// More on window.g_loading strategy: See ajax/ajax.js

// BEGIN CONFIG AREA ----------------------------

// Period: Time in milliseconds between each flow increment
// 12 x 8 = 0.096 seconds may seem short, but is about optimal when
// the mouse is moved quickly over a whole menu full of flowing links

// END CONFIG AREA ----------------------------


mytrace = new Array();
function showtrace() {
	var stmt = '', l = mytrace.length;
	var b = l > 50 ? l - 50 : 0;
	for (var i=b; i<l; i++) stmt = stmt + mytrace[i];
	alert(stmt);
}
function traceBln(blnVal) { return (blnVal) ? 'true' : 'false'; }


// An own object type to manage the flow on one DOM element
function objFlowQueueElement (strDomId, arrStart, arrFinal, arrIncr, blnUniDir)
{
	this.strDomId = strDomId;		// DOM-Id Value
	this.arrStart = arrStart;		// Array of 3 ints (R,G,B) - start value
	this.arrFinal = arrFinal;		// Array of 3 ints (R,G,B) - final value
	this.arrIncr  = arrIncr;		// Array of 3 ints (R,G,B) - increment values
	this.arrCurr  = arrStart.slice();	// Copy array - NOT a reference
	this.blnUniDir = blnUniDir;		// True if only unidirectional flow required
	this.blnGoUp = true;	// true: Start to Final, false: Final to Start
	this.blnOver = true;	// True if mouse over the DOM element
	this.blnFlow = true;	// True if color is currently flowing
}

// An own object type to manage the timer for a particular interval (ms)
function timerQueue () {
	this.timerHandle = null;			// gets handle from setTimeout()
	this.intActiveItems = 0;			// number of elements not yet released
	this.arrFlowQueue = new Array();	// of objFlowQueueElement objects
}

// An array of timerQueue objects, indexed by timer interval (ms)
arrTimerObjects = new Array();

// A function to ensure that there is an entry in arrTimerObjects for this intPeriod
function initPeriodQueue (intPeriod) {
	if (typeof arrTimerObjects[intPeriod] == 'undefined') {
		arrTimerObjects[intPeriod] = new timerQueue();
	}
}

// This function is triggered by newflow()
function processPeriodQueue (intPeriod) {
	// Called by timer via setTimeout()
	var objTimerQueue = arrTimerObjects[intPeriod];
	if (objTimerQueue.intActiveItems > 0) {
		// Iterate this timers flow queue, calling doflow() for each active item
		var arrFlowQueue = objTimerQueue.arrFlowQueue;
		var l = arrFlowQueue.length;
		for (i=0; i<l; i++) {
			if (arrFlowQueue[i].strDomId != '') {
				objTimerQueue.intActiveItems -= doflow(arrFlowQueue[i]);
			}
		}	// loop
	}		// work to do
	if (objTimerQueue.intActiveItems > 0) {
		// Start the timer for this queue again
		objTimerQueue.timerHandle = window.setTimeout(
									'processPeriodQueue(' + intPeriod + ')', intPeriod);
	}
	else {
		// Set this timer slot to idle
		objTimerQueue.timerHandle = null;
	}
}	// processPeriodQueue()

// Internal Workhorse to apply the next flow step to one DOM element
// Returns 0 if the passed item is still active, 1 if not (any more)
function doflow( objFQE ) {

	// mytrace[mytrace.length] = "doflow strDomId=" + objFQE.strDomId + ' / ';
	// mytrace[mytrace.length] = 'sta0=' + objFQE.arrStart[0] + "\n";

	if (window.g_loading) { objFQE.strDomId = ''; return 1; }

	if (!objFQE.blnFlow) return 0;	// Waiting to flow (mouse over element)

	var arrCurr = objFQE.arrCurr;	// Shortcut reference
	var arrIncr = objFQE.arrIncr;	// Shortcut reference

	var arrDone = [false, false, false];

	// Calculate new RGB values using arrIncr, depending on flow direction
	if (objFQE.blnGoUp) {
		var arrFinal = objFQE.arrFinal;	// Shortcut reference
		// mytrace[mytrace.length] = "UP cur: " + arrCurr[0] + ", inc:" + arrIncr[0] + ", fnl:" + arrFinal[0] + "\n";
		for (c=0; c<3; c++) {
			if (arrIncr[c] > 0 && arrCurr[c] < arrFinal[c]) arrCurr[c] += arrIncr[c];
			if (arrIncr[c] < 0 && arrCurr[c] > arrFinal[c]) arrCurr[c] += arrIncr[c];
			// Note if we are done (have reached final) and correct any overshoot
			// This way assures that inhomogeneous parameters never exceed "final"
			if (arrIncr[c] == 0 || (arrIncr[c] > 0 && arrCurr[c] >= arrFinal[c])
								|| (arrIncr[c] < 0 && arrCurr[c] <= arrFinal[c])) {
				arrCurr[c] = arrFinal[c];
				arrDone[c] = true;
			}
		}
	}
	else {
		var arrStart = objFQE.arrStart;	// Shortcut reference
		// mytrace[mytrace.length] = "DN cur: " + arrCurr[0] + ", inc:" + arrIncr[0] + ", sta:" + arrStart[0] + "\n";
		for (c=0; c<3; c++) {
			if (arrIncr[c] > 0 && arrCurr[c] > arrStart[c]) arrCurr[c] -= arrIncr[c];
			if (arrIncr[c] < 0 && arrCurr[c] < arrStart[c]) arrCurr[c] -= arrIncr[c];
			if (arrIncr[c] == 0 || (arrIncr[c] > 0 && arrCurr[c] <= arrStart[c])
								|| (arrIncr[c] < 0 && arrCurr[c] >= arrStart[c])) {
				arrCurr[c] = arrStart[c];
				arrDone[c] = true;
			}
		}
	}

	// Assign the new color value, checking if element is still defined
	// It may not be - if the page is reloaded or another page loaded
	var objFlowElem = document.getElementById(objFQE.strDomId);
	if (objFlowElem) {
		objFlowElem.style.backgroundColor =
			'rgb(' + arrCurr[0] + ',' + arrCurr[1] + ',' + arrCurr[2] + ')';
	}
	else {
		// The page appears to have been unloaded
		// mytrace[mytrace.length] = "doflow: Page unloaded  ";
		objFQE.strDomId = ''; return 1;
	}

	if (arrDone[0] && arrDone[1] && arrDone[2]) {

		// This cycle is done - is any action outstanding?
		// mytrace[mytrace.length] = 'doflow done for ' + objFQE.strDomId +
		// 						  ', goup=' + traceBln(objFQE.blnGoUp) +
		// 						  ', over=' + traceBln(objFQE.blnOver) +
		// 						  ', sta0=' + objFQE.arrStart[0] + "\n";

		if (objFQE.blnUniDir) {	// unidirectional flow
			// showtrace();
			objFQE.strDomId = ''; return 1;
		}

		if (objFQE.blnGoUp) {
			// Finished flowing up
			objFQE.blnGoUp = false;
			// Flow down again or wait for down event
			if (objFQE.blnOver) {
				// The mouse is over the element - color remains up
				objFQE.blnFlow = false;
				// A new mouse event will trigger the flow down
				return 0;
			}
			// Fall through to flow on down
		}
		else {
			// Finished flowing down - up again or release slot
			if (objFQE.blnOver) {
				objFQE.blnGoUp = true;
				// Fall through to flow up again
			}
			else {
				objFQE.strDomId = '';
				return 1;
			}
		}
		// showtrace();
	}	// Cycle finished

	// This flow is not yet finished
	return 0;

}	// doflow()


// EXTERNAL FUNCTIONS - these are what users of colorflow call

// Accept parameters for a (probably) new flow item and start the flow
function newflow(strDomId, intPeriod, arrStart, arrFinal, arrIncr, blnUniDir) {

	// arrStart = [245,245,235];	/* RGB - Light beige */
	// arrFinal = [255,255,255];	/* RGB - White */
	// How much to change the color channels per flow increment:
	// arrIncr   = [1,1,2];
	// intPeriod = How long to wait between increments (ms)

	// mytrace[mytrace.length] = "newflow(" + strDomId + ")  ";

	if (! document.getElementById(strDomId)) {
		// The page appears to have been unloaded
		return;
	}

	initPeriodQueue(intPeriod);							// Ensure timer object set
	var objTimerQueue = arrTimerObjects[intPeriod];		// Get a handle to it
	var arrFlowQueue  = objTimerQueue.arrFlowQueue;		// Ref to the timers flow queue

	// Find or assign the/a queue slot for this element
	var i, l = arrFlowQueue.length, freeslot = -1;
	for (i=0; i<l; i++) {
		if (arrFlowQueue[i].strDomId == strDomId) {
			// Use the existing element
			break;
		}
		// Note first free slot (if any)
		if (freeslot == -1 && arrFlowQueue[i].strDomId == '') freeslot = i;
	}

	if (i < l) {
		// The given strDomId is already in use
		arrFlowQueue[i].blnOver = true;
		// Dont't wait to go right down - reverse direction immediately
		arrFlowQueue[i].blnGoUp = true;
	}
	else {
		// A new active element
		objTimerQueue.intActiveItems++;
		if (freeslot > -1) {
			// Re-use a free slot
			i = freeslot;
			arrFlowQueue[i].strDomId = strDomId;
			arrFlowQueue[i].arrStart = arrStart;
			arrFlowQueue[i].arrFinal = arrFinal;
			arrFlowQueue[i].arrIncr  = arrIncr;
			arrFlowQueue[i].arrCurr  = arrStart.slice();
			arrFlowQueue[i].blnUniDir = blnUniDir;
			arrFlowQueue[i].blnGoUp = true;
			arrFlowQueue[i].blnOver = true;
			arrFlowQueue[i].blnFlow = true;
		}
		else {
			// Assign a new element ... blnGoUp, mouseIsOver, isNowFlowing
			arrFlowQueue[i] = new objFlowQueueElement(strDomId,
									arrStart, arrFinal, arrIncr, blnUniDir);
		}
	}

	// Initiate the timer if it's not currently running
	if (objTimerQueue.timerHandle == null) processPeriodQueue(intPeriod);

	// showtrace();

}	// newflow()


// Flow down any DOM element - only after newflow()
function flowdown(strDomId, intPeriod) {

	// mytrace[mytrace.length] = "flowdn(" + strDomId + ", " + intPeriod + ")  ";

	if (! document.getElementById(strDomId)) {
		// The page appears to have been unloaded
		return;
	}

	// Find the queue element for this strDomId
	initPeriodQueue(intPeriod);							// Ensure timer object set
	var objTimerQueue = arrTimerObjects[intPeriod];		// Handle to timer object
	var arrFlowQueue  = objTimerQueue.arrFlowQueue;		// Ref to the timers flow queue

	var i, l = arrFlowQueue.length;
	for (i=0; i<l; i++) {
		if (arrFlowQueue[i].strDomId == strDomId) break;
	}

	// In case the mouse started over the button...
	if (i == l) return;

	// The mouse has moved out of this area
	arrFlowQueue[i].blnOver = false;
	// In any case, we'll be flowing now
	arrFlowQueue[i].blnFlow = true;
	// But if the flow up was unfinished then let it continue
	// arrFlowQueue[i].blnGoUp = false;

	// Initiate the timer if it's not currently running
	if (objTimerQueue.timerHandle == null) processPeriodQueue(intPeriod);

	// showtrace();

}	// flowdown()


// Flow up a menu item
function flowupmenu(strTi) {
	// strTi = (string) tabindex value
	var strDomId = 'flow' + strTi;
	newflow(strDomId, g_period, g_lnbase, g_lnpeak, g_lnincr, false);
}

// Flow up a menu item with color reversal
function flowupmenurev(strTi) {
	// strTi = (string) tabindex value
	var strDomId = 'flow' + strTi;
	var arrRevIncr = [-(g_lnincr[0]), -(g_lnincr[1]), -(g_lnincr[2])];
	newflow(strDomId, g_period, g_lnpeak, g_lnbase, arrRevIncr, false);
}

// Flow down a menu item
function flowdownmenu(strTi) {
	// strTi = (string) tabindex value
	var strDomId = 'flow' + strTi;
	flowdown(strDomId, g_period);
}

window.g_loading = false;

