Map = function(){
	this.table = new Object();
	this.keySet = new Array();
	this.put = function(K,V){if(typeof(K)!="undefined"){if(this.contains(K)==false){this.keySet.push(K);this.table[K]=typeof(V)=="undefined"?null:V;return true;}else{return false;}}else{return false;}}
	this.remove = function(k){delete this.table[k];for(i=0;i<this.keySet.length;i++){if(k==this.keySet[i]){this.keySet.splice(i,1);break;}}}
	this.size = function(){var i=0;for(var k in this.table){i++;} return i;}
	this.get = function(key){return this.table[key];}
	this.getAt = function(i){return this.get(this.keySet[i]);}
	this.contains = function(key){ return typeof(this.table[key])!="undefined";}
	this.clear = function(){for(var k in this.table){delete this.table[k];}}
	this.keys = function(){return this.keySet;}
}
/**
 * 
 * @param {DomElement} container
 * @param {int} interval
 */
function BaseSlideShow(container, interval){
	this.interval = interval;
	this.active = 0;
	this.map = new Map();
	
	/**
	 * The start function that init the timer
	 */
	this.start = function (){
		if (this.active){
			// throw an exception, should be IllegalStateException
			throw "IllegalTimerStateException: Timer already started ...";
		}
		// activation flag
		this.active = 1;
		// set the timer interval
		var owner = this;
		this.timer = setInterval(function(){owner.next(true)}, this.interval);
		if (this.toggleBtn && this.playClass){
			this.toggleBtn.className = this.stopClass;
		}
	}
	/**
	 * Stop the timer
	 */
	this.stop = function (){
		// chech if timer was not set
		if (!this.timer){
			// throw new IllegalTimerStateException();
			throw "IllegalTimerStateException: Timer has not started yet ...";
		}
		// deactivatation flag
		this.active = 0;
		// clear the interval so that the time stop calling the method
		clearInterval(this.timer);
		if (this.toggleBtn && this.stopClass){
			this.toggleBtn.className = this.playClass;
		}
	}
	/**
	 * if not started, start, else it stops it
	 * @param {Object} e: the Elemet which was clicked
	 * @param {Stirng} playClass: css class in case of class
	 * @param {String} stopClass: css class in case of stop
	 */
	this.toggle = function(e, playClass, stopClass){
//		alert(e.className);
		try{
			this.start();
			// if started, apply stop class to it (i.e. Stop icon)
//			if (e && e.className)
//				e.className = stopClass;
		}catch(err){
			this.stop();
//			if (e && e.className)
//				e.className = playClass;
		}
	}
	/**
	 * Perfom fading between two elements
	 * @param {Object} e1: first element
	 * @param {Object} e2: second element
	 * @param {int} steps: number of steps
	 * @param {int} delay: delay between each step [fade Interval]
	 */
	this.fadeInOut = function (e1, e2, steps, delay){
		var step = 100 / steps;
//		for(var i=0; i< steps + 1; i++){
			setTimeout(getAlphaCaller(e1, e2, step, step, delay), delay);
//		}
	}
	/**
	 * As apply alpha is called from a call back function upon user event
	 * the this object doesn't refere to the current slide show objec
	 * so, we need to use JS closure to solve this issue
	 * @param {Object} e1: first element
	 * @param {Object} e2: second element
	 * @param {int} amount: the opacity amount
	 */
	function getAlphaCaller(e1, e2, amount, step, delay){
//		for( var i = 0; i < arguments.length; i++ ) {
//			alert("This accident was caused by " + arguments[i]);
//		}
		
		return function(){
				if (amount + step <= 100){
					setTimeout(getAlphaCaller(e1, e2, amount + step, step, delay), delay);
				} else {
					setTimeout(function() {
						e1.style.zIndex = 0;
						e2.style.zIndex = 999;
					}, delay);
				}
				applyAlpha(e1, e2, amount);
			}
	}
	/**
	 * This is the actual function to be executes
	 * @param {Object} e1: first element
	 * @param {Object} e2: second element
	 * @param {int} v: the opacity amount
	 */
	function applyAlpha(e1, e2, v){
		if (e2) {
			e2.style.filter = "alpha(opacity=" + v + ")";
			e2.style.opacity = v / 100;
			addCSSClass(e2, "selected");
		}
		if (e1) {
			e1.style.filter = "alpha(opacity=" + (100 - v) + ")";
			e1.style.opacity = 1 - (v / 100);
			if(v >= 100){
				removeCSSClass(e1, "selected");
			}
		}
	}
	/**
	 * go to the next element in the slide show
	 * @param {boolean} fade: fade or not
	 */
	this.next = function (fade){
		i = this.getSelected();
		this.goto(i, ++i, fade);	// this method is implemented in the sub class :)
	}
	/**
	 * go to the prev element in the slide show
	 * @param {boolean} fade: fade or not
	 */
	this.prev = function (fade){
		i = this.getSelected();
		this.goto(i, --i, fade);
	}
	/**
	 * go to the first element in the slide show
	 * @param {boolean} fade: fade or not
	 */
	this.first = function(fade){
		var size = this.map.size();
		i = this.getSelected();
		if (i != 0){
			this.goto(i, 0, fade);
		}
	}
	/**
	 * go to the last element in the slide show
	 * @param {boolean} fade: fade or not
	 */
	this.last = function(fade){
		var lastIndex = this.map.size() - 1;
		i = this.getSelected();
		if (i != lastIndex){
			this.goto(i, lastIndex, fade);
		}else{
//			alert("watch out, this is the last element ... ");
			return;
		}
	}
	/**
	 * 
	 * @param {String} containerClass
	 * @param {String} containerId
	 * @param {Array[Strings} classNames: must be in the order, 
	 * 							First, prev, play, stop, next, last	: Add all buttons
	 * 					OR		prev, play, stop, next				: don't add first & last
	 * 					OR		play, stop							: only add play | stop
	 * 
	 */
	this.addControls = function(containerClass, containerId, 
			classNames){
		
		if(!containerClass){
			// throw "";
		}
		var count = classNames.length;
//		var vv = document.getElementById("x");
		var controlsContainer = document.createElement("div");
		controlsContainer.className = containerClass;
		controlsContainer.id = containerId;
		container.appendChild(controlsContainer);
		
		var controls = [];
		var playIndex = count / 2 - 1;			// if 2 classes were passed, result = 2 / 2 - 1 = 1 - 1 = 0
												// if 4 classes were passed, result = 4 / 2 - 1 = 2 - 1 = 1
												// if 6 classes were passed, result = 6 / 2 - 1 = 3 - 1 = 2
		for (var i=0; i<count; i++){
//			alert(i);
			if (i == playIndex + 1){
				controls[i-1].className = classNames[i];
//				if (!this.toggleBtn)
					this.toggleBtn = controls[i-1];
					this.playClass = classNames[i - 1];
					this.stopClass = classNames[i];
				continue;
			}
		
			controls[i] = document.createElement("span");
			controls[i].className = classNames[i];
			
//			controls[i].style.filter = "alpha(opacity=30)";
//			controls[i].style.opacity = .3;
//			controls[i].onmouseover = function(){
//				this.style.filter = "alpha(opacity=100)";
//				this.style.opacity = 1;
//			}
//			controls[i].onmouseout = function(){
//				this.style.filter = "alpha(opacity=30)";
//				this.style.opacity = .3;
//			}
			if (this.map.size() == 1) {
				controls[i].disabled = true;
				controls[i].background="#CCCCCC";
			}
			controlsContainer.appendChild(controls[i]);
		}
		
		var _this = this;
		
		if (this.map.size() > 1) {
			controls[playIndex].onclick = function(){
				_this.toggle(_this.toggleBtn, classNames[playIndex], classNames[playIndex + 1]);
		}
	}
		/*
		var prevIndex = count / 3 - 1;			// if 4 classes were passed, result = 4 / 3 - 1 = 1 - 1 = 0
												// if 6 classes were passed, result = 6 / 3 - 1 = 2 - 1 = 1
		*/
		if (count == 4){
			controls[0].onclick = function(){
				_this.prev(false);
			}
			controls[count - 1].onclick = function(){
				_this.next(false);
			}
		}else if (count == 6){
			controls[0].onclick = function(){
				_this.first(false);
			}
			controls[1].onclick = function(){
				_this.prev(false);
			}
			controls[count - 2].onclick = function(){
				_this.next(false);
			}
			controls[count - 1].onclick = function(){
				_this.last(false);
			}
		}
	}
}

