// Customisable Scrolling Area version 2.1
// Copyright © 2011 Exploit The Web Limited, all rights reserved.

// How to use this script:
//
// After the script tag that calls this script (e.g. <script type="text/javascript" src="/_js/scrollingarea2.js"></script>),
// you must have some more script that defines a 'scrollSettings' object.
// The scrollSettings object has the following attributes:
//    scrollingAreaID (required): the id attribute of the page element that houses the scrolling content.
//    orientation (optional):     "h" for horizontal, "v" for vertical. If not specified, "v" is assumed.
//    period (required):          the amount of time, in milliseconds, between each movement (or "step") of the scrolling content.
//    step (required):            the number of pixels by which the scrolling content will move each time (every "period" milliseconds).
//    pause (optional):           the number of seconds that the scrolling will be paused for after each child element scrolls out of view. If not specified, no pause will occur.
//    debugAreaID (optional):     the id attribute of the page element that houses the debugging information. If not specified, no debugging is performed.
//    debugLevel (optional):      A number from 0 to 2 inclusive, defining how detailed debugging information should be. 0 is none, 2 is max. If not specified, 0 is assumed. You can actually specify a number greater than 2, it will be treated the same as 2.
// Then, somewhere in your page you need to define how/when to start scrolling.
// This is done with the function startScroll(), so you could for example add this to an onclick handler in any one of your page elements.
//
// example usage:
//
//<script type="text/javascript" src="/_js/scrollingarea2.1.js"></script>
//<script type="text/javascript">
//	var scrollSettings = {scrollingAreaID: "div_scrollingarea", orientation: "v", period: 40, step: 1, debugAreaID: "div_debug", debugLevel: 1};
//	if (window.addEventListener){  
//		window.addEventListener('load', startScroll, false);   
//	} else if (window.attachEvent){  
//		window.attachEvent('load', startScroll);  
//	}
//</script>
//<div id="div_debug" style="display: none; position: absolute; z-index: 901; top: 300px; left: 200px; border: 3px solid black; width: 400px; height: 300px; background-color: white;"></div>
//

var debugArea;
var debugForm;
var scrollArea;
var scrollAreaSize;
var itemContainer;
var allItems;
var firstItemSize;
var scrollInterval;
var debugIndent = "";

function fatal (err) {
	stopScroll();
	debugMsg(err);
}

function firstChild(myParent) {
	var candidate = myParent.firstChild;
	while (candidate && !candidate.tagName) candidate = candidate.nextSibling;
	return candidate;
}

function getSize(item) {
	var itemRect = item.getBoundingClientRect();
	if (scrollSettings.orientation == "h") itemSize = itemRect.right - itemRect.left;
	else itemSize = itemRect.bottom - itemRect.top;
	return itemSize;
}

function renumberItems() {
	// rebuild the allItems array
	debugMsg("BEGIN renumberItems()");
	allItems = new Array();
	var i = 0;
	debugMsg("finding child elements of " + itemContainer.id);
	var candidate = (scrollSettings.step >= 0) ? itemContainer.firstChild : itemContainer.lastChild;
	while (candidate) {
		debugMsg(candidate.nodeName);
		if (candidate.tagName) {
			candidate.id = "item" + i;
			if (scrollSettings.orientation == "h") {
				if (typeof(candidate.style.cssFloat) == "string") {
					candidate.style.cssFloat = "left"; // Firefox
				} else {
					candidate.style.float = "left"; // IE
				}
			}
			allItems.push(candidate);
			i++;
		}
		candidate = (scrollSettings.step >= 0) ? candidate.nextSibling : candidate.previousSibling;
	}
	if (allItems.length) {
		// get size of first item
		firstItemSize = getSize(allItems[0]);
	} else {
		alert("No items found to scroll");
	}
	debugMsg("END renumberItems { return " + i + " }");
	// return the number of items
	return i;
}

function copyAndAppend(item) {
	// create the new item
	var newItem = document.createElement(item.tagName.toLowerCase());
	if (item.className) newItem.className = item.className;
	if (item.title) newItem.title = item.title;
	if (item.innerHTML) newItem.innerHTML = item.innerHTML;
	// insert it into the list
	if (scrollSettings.step >= 0) itemContainer.insertBefore(newItem, null); // at the end
	else itemContainer.insertBefore(newItem, firstChild(scrollArea)); // at the beginning
	// reassign IDs to each item
	renumberItems();
	// return the new item
	return newItem;
}

function startScroll() {
	debugArea = scrollSettings.debugAreaID ? document.getElementById(scrollSettings.debugAreaID) : null;
	if (debugArea && scrollSettings.debugLevel) {
		var myHTML = "<form id=\"form_debug\" style=\"margin: 0px; padding: 0px; font-family: arial; font-size: 11px;\">";
		myHTML += "  <table style=\"border-collapse: collapse; width: 100%; height: 100%;\">";
		myHTML += "    <tr>";
		myHTML += "      <td style=\"padding: 2px;\">scrollSettings.period</td>";
		myHTML += "      <td style=\"padding: 2px;\"><input type=\"text\" name=\"period\" value=\"" + scrollSettings.period + "\" style=\"border-style: none; border-width: 0px; width: 50px; font-size: 11px;\" /></td>";
		myHTML += "      <td style=\"padding: 2px;\" rowspan=\"6\">allItems</td>";
		myHTML += "      <td style=\"padding: 2px;\" rowspan=\"6\"><textarea name=\"allItems\" style=\"border-width: 1px; padding: 0px; width: 100px; height: 120px; font-size: 11px\"></textarea></td>";
		myHTML += "    </tr>";
		myHTML += "    <tr>";
		myHTML += "      <td style=\"padding: 2px;\">scrollSettings.step</td>";
		myHTML += "      <td style=\"padding: 2px;\"><input type=\"text\" name=\"step\" value=\"" + scrollSettings.step + "\" style=\"border-style: none; border-width: 0px; width: 50px; font-size: 11px;\" /></tr>";
		myHTML += "    <tr>";
		myHTML += "      <td style=\"padding: 2px;\">scrollArea.scrollTop</td>";
		myHTML += "      <td style=\"padding: 2px;\"><input type=\"text\" name=\"scrollTop\" value=\"0\" style=\"border-style: none; border-width: 0px; width: 50px; font-size: 11px;\" /></td>";
		myHTML += "      </tr>";
		myHTML += "    <tr>";
		myHTML += "      <td style=\"padding: 2px;\">scrollArea.scrollLeft</td>";
		myHTML += "      <td style=\"padding: 2px;\"><input type=\"text\" name=\"scrollLeft\" value=\"0\" style=\"border-style: none; border-width: 0px; width: 50px; font-size: 11px;\" /></td>";
		myHTML += "    </tr>";
		myHTML += "    <tr>";
		myHTML += "      <td style=\"padding: 2px;\">scrolledOff</td>";
		myHTML += "      <td style=\"padding: 2px;\"><input type=\"text\" name=\"scrolledOff\" value=\"0\" style=\"border-style: none; border-width: 0px; width: 50px; font-size: 11px;\" /></td>";
		myHTML += "    </tr>";
		myHTML += "    <tr>";
		myHTML += "      <td style=\"padding: 2px;\">firstItemSize</td>";
		myHTML += "      <td style=\"padding: 2px;\"><input type=\"text\" name=\"firstItemSize\" value=\"?\" style=\"border-style: none; border-width: 0px; width: 50px; font-size: 11px;\" /></td>";
		myHTML += "    </tr>";
		myHTML += "    <tr>";
		myHTML += "      <td style=\"padding: 2px;\" colspan=\"4\">messages<br /><textarea name=\"messages\" style=\"border-width: 1px; padding: 0px; width: 360px; height: 120px; font-size: 11px;\"></textarea></td>";
		myHTML += "    </tr>";
		myHTML += "    <tr>";
		myHTML += "      <td style=\"padding: 2px;\" colspan=\"4\"><input type=\"text\" name=\"time\" style=\"border-style: none; border-width: 0px; width: 50px; font-size: 11px;\" /></td>";
		myHTML += "    </tr>";
		myHTML += "  </table>";
		myHTML += "</form>";
		debugArea.innerHTML = myHTML;
		debugArea.style.display = "block";
		debugForm = document.getElementById("form_debug");
	}
	debugMsg("BEGIN startScroll()");
	debugIndent += "  ";
	scrollArea = scrollSettings.scrollingAreaID ? document.getElementById(scrollSettings.scrollingAreaID) : null;
	if (!scrollArea) {
		
		debugMsg("Cannot find scrollArea");
		debugIndent = debugIndent.replace(/  /, "");
		debugMsg("END startScroll()");
		return false;
		
	} else {
		
		scrollAreaSize = getSize(scrollArea);
		itemContainer = firstChild(scrollArea);
		scrollSettings.playSpeed = scrollSettings.step;
		scrollSettings.ffwdSpeed = Math.abs(scrollSettings.playSpeed) * 10;
		scrollSettings.rewindSpeed = 0 - scrollSettings.ffwdSpeed;
		debugStats();
		
		if (scrollSettings.debugLevel > 1) alert("About to start scrolling: calling renumberItems()");
		renumberItems();
		debugStats();
		if (scrollSettings.debugLevel > 1) alert("copyAndAppend(allItems[0])");
		copyAndAppend(allItems[0]);
		debugStats();
		if (scrollSettings.debugLevel > 1) alert("copyAndAppend(allItems[1])");
		copyAndAppend(allItems[1]);
		debugStats();
		
		if (scrollSettings.debugLevel > 1) alert("scroll to the start...");
		if (scrollSettings.step >= 0) {
			scrollArea.scrollLeft = 0;
			scrollArea.scrollTop = 0;
		} else {
			scrollArea.scrollLeft = 9999;
			scrollArea.scrollTop = 9999;
		}
		debugStats();
		
		if (scrollSettings.debugLevel > 1) alert("...and begin!");
		scrollInterval = window.setInterval(scroll, scrollSettings.period);
		
		debugIndent = debugIndent.replace(/  /, "");
		debugMsg("END startScroll()");
		return true;
		
	}
}

var newPos;
var scrolledOff;
function scroll() {
	
	if (debugForm) {
		debugMsg("BEGIN scroll()");
		debugIndent += "  ";
		debugStats();
	}
	
	// find out where we need to scroll to
	if (scrollSettings.orientation == "h") {
		newPos = scrollArea.scrollLeft + scrollSettings.step;
	} else {
		newPos = scrollArea.scrollTop + scrollSettings.step;
	}
	// if we are scrolling to a place where the first item disappears, remove it from the list and stick the appropriate item at the end to balance
	scrolledOff = 0;
	if (scrollSettings.step >= 0) {
		scrolledOff = newPos;
	} else if (scrollSettings.orientation == "h") {
		scrolledOff = Math.ceil(scrollArea.scrollWidth - scrollAreaSize - newPos);
	} else {
		scrolledOff = Math.ceil(scrollArea.scrollHeight - scrollAreaSize - newPos);
	}
	if (scrolledOff >= firstItemSize) {
		
		if (scrollSettings.debugLevel > 1) {
			pauseScroll();
			alert("scrolledOff is now at " + scrolledOff + " so it's time to remove allItems[0] and copy allItems[2] at the back of the queue (because allItems[0] and allItems[1] were already copied by startScroll()).");
		}
		
		// ok so items are being added / removed, so we need to adjust newPos to allow for this.
		// If scrolling forwards, reduce by the size of the first item (being removed).
		// If scrolling backwards, increase by the size of the 3rd item (being added).
		if (scrollSettings.debugLevel > 1) alert("newPos was going to be " + newPos + "...");
		if (scrollSettings.step >= 0) newPos -= firstItemSize; else newPos += firstItemSize;
		if (scrollSettings.debugLevel > 1) alert("...but now it'll be " + newPos);
		
		// scrollArea.removeChild(allItems[0]) doesn't seem to work, perhaps because allItems[0] is a copy of the node, not the node itself
		if (scrollSettings.debugLevel > 1) alert("Removing allItems[0]...");
		try { itemContainer.removeChild(document.getElementById(allItems[0].id)); } catch(e) { fatal("itemContainer.removeChild(" + allItems[0].id + ") failed: " + e.message); }
		debugStats();
		
		if (scrollSettings.debugLevel > 1) alert("Renumbering items...");
		try { renumberItems(); } catch(e) { fatal("renumberItems failed: " + e.message); }
		debugStats();
		
		if (scrollSettings.debugLevel > 1) alert("Adding what has now become allItems[1]...");
		try { copyAndAppend(allItems[1]); } catch(e) { fatal("copyAndAppend failed: " + e.message); }
		debugStats();
		
		if (scrollSettings.debugLevel > 1) {
			var keepDebugging = confirm("...and on we go! P.S. Press 'Cancel' to shut me up");
			if (!keepDebugging) scrollSettings.debugLevel = 1;
			resumeScroll();
		}
		
	}
	
	if (scrollSettings.orientation == "h") {
		//debugMsg(" trying to change scrollArea.scrollLeft from " + scrollArea.scrollLeft + " to " + newPos);
		scrollArea.scrollLeft = newPos;
		//debugMsg("scrollArea.scrollLeft = " + scrollArea.scrollLeft);
	} else {
		//debugMsg(" trying to change scrollArea.scrollTop from " + scrollArea.scrollTop + " to " + newPos);
		scrollArea.scrollTop = newPos;
		//debugMsg("scrollArea.scrollTop = " + scrollArea.scrollTop);
	}
		
	if (scrollSettings.pause && (scrolledOff >= firstItemSize)) pauseScroll(scrollSettings.pause);
	
	if (debugForm) {
		debugIndent = debugIndent.replace(/  /, "");
		debugMsg("END scroll()");
	}
	
	return true;
	
}

function stopScroll() {
	
	// like pauseScroll() but also blocks resumeScroll() from working
	
	debugMsg("BEGIN stopScroll()");
	debugIndent += "  ";
	
	pauseScroll(); // don't care about the return value
	scrollInterval = -1;
	
	debugIndent = debugIndent.replace(/  /, "");
	debugMsg("END stopScroll()");
	
	return true;
}

function restartScroll() {
	
	// unblock and call resumeScroll()
	
	debugMsg("BEGIN restartScroll()");
	debugIndent += "  ";
	
	scrollInterval = null;
	var ok = resumeScroll();
	
	debugIndent = debugIndent.replace(/  /, "");
	debugMsg("END restartScroll { return " + ok + "}");
	return ok;
	
}

function pauseScroll(seconds) {
	
	debugMsg("BEGIN pauseScroll()");
	debugIndent += "  ";
	
	var ok = true;
	if (scrollInterval == -1) {
		// scroll has been stopped, so don't go messing with it
		ok = false;
	} else if (scrollInterval) {
		window.clearInterval(scrollInterval);
		scrollInterval = null;
		if (seconds) window.setTimeout(resumeScroll, seconds * 1000);
	} else {
		ok = false;
	}
	
	debugIndent = debugIndent.replace(/  /, "");
	debugMsg("END pauseScroll { return " + ok + "}");
	return ok;
	
}

function resumeScroll() {
	
	debugMsg("BEGIN resumeScroll()");
	debugIndent += "  ";
	
	var ok = true;
	if (!scrollInterval) {
		if (!scrollArea) {
			debugMsg("Cannot find scrollArea");
			ok = false;
		} else if (!firstItemSize) {
			debugMsg("Cannot find firstItemSize");
			ok = false;
		} else {
			scrollInterval = window.setInterval(scroll, scrollSettings.period);
		}
	} else {
		ok = false;
	}
	
	debugIndent = debugIndent.replace(/  /, "");
	debugMsg("END resumeScroll { return " + ok + "}");
	return ok;
	
}

function play() {
	debugMsg("BEGIN play()");
	debugIndent += "  ";
	scrollSettings.step = scrollSettings.playSpeed;
	debugIndent = debugIndent.replace(/  /, "");
	debugMsg("END play()");
}
function ffwd() {
	debugMsg("BEGIN ffwd()");
	debugIndent += "  ";
	scrollSettings.step = scrollSettings.ffwdSpeed;
	debugIndent = debugIndent.replace(/  /, "");
	debugMsg("END ffwd()");
}
function rewind() {
	debugMsg("BEGIN rewind()");
	debugIndent += "  ";
	scrollSettings.step = scrollSettings.rewindSpeed;
	debugIndent = debugIndent.replace(/  /, "");
	debugMsg("END rewind()");
}

function showPlayButton() {
	document.getElementById("a_pause").className = "hidden scrollcontrol";
	document.getElementById("a_play").className = "scrollcontrol";
}
function showPauseButton() {
	document.getElementById("a_play").className = "hidden scrollcontrol";
	document.getElementById("a_pause").className = "scrollcontrol";
}

function leadingZeros(str, len) {
	var zeros = len - str.length;
	if (zeros > 0) for (var i = 0; i < zeros; i++) str = "0" + str;
	return str;
}

function debugStats() {
	if (debugForm) {
		var now = new Date();
		debugForm.elements["time"].value = leadingZeros(now.getHours(), 2) + ":" + leadingZeros(now.getMinutes(), 2) + ":" + leadingZeros(now.getSeconds(), 2);
		debugForm.elements["period"].value = scrollSettings.period;
		debugForm.elements["step"].value = scrollSettings.step;
		if (scrollArea) {
			debugForm.elements["scrollTop"].value = scrollArea.scrollTop;
			debugForm.elements["scrollLeft"].value = scrollArea.scrollLeft;
		} else {
			debugMsg("scrollArea = undefined");
		}
		debugForm.elements["scrolledOff"].value = scrolledOff;
		debugForm.elements["firstItemSize"].value = firstItemSize;
		debugForm.elements["allItems"].innerHTML = "";
		if (!allItems) {
			debugForm.elements["allItems"].innerHTML = "undefined";
		} else if (scrollSettings.step >= 0) {
			for (var i in allItems) debugForm.elements["allItems"].innerHTML += i + ": " + allItems[i].id + " (" + allItems[i].title + ")\n";
		} else {
			for (var i = allItems.length - 1; i >= 0; i--) debugForm.elements["allItems"].innerHTML += i + ": " + allItems[i].id + " (" + allItems[i].title + ")\n";
		}
	}
}

function debugMsg(msg) {
	if (debugForm) {
		var debugField = debugForm.elements["messages"];
		debugField.value += debugIndent + msg + "\n";
		debugField.scrollTop = debugField.scrollHeight;
	}
}