function SlideShow(containerId, interval){
	this._super = BaseSlideShow;
	/**
	 * this method is called from the super methods (mimic to virtual functions in C++)
	 * @param {Object} current
	 * @param {Object} target
	 * @param {Object} fade
	 */
	this.goto = function (current, target, fade){
		if (target == current){
			return;
		}
		var size = this.map.size();

		var steps = 5;
		var delay = 200;
		target = (target + size) % size;
		if (fade) {
			this.fadeInOut(this.map.getAt(current), this.map.getAt(target), steps, delay);
//			var _this = this;
//			setTimeout(function(){
//				_this.map.getAt(current).className = "";
//				_this.map.getAt(target).className = "selected";
//			}, steps * delay);
		}else{
			this.map.getAt(current).className = "";
			this.map.getAt(current).style.filter = "alpha(opacity=0)";
			this.map.getAt(current).style.opacity = 0;
			this.map.getAt(current).style.zIndex = 0;
			this.map.getAt(target).style.filter = "alpha(opacity=100)";
			this.map.getAt(target).style.opacity = 1;
			this.map.getAt(target).style.zIndex = 999;
			this.map.getAt(target).className = "selected";
		}
	}
	/**
	 * get the index of the currently selected element
	 */
	this.getSelected = function (){
		var size =	 this.map.size();
		for(var i=0; i<size; i++){
			var c = this.map.getAt(i);
			if (c.className.toLowerCase().indexOf("selected") != -1){
				return i;
			}
		}
	}
	
	this._constructor = function (){
		if (containerId == null){
			throw "NullPointerException: container id is null ...";
		}
		this.container = document.getElementById(containerId);
	
		// get the container element to updates its li elements 
		if (this.container == null){
			throw "InvalidArgumentException: Invalid container id ...";
		}
		try{
			this._super(this.container, interval);
		}catch(err){
			// do nothing as the container id is invalid
			alert("error initializing super");
		}
		var li_elements = this.container.getElementsByTagName("li")
		for(var i=0; i<li_elements.length; i++){
			// save the list item in the hash table 
			this.map.put(i, li_elements[i]);
		}
	}
	
	this._constructor();	// call the virtual constructor
}











































/**
 * 
 * @param {String} containerId
 * @param {int} interval
 * @param {Array} sources
 * @param {index} selected
 * @param {array} classes
 * @param {int} fadeInterval
 */
function LazySlideShow(containerId, interval, sources, selected, classes, fadeInterval){
	this._super = BaseSlideShow;
	if (containerId == null){
		throw "InvalidArgumentException: Invalid container id [null]...";
	}
	this.container = document.getElementById(containerId);
	this.current = selected;
	this.rand = Math.random();
	this.fadeInterval = fadeInterval;
	this.rand = ((this.rand + "").substring((this.rand + "").indexOf(".") + 1));
	
	this.tempMap = new Map();
	
	/**
	 * 
	 * @param {int} current
	 * @param {int} target
	 * @param {boolean} fade
	 */
	this.goto = function (current, target, fade){
		if (target == current){
			return;
		}
		var steps = 10;
		var delay = interval / (steps * 2);
		
		var size = this.map.size();
		target = (target + size) % size;
		this.current = target;
		
		if (fade) {
			this.fadeInOut(this.map.getAt(current), this.map.getAt(target), steps, this.fadeInterval);
			var _this = this;
//			setTimeout(function(){
//				_this.map.getAt(current).className = "";
//				_this.map.getAt(target).className = "selected";
//			}, steps * delay);
		}else{
			this.map.getAt(current).className = "";
			this.map.getAt(current).style.filter = "alpha(opacity=0)";
			this.map.getAt(current).style.opacity = 0;
			this.map.getAt(target).style.filter = "alpha(opacity=100)";
			this.map.getAt(target).style.opacity = 1;
			this.map.getAt(target).className = "selected";
		}
	}
	
	this.getSelected = function (){
		return this.current;
	}
	
	
	this._constructor = function (){
		// try to intialize the super 
		try{
			this._super(this.container, interval);
		}catch(err){
			// do nothing as the container id is invalid
			alert("error initializing super");
		}
		var children = this.container.getElementsByTagName("img");
		this.imgContainer = document.createElement("div");
		this.imgContainer.id = "images_container_" + this.rand;
		this.container.appendChild(this.imgContainer);
		
		for (var i = 0; i < children.length; i++) {
			this.container.removeChild(children[i]);
		}
		this.loadImages(sources);
		// add the controll buttons with the following styles to the buttons
		this.addControls("slideshow-control-buttons", "slideshow-control-buttons",
					[	"slideShowFirst",
						"slideShowPrev",
						"slideShowPlayIcon",
						"slideShowStopIcon",
						"slideShowNext",
						"slideShowLast"
					]);
	}
	this.loadImages = function (sources){
		for(var i=0; i<sources.length; i++){
			// save the list item in the hash table 
			var img = new Image();
			img.src = sources[i];
			img.id = "LazySlideShow_" + this.rand + i;
			img.style.opacity = 0;
			img.style.filter = "alpha(opacity = 0)	";
			if (i == selected){
				img.className = "selected";
				img.style.opacity = 1;
				img.style.filter = "alpha(opacity = 100)";
			}
			this.tempMap.put(i, img);
			var timeout = 10;
		}

		// recursive check to check if the image is loaded or not,
		// if the image is not loaded it sets timeout again
		var _this = this;
		var func = function(){
			for (var i = 0; i < _this.tempMap.size(); i++) {
				if (_this.tempMap.get(i)) {
					if (_this.tempMap.get(i).complete) {
						var tmpId = _this.tempMap.get(i).id;
						_this.imgContainer.appendChild(_this.tempMap.get(i));
						_this.map.put(tmpId, document.getElementById(tmpId));
						if (_this.map.size() == _this.tempMap.size()){
							clearInterval(_this.x);
						}
					}
//					else {
//						document.write(i + "  ")
//					}
				}
			}
		}
		// call the function to check if the images are loaded or not
		// loaded images are added to the hash table. if all images are loaded, it 
		// clears the interval
		this.x = setInterval(func, 100);
	}
	// define the play/stop button as a member variable
	this.toggleBtn = null;
	
	// call the [virtual] constructor
	this._constructor();
}	