/*
 * 
 * INSTRUCTIONS:
 * 
 * 1. Create a DIV element with id -id- where the slider should be placed.
 * 
 * 2. Create an HTML template for the entries and note its id -template-.
 * 
 * 3. Insert the following JavaScript code in the document load function:
 * 
 * 		var slider = new Slider(-id-,-template-,BASE_URL+"/-servlet-",
 * 		-nondefault_id-,-direction-,-style-);
 * 
 * where:
 * 		-id- is given in step 1,
 * 		-tempalte- is given in step 2,
 * 		-servlet- is the name of the servlet (plus parameters, if necessary),
 * 		-nondefault_id- is an id for the slider itself (null as input
 * 			automatically sets the slider's id to SLIDER_ID, see below),
 * 		-direction- is either direction.HORIZONTAL or direction.VERTICAL, and
 * 		-style- is an array of four strings that represent the CSS classes.
 * 
 *  Example:
 *  
 *  	var slider = new Slider("slider-anchor","slider-entry-template",
 *			BASE_URL+"/GetMemberPicturesData?quest="+QUEST_ID,null,direction.HORIZONTAL,
 *			["scroll-left","image-slider-list","scroll-right","image-entire-slider"]);
 * 
 * NOTES:
 * 
 * 	-EXPECTED: The second string in the -style- array should reference a class
 * 	that defines a width in pixels.  That is, within the CSS class there should
 * 	be a line 'width: ??px;'.
 * 
 * 	-DATA INPUT: The data array contains a list of keys and values.  If the key
 * 	begins with "image", then Slider will set the src attribute of the object
 * 	whose id is the given key.  Similarly, keys beginning with "link' set
 * 	the object's href attribute.  The value of data["id"] is used as the id
 * 	for the entry.
 * 
 * 	The function pointer stored in ondownload is executed when new data is downloaded.
 * 	The response from the server is the only input parameter.
 * 
 * ACHTUNG:
 * 
 * 	When adding an event on+'event', you have to update AttrBinding in ajax_json.js.
 * 	See the example with 'onclick' that is already implemented.  To add an event to the
 * 	template, create an attribute in the servlet "on'event'_'name'".  In CreateBinder,
 *  add an if-clause just like for "onclick".  In AttrBinding, create another if-clause
 *	just like the one for "onclick".  You do not need to update CreateBinder and
 *	AttrBinding if the event is already implemented. The value of the attribute
 *	"on'event'_name'" is a string "str;str;str;...", where the first str in the ';'
 *	delimited list is the name of the function to be called when 'event' occurs.
 *	The rest of the string is sent into the function as a parameter.
 *
 *	The slider's list ID is SLIDER_LIST_ID+this.slider_index.
 *
 *	MULTIPLE SLIDERS ON A PAGE: If no ID is passed into the constructor, the slider's
 *	ID takes the form of SLIDER_ID+this.slider_index.
 * 
 * TODO:
 * 
 * 	Currently, the program assumes that each entry has the same width.  In
 * 	order to accomodate cases with entries of variable width, a few changes
 * 	need to be made.
 * 
 * 	Not entirely sure but the servlet for "Member Pictures" gets a list of the
 * 	pictures from the database each time it is called, which can be multiple
 * 	times per second.  Perhaps a more efficient method would be creating a
 * 	cache or session cookie on the server that temporarily stores the 
 * 	list of pictures for faster access.
 * 
 */

/*
 * Global constants.
 */

var SLIDER_ID = "slider_14534";//default id of the slider
var SLIDER_LIST_ID = "slider_list_14534";//id for the list of objects to be scrolled
var SLIDER_BODY_ID = "slider_body_14534";//id for the body of the slider object
var DEFAULT_STYLE = ["scroll-left","","scroll-right","",""];//default classes
var SLIDING_SPEED = 100;//milliseconds per scroll action
var EXTRA_ENTRIES = 3;//number of extra entries to place at the ends of the list
var direction = {"HORIZONTAL":0,"VERTICAL":1};//direction of the slider

/*
 * Global variables.
 */

var request = new JsonRequest();

/*
 * Slider constructor.
 * INPUT:
 * 	-anchor: string id of HTML element where slider is to be attached (required)
 * 	-template: string id of HTML template for the entries in the slider (required)
 * 	-servlet: servlet name string (required)
 * 	-id: id for the slider
 * 	-orientation: vertical or horizontal slider
 * 	-style: array of three strings
 * 		-first string, classes for first button
 * 		-second string, classes for middle area
 * 		-third string, classes for second button
 * 		-fourth string, classes for the entire slider
 * 		-fifth string, classes for the separator
 */
function Slider(anchor,template,servlet,id,orientation,style,looping,ondownload) {
	var obj = $("#"+anchor)[0];
	if(obj == null) return;
	else this.anchor = obj;
	obj = document.getElementById(template);
	if(obj == null) return;
	else this.template = template;
	this.servlet = servlet;
	if(looping == null) looping = true;
	this.static_loop = looping;
	this.ondownload = ondownload;
	if(orientation != null) {
		switch(orientation) {
		case direction.HORIZONTAL:
		case direction.VERTICAL:
			this.direction = orientation;
			break;
		default:
			this.direction = direction.HORIZONTAL;
			break;
		}
	}
	this.HandleSliderId(id);
	if(style != null) this.Initialize(style);
	else this.Initialize(DEFAULT_STYLE);
	if(style.length > 4) this.separator_style = style[4];
	this.InitializeData();
}

/*
 * Slider properties.
 */

Slider.prototype.direction = direction.HORIZONTAL;//horizontal or vertical orientation
Slider.prototype.anchor = null;//pointer to the HTML parent
Slider.prototype.id = "";//id for the slider
Slider.prototype.slider_index = "";//used to distinguish multiple sliders on a single page
Slider.prototype.template = "";//string id of HTML template for the entries in the slider
Slider.prototype.servlet = null;//name of the servlet which delivers the data
Slider.prototype.padded = null;//boolean whether the initial data array was padded with empty images
Slider.prototype.current_right = -1;//integer identifying the last entry on the right
Slider.prototype.current_left = -1;//integer identifying the first entry on the left
Slider.prototype.width = -1;//pixel width of each entry
Slider.prototype.position_change = 0;//number of pixels modulo the entry width that the slider moved
Slider.prototype.processing_left = false;//waiting for entries on the left side of the list?
Slider.prototype.processing_right = false;//waiting for entries on the right side of the list?
Slider.prototype.processing_init = false;//waiting for entries during initiation?
Slider.prototype.list_size = 0;//maximum number of items for the list
Slider.prototype.binder = null;//DataBinder used to fill out the entry templates
Slider.prototype.initializing = 0;//used to control GetData for the first two downloads
Slider.prototype.cancel = 0;//whether to cancel the request; used in resetting the slider
Slider.prototype.sliding = false;//whether SlideTo is currently working
Slider.prototype.slider_interval = -1;//index returned by window.setInterval in SlideTo
Slider.prototype.mouseover = false;//whether mouse is over the slider
Slider.prototype.slider_scroll_max = 0;//sliding variable
Slider.prototype.scrolling = 0;//sliding variable
Slider.prototype.timer = -1;//sliding variable
Slider.prototype.dragging = 0;//dragging variable
Slider.prototype.last_x = 0;//dragging variable
Slider.prototype.last_y = 0;//dragging variable
Slider.prototype.ondownload = null;//pointer to function to be executed on download
Slider.prototype.static_loop = true;//whether to loop even if dynamic downloads are not used
Slider.prototype.top = 0;//index of entry at the top of the list; used with static looping
Slider.prototype.centered = false;//whether SlideTo should center the entry in the middle of the body
Slider.prototype.separator_style = "";//classes for the separator
Slider.prototype.deleting = false;//whether the slider is in the process of being deleted
Slider.prototype.scrollbar = null;//pointer to accompanying scrollbar

/*
 * Slider methods.
 */

/*
 * Creates the buttons and the HTML for the body of the slider.
 * INPUT: 
 * 		-style: Array of strings that are the CSS styles for the buttons
 * 			and body.
 */
Slider.prototype.Initialize = function(style) {
	var left_button = this.CreateLeftButton(style);
	var body = this.CreateBody(style);
	var right_button = this.CreateRightButton(style);
	
	var slider = document.createElement("DIV");
	if(style != null && style.length != null && style.length >= 4) {
		if(!jQuery.browser.msie) slider.setAttribute("class",style[3]);
		else slider.className = style[3];
	}
	slider.id = this.id;
	slider.appendChild(left_button);
	slider.appendChild(body);
	slider.appendChild(right_button);
	
	var me = this;
	slider.onmouseover = function() {me.mouseover = true;};
	slider.onmouseout = function() {me.mouseover = false;};
	
	this.anchor.appendChild(slider);
}

/*
 * Even when the slider uses static data, that is, no dynamic downloads
 * are performed, loop back to the first element instead of stopping
 * at the end of the list when scrolling.
 */
Slider.prototype.EnableLooping = function() {
	this.static_loop = true;
}

Slider.prototype.DisableLooping = function() {
	this.static_loop = false;
}

/*
 * The following two methods set the 'centered' variable.  When enabled,
 * SlideTo positions the entry at the center of the slider.  If disabled,
 * it is positioned at the beginning.
 */
Slider.prototype.EnableCentering = function() {
	this.centered = true;
}

Slider.prototype.DisableCentering = function() {
	this.centered = false;
}

/*
 * Process the id parameter in the constructor.
 * INPUT:
 * 	-id: identification for the slider given by the user
 */
Slider.prototype.HandleSliderId = function(id) {
	if(id == null) {
		if(document.getElementById(SLIDER_ID) != null) {
			var index = 0;
			while(document.getElementById(SLIDER_ID+index) != null)
				index++;
			this.id = SLIDER_ID+index;
			this.slider_index = index;
		} else this.id = SLIDER_ID;
	} else this.id = id;
}

Slider.prototype.CreateLeftButton = function(style) {
	return this.CreateButton(style,1);
}

Slider.prototype.CreateRightButton = function(style) {
	return this.CreateButton(style,3);
}

/*
 * Creates a button.
 * INPUT:
 * 		-style: Array of CSS styles.
 * 		-which: Which button is to be created.
 */
Slider.prototype.CreateButton = function(style,which) {
	var me = this;
	var button = document.createElement("DIV");
	if(style != null && style.length != null && style.length >= which) {
		if(!jQuery.browser.msie) button.setAttribute("class",style[which-1]);
		else button.className = style[which-1];
	}
	if(which == 1) button.onmouseover = function() {me.ScrollUp();};
	else button.onmouseover = function() {me.ScrollDown();};
	button.onmouseout = function() {me.StopScroll();};
	return button;
}

Slider.prototype.CreateBody = function(style) {
	var me = this;
	var body = document.createElement("DIV");
	body.id = SLIDER_BODY_ID+this.slider_index;
	if(style != null && style.length != null && style.length >= 2) {
		if(!jQuery.browser.msie) body.setAttribute("class","slider-body "+style[1]);
		else body.className = "slider-body "+style[1];
	}
	AddEvent(document.body,"mouseup",function(e) {me.StopDrag(e);});
	var list = document.createElement("DIV");
	if(!jQuery.browser.msie) list.setAttribute("class","slider-list");
	else list.className = "slider-list";
	list.id = SLIDER_LIST_ID+this.slider_index;
	list.onmousedown = function(event) {me.StartDrag(event)};
	list.onmouseup = function(event) {me.StopDrag(event)};
	list.onmousemove = function(event) {me.Drag(event)};
	body.appendChild(list);
	return body;
}

/*
 * Initializes some of Slider's variables and requests data from
 * the server to populate the list.
 */
Slider.prototype.InitializeData = function() {
	this.processing_init = true;
	this.SetThisWidth();
	this.BeginInitialDownload();
}

/*
 * Sets the dimension of the entry.  If direction
 * is HORIZONTAL, then this.width
 * contains the width of the entry.  If direction is VERTICAL, then this.width
 * contains the height of the entry.
 */
Slider.prototype.SetThisWidth = function() {
	var list = document.getElementById(SLIDER_LIST_ID+this.slider_index);
	//calculate width
	var clone = $("#"+this.template).clone(true);
	clone.attr("id","tempid-123743532");
	clone.appendTo(list);
	var temp = document.getElementById("tempid-123743532");
	if(this.direction == direction.HORIZONTAL) this.width = temp.offsetWidth;
	else this.width = temp.offsetHeight;
	list.removeChild(temp);
	/*var ptr = $("#"+this.template)[0];
	ptr.parentNode.style.display = "inline";
	if(this.direction == direction.HORIZONTAL) this.width = ptr.offsetWidth;
	else this.width = ptr.offsetHeight;
	ptr.parentNode.style.display = "none";*/
}

/*
 * Create URL and download from the server.
 */
Slider.prototype.BeginInitialDownload = function() {
	var body = document.getElementById(SLIDER_BODY_ID+this.slider_index);
	//calculate number of entries to download
	var num;
	if(this.direction == direction.HORIZONTAL) num = Math.ceil(body.offsetWidth/this.width);
	else num = Math.ceil(body.offsetHeight/this.width);
	if(isNaN(num)) {
		if(console != null) console.log("FATAL ERROR: num is not a number in 'BeginInitialDownload'.");
		return;
	}
	//call server
	if(this.servlet == null) {
		this.processing_init = false;
		return;
	}
	var me = this;
	if(this.servlet.indexOf("?") >= 0)
		request.send(this.servlet+"&init&max="+num,function(data) {me.ProcessInitialResponse(data);});
	else request.send(this.servlet+"?init&max="+num,function(data) {me.ProcessInitialResponse(data);});
}

/*
 * Adds an entry to the list.
 * INPUT:
 * 		-list: Pointer to the HTML object representing the list.
 * 		-data: Data with which to initialize the template.
 * 		-side: Is the entry added on the left or right side?
 * 			true = right; false = left;
 */
Slider.prototype.AddEntry = function(list,data,side) {
	var clone = $("#"+this.template).clone(true);
	clone.attr("id","tempid");
	if(side) clone.appendTo(list);
	else $(list.firstChild).before(clone);
	var temp = document.getElementById("tempid");
	if(this.direction == direction.HORIZONTAL) {
		this.slider_scroll_max -= this.width;
		var current = (isNaN(parseInt($(list).css("width")))) ? 0 : parseInt($(list).css("width"));
		list.style.width = current+this.width+"px";
	} else {
		this.slider_scroll_max -= this.width;
		var current = (isNaN(parseInt($(list).css("height")))) ? 0 : parseInt($(list).css("height"));
		list.style.height = current+this.width+"px";
	}
	temp.style.zIndex = "90";
	temp.id = data["id"];
	this.binder.bindData(temp,data);
	return this.width;
}

/*
 * Processes the first response from the server.  The data array
 * contains a Boolean in the first position indicating whether the 
 * list was padded, that is, "empty" entries were added because 
 * there were not enough entries to fill the width of the slider,
 * an array of ids/attributes that will be set in the template in 
 * the second position, and the number of entries existing on the
 * server in the third position.  The data to fill the template
 * follows. 
 * INPUT:
 * 		-data: Array from server.
 */
Slider.prototype.ProcessInitialResponse = function(data) {
	if(this.cancel > 0) {
		this.cancel--;
		return;
	}
	if(data == null || data.length == null || data.length <= 2)
		return;
	this.padded = data[0];
	this.CreateBinder(data[1]);
	this.list_size = data[2];
	var list = document.getElementById(SLIDER_LIST_ID+this.slider_index);
	var width = 0;
	for(var i = 3; i < data.length; i++) {
		if(data[i].separator) {
			width += this.AddSeparator(true);
			continue;
		}
		width += this.AddEntry(list,data[i],true);
	}
	this.ModifyInitialParameters(width,data.length-3);
	this.processing_init = false;
	if(this.static_loop && this.padded) this.Loop(1,false);
	if(this.ondownload != null) this.ondownload(data);
	if(this.servlet == null) return;
	this.GetData(EXTRA_ENTRIES,true);
	this.GetData(EXTRA_ENTRIES,false);
}

/*
 * Gets width and height of template.
 * INPUT:
 * 		-list: pointer to list node
 */
Slider.prototype.GetTemplateDimensions = function(list) {
	var dim = {"width":0,"height":0};
	var clone = $("#"+this.template).clone(true);
	clone.attr("id","tempid");
	clone.appendTo(list);
	var temp = document.getElementById("tempid");
	dim.width = temp.offsetWidth;
	dim.height = temp.offsetHeight;
	list.removeChild(temp);
	return dim;
}

/*
 * If we have a large dynamic list and we have added the last of all possible
 * elements, add a separator to the list.
 */
Slider.prototype.AddSeparator = function(side) {
	//create separator and add to list
	var separator = document.createElement("DIV");
	if(this.direction == direction.HORIZONTAL) separator.className = "slider_separator-horizontal "+this.separator_style;
	else separator.className = "slider_separator-vertical "+this.separator_style;
	var id = "temp_slider_separator_1345255";
	separator.id = id;
	var list = document.getElementById(SLIDER_LIST_ID+this.slider_index);
	list.appendChild(separator);
	if(side) list.appendChild(separator);
	else $(list.firstChild).before(separator);
	this.slider_scroll_max -= this.width;
	//update the parameters of the entry
	var obj = document.getElementById(id);
	obj.id = "";
	var dim = this.GetTemplateDimensions(list);
	obj.style.width = dim.width+"px";
	obj.style.height = dim.height+"px";
	if(this.direction == direction.HORIZONTAL) {
		var current = (isNaN(parseInt($(list).css("width")))) ? 0 : parseInt($(list).css("width"));
		list.style.width = current+this.width+"px";
	} else {
		var current = (isNaN(parseInt($(list).css("height")))) ? 0 : parseInt($(list).css("height"));
		list.style.height = current+this.width+"px";
	}
	return this.width;
}

/*
 * INPUT:
 * 	-num: number of entries to download
 * 	-side: get entries on the right (true) or left (false) side
 */
Slider.prototype.GetData = function(num,side) {
	if(this.padded) return;
	if(this.servlet == null) return;
	if(this.processing_left && !side) return;
	if(this.processing_right && side) return;
	if(!this.static_loop) {
		if(!side && this.current_left == NegativeMod(-1,this.list_size)) return;
		if(side && this.current_right == 0) return;
		if(!side && this.current_left-num < 0) num = this.current_left+1;
		if(side && this.current_right+num >= this.list_size) num = this.list_size-this.current_right;
	}
	if(side) this.processing_right = true;
	else this.processing_left = true;
	var me = this;
	var current = this.current_left;
	if(side) current = this.current_right;
	var url = this.servlet+"?";
	if(this.servlet.indexOf("?") >= 0) url = this.servlet+"&";
	url += "current="+current+"&padding=false&max="+num+"&side="+side;
	request.send(url,function(data) {me.ProcessResponse(data,side);});
}

/*
 * Sets the list's width, the list's new position, current_right,
 * and current_left.
 * INPUT:
 * 		-list: Pointer to the HTML object representing the list.
 * 		-side: Is the entry added on the left or right side?
 * 			true = right; false = left;
 * 		-width: Number of pixels that the list shrunk/grew.
 * 		-right: current_right.
 * 		-left: current_left.
 * 		-removing: Whether entries were removed or added.
 * 			true = removed; false = added;
 */
Slider.prototype.SetParameters =  function(list,side,width,right,left,removing) {
	var pos = 0;
	if(!side && !removing || side && removing) {
		var current_position;
		if(this.direction == direction.HORIZONTAL) current_position = parseInt(list.style.left.substring(0,list.style.left.length-2));
		else current_position = parseInt(list.style.top.substring(0,list.style.top.length-2));
		if(isNaN(current_position)) current_position = 0;
		if(removing) pos = current_position+width;
		else pos = current_position-width;
		if(this.direction == direction.HORIZONTAL) {
			if(pos > 0) list.style.left = "0px";
			else if(pos < this.slider_scroll_max) list.style.left = this.slider_scroll_max+"px";
			else list.style.left = pos+"px";
		} else {
			if(pos > 0) list.style.top = "0px";
			else if(pos < this.slider_scroll_max) list.style.top = this.slider_scroll_max+"px";
			else list.style.top = pos+"px";
		}
	}
	if(this.static_loop) {
		if(side && !removing || !side && removing) this.current_right = NegativeMod(right,this.list_size);
		else this.current_left = NegativeMod(left,this.list_size);
	}
	if(pos > 0) pos = 0;
	if(pos < this.slider_scroll_max) pos = this.slider_scroll_max;
	if(this.scrollbar != null)
		this.scrollbar.MoveButtonTo(parseInt(parseFloat(pos)/parseFloat(this.slider_scroll_max)*parseFloat(this.scrollbar.pixels)));
}

/*
 * Processes the data received from the server.  Contains only information used
 * to initialize the new entry.
 * INPUT:
 * 		-data: Array from server.
 * 		-side: Is the entry added on the left or right side?
 * 			true = right; false = left;
 */
Slider.prototype.ProcessResponse = function(data,side) {
	if(this.deleting) return;
	if(this.cancel > 0) {
		this.cancel--;
		return;
	}
	if(data == null) {
		if(side) this.processing_right = false;
		else this.processing_left = false;
		return;
	}
	var me = this;
	var list = document.getElementById(SLIDER_LIST_ID+this.slider_index);
	var width = 0;
	for(var i = 0; i < data.length; i++) {
		if(data[i].separator) {
			width += this.AddSeparator(side);
			continue;
		}
		width += this.AddEntry(list,data[i],side);
	}
	this.SetParameters(list,side,width,this.current_right+data.length,
			this.current_left-data.length,false);
	//remove entry from the other side
	if(this.initializing >= 2) this.RemoveEntry(data.length,side);
	if(this.initializing < 2) this.initializing++;
	this.processing_left = false;
	this.processing_right = false;
	if(this.ondownload != null) this.ondownload(data);
	return width;
}

/*
 * Creates the binder that will be used to initialize the templates.
 * INPUT:
 * 		-attr: List of attributes in the template.
 */
Slider.prototype.CreateBinder = function(attr) {
	this.binder = new DataBinder();
	if(attr == null) return;
	for(var i = 0; i < attr.length; i++) {
		var attrn = new String(attr[i]);
		if(attrn.length < 2) continue;
		if(attrn.substring(0,2).toLowerCase() == "id") {
			this.binder.bindAttr("id",attrn);
			continue;
		}
		if(attrn.length < 4) continue;
		if(attrn.substring(0,4).toLowerCase() == "link") {
			this.binder.bindAttr("href",attrn);
			continue;
		}
		if(attrn.substring(0,4).toLowerCase() == "html") {
			this.binder.bindAttr("innerHTML",attrn);
			continue;
		}
		if(attrn.substring(0,4).toLowerCase() == "name") {
			this.binder.bindAttr("name",attrn);
			continue;
		}
		if(attrn.length < 5) continue;
		if(attrn.substring(0,5).toLowerCase() == "image") {
			this.binder.bindAttr("src",attrn);
			continue;
		}
		if(attrn.substring(0,5).toLowerCase() == "style") {
			this.binder.bindAttr("class",attrn);
			continue;
		}
		if(attrn.substring(0,5).toLowerCase() == "value") {
			this.binder.bindAttr("value",attrn);
			continue;
		}
		if(attrn.length < 7) continue;
		if(attrn.substring(0,7).toLowerCase() == "onclick") {
			this.binder.bindAttr("onclick",attrn);
			continue;
		}
	}
}

/*
 * Removes entries from the list.
 * INPUT:
 * 		-num: Number of entries to remove.
 * 		-side: Is the entry added on the left or right side?
 * 			true = right; false = left;
 */
Slider.prototype.RemoveEntry = function(num,side) {
	var list = document.getElementById(SLIDER_LIST_ID+this.slider_index);
	var width = 0;
	if(side) {//remove on the left side
		for(var i = 0; i < num; i++) {
			if(list.firstChild) {
				if(this.direction == direction.HORIZONTAL) width += list.firstChild.offsetWidth;
				else width += list.firstChild.offsetHeight;
				list.removeChild(list.firstChild);
			}
		}
	} else {//remove on the right side
		for(var i = 0; i < num; i++) {
			if(list.lastChild) {
				if(this.direction == direction.HORIZONTAL) width += list.lastChild.offsetWidth;
				else width += list.lastChild.offsetHeight;
				list.removeChild(list.lastChild);
			}
		}
	}
	if(!(this.static_loop && this.padded)) {
		if(this.direction == direction.HORIZONTAL) list.style.width = parseInt($(list).css("width"))-width+"px";
		else list.style.height = parseInt($(list).css("height"))-width+"px";
		this.slider_scroll_max += width;
	}
	this.SetParameters(list,side,width,this.current_right-num,this.current_left+num,true);
}

/*
 * If entry list is static and looping is enabled, then remove one entry from the
 * beginning or end of the list and append to the end or beginning of the list.
 * INPUT:
 * 	-num: number of entries to move
 * 	-side: direction of scrolling
 */
Slider.prototype.Loop = function(num,side) {
	//save the children in a temporary list
	var children = new Array(num);
	var list = document.getElementById(SLIDER_LIST_ID+this.slider_index);
	var child = null;
	if(side) child = list.firstChild;
	else child = list.lastChild;
	for(var i = 0; i < num; i++) {
		if(child == null) {
			num = i+1;
			break;
		}
		children[i] = child;
		if(side) child = child.nextSibling;
		else child = child.previousSibling;
	}
	if(side) this.top = NegativeMod(this.top+num,this.list_size);
	else this.top = NegativeMod(this.top-num,this.list_size);
	//remove the children from one end
	this.RemoveEntry(num,side);
	//add the chldren at the other end
	this.AddNodesToEnd(children,side);
}

/*
 * Add nodes to the other end of the list.
 * INPUT:
 * 	-children: nodes to add
 * 	-side: which end of the list
 */
Slider.prototype.AddNodesToEnd = function(children,side) {
	var node = 0;
	var list = document.getElementById(SLIDER_LIST_ID+this.slider_index);
	if(side) {
		while(children[node] != null) {
			list.appendChild(children[node]);
			node++;
		}
	} else {
		var width = 0;
		if(list.firstChild == null) {
			if(children[node] == null) return;
			list.appendChild(children[node]);
			node++;
		}
		while(children[node] != null) {
			$(list.firstChild).before(children[node]);
			if(this.direction == direction.HORIZONTAL) width += children[node].offsetWidth;
			else width += children[node].offsetHeight;
			node++;
		}
		var position = this.GetListPosition();
		position -= width;
		if(this.direction == direction.HORIZONTAL) list.style.left = position+"px";
		else list.style.top = position+"px";
	}
}

/*
 * Get position of list.
 */
Slider.prototype.GetListPosition = function() {
	var info;
	var current;
	if(this.direction == direction.HORIZONTAL) info = $("#"+SLIDER_LIST_ID+this.slider_index).css("left");
	else info = $("#"+SLIDER_LIST_ID+this.slider_index).css("top");
	if(info != null) {
		current = parseInt(info.substring(0,info.length-2));
		if(isNaN(current)) current = 0;
	}
	return current;
}

/*
 * Updates the variable position_change by adding/subtracting the number of
 * pixels that the list moved.  position_change is an accumulating sum.
 * When the absolute value of this variable exceeds the width of an entry,
 * then a new entry is downloaded.
 * INPUT:
 * 		-v: Number of pixels the list moved.
 */
Slider.prototype.UpdateList = function(v) {
	this.position_change = this.position_change+v;
	var num = Math.floor(Math.abs(this.position_change)/this.width);
	if(this.position_change >= this.width) {
		this.GetData(num,false);
		if(this.static_loop && this.padded) this.Loop(num,false);
		this.position_change = this.position_change % this.width;
	}
	if(this.position_change <= -this.width) {
		this.GetData(num,true);
		if(this.static_loop && this.padded) this.Loop(num,true);
		this.position_change = -(Math.abs(this.position_change) % this.width);
	}
}

/*
 * Set a few initialization parameters.
 * INPUT:
 * 	-width: size of the list in number of pixels
 * 	-length: number of entries in the list
 */
Slider.prototype.ModifyInitialParameters = function(width,length) {
	var body = document.getElementById(SLIDER_BODY_ID+this.slider_index);
	var list = document.getElementById(SLIDER_LIST_ID+this.slider_index);
	this.current_right = NegativeMod(length,this.list_size);
	this.current_left = NegativeMod(-1,this.list_size);
	if(this.direction == direction.HORIZONTAL) this.slider_scroll_max += body.offsetWidth;
	else this.slider_scroll_max += body.offsetHeight;
}

/*
 * Clear the list and request fresh data from the server.
 * ASSUMPTION: Responses return in order of the requests.
 * INPUT:
 * 	-data: data list without the first three initialization parameters
 */
Slider.prototype.reset = function(data) {
	if(this.deleting) return;
	this.CancelAll();
	var list = document.getElementById(SLIDER_LIST_ID+this.slider_index);
	while(list.childNodes.length > 0)
		list.removeChild(list.childNodes[0]);
	list.style.left = "0px";
	list.style.top = "0px";
	this.top = 0;
	if(data != null) {
		var width = this.ProcessResponse(data,true);
		if(width != null) this.ModifyInitialParameters(width,data.length);
		if(this.static_loop && this.padded) this.Loop(1,false);
		return;
	}
	this.processing_init = true;
	this.BeginInitialDownload();
}

/*
 * Cancels currently running requests and resets a few parameters.
 */
Slider.prototype.CancelAll = function() {
	if(this.processing_init) {
		this.cancel++;
		this.processing_init = false;
	}
	if(this.processing_left) {
		this.cancel++;
		this.processing_left = false;
	}
	if(this.processing_right) {
		this.cancel++;
		this.processing_right = false;
	}
	this.initializing = 0;
	this.position_change = 0;
	this.slider_scroll_max = 0;
}

/*
 * Scrolling and dragging functions.
 */

/*
 * The following functions are used when the user places the cursor over
 * one of the slider's buttons.
 */
Slider.prototype.ScrollAtIntervals = function(v) {
	var me = this;
	if(this.sliding) this.TurnSlidingOff();
	var newpos = 0;
	var oldpos = 0;
	var info;
	if(this.direction == direction.HORIZONTAL) info = $("#"+SLIDER_LIST_ID+this.slider_index).css("left");
	else info = $("#"+SLIDER_LIST_ID+this.slider_index).css("top");
	if(info != null) {
		var length = info.length;
		var num = info.substring(0,length-2);
		oldpos = parseInt(num);
		newpos = oldpos+v;
	}
	if(isNaN(newpos)) newpos = 0;
	if(newpos > 0) {
		newpos = 0;
		if(!this.padded && this.static_loop) this.GetData(3,false);
	}
	if(newpos < this.slider_scroll_max) {
		newpos = this.slider_scroll_max;
		if(!this.padded && this.static_loop) this.GetData(3,true);
	}
	if(this.direction == direction.HORIZONTAL) $("#"+SLIDER_LIST_ID+this.slider_index).css("left",newpos);
	else $("#"+SLIDER_LIST_ID+this.slider_index).css("top",newpos);
	if(this.scrollbar != null)
		this.scrollbar.MoveButtonTo(parseInt(parseFloat(newpos)/parseFloat(this.slider_scroll_max)*parseFloat(this.scrollbar.pixels)));
	if(oldpos != newpos) this.UpdateList(v);
	if(this.scrolling != 0) {
		this.scrolling = 0;
		this.timer = window.setInterval(function() {me.ScrollAtIntervals(v)},SLIDING_SPEED);
	}
}

Slider.prototype.IsSmallList = function() {
	var body = document.getElementById(SLIDER_BODY_ID+this.slider_index);
	if(this.direction == direction.HORIZONTAL) {
		if(this.list_size*this.width < body.offsetWidth) return true;
		return false;
	} else {
		if(this.list_size*this.width < body.offsetHeight) return true;
		return false;
	}
}

Slider.prototype.ScrollUp = function() {
	if(this.deleting) return;
	if(this.IsSmallList()) return;
	this.scrolling = 1;
	this.ScrollAtIntervals(10);
}

Slider.prototype.ScrollDown = function() {
	if(this.deleting) return;
	if(this.IsSmallList()) return;
	this.scrolling = 1;
	this.ScrollAtIntervals(-10);
}
	 
Slider.prototype.StopScroll = function() {
	this.scrolling = 0;
	if(this.timer >= 0) window.clearInterval(this.timer);
	this.timer = -1;
}

/*
 * The following functions are used when the user clicks on the list
 * and attempts to drag.
 */
Slider.prototype.StartDrag = function(ev) {
	if(this.deleting) return;
	if(this.IsSmallList()) return;
	if(this.scrollbar != null && this.scrollbar.active) return;
	disableSelection(document.body);
	this.TurnSlidingOff();
	this.dragging = 1;
	ev = ev || window.event;
	var mouse = getMouseCoordinates(ev);
	this.last_x = mouse.x;
	this.last_y = mouse.y;
}
	 
Slider.prototype.StopDrag = function(ev) {
	this.dragging = 0;
	enableSelection(document.body);
}
	 
Slider.prototype.Drag = function(ev) {
	if(this.sliding) return;
	if(this.scrollbar != null && this.scrollbar.active) return;
	click = false;
	if (this.dragging != 0) {
		ev = ev || window.event;
		var mouse = getMouseCoordinates(ev);
		var v;
		if(this.direction == direction.HORIZONTAL) v = parseInt(mouse.x)-parseInt(this.last_x);
		else v = parseInt(mouse.y)-parseInt(this.last_y);
		var newpos;
		var oldpos;
		if(this.direction == direction.HORIZONTAL) {
			oldpos = parseInt($("#"+SLIDER_LIST_ID+this.slider_index).css("left"));
			newpos = oldpos+v;
		} else {
			oldpos = parseInt($("#"+SLIDER_LIST_ID+this.slider_index).css("top"));
			newpos = oldpos+v;
		}
		if (newpos > 0)
			newpos = 0;
		if (newpos < this.slider_scroll_max)
			newpos = this.slider_scroll_max;
		if(this.direction == direction.HORIZONTAL) $("#"+SLIDER_LIST_ID+this.slider_index).css("left",newpos);
		else $("#"+SLIDER_LIST_ID+this.slider_index).css("top",newpos);
		this.last_x = mouse.x;
		this.last_y = mouse.y;
		if(!IE) deselect();
		if(oldpos != newpos) this.UpdateList(v);
		if(this.scrollbar != null)
			this.scrollbar.MoveButtonTo(parseInt(parseFloat(newpos)/parseFloat(this.slider_scroll_max)*parseFloat(this.scrollbar.pixels)));
	}
}

/*
 * Move the slider to the entry referenced by the index.  Cancels a previous
 * sliding job if a new one comes in from the user, ie. when the user moves
 * the mouse over the player icons quickly.
 * INPUT:
 * 	-index: which entry in the list to show in the beginning
 * 	-jump: whether to animate the change
 * 		-false: animate sliding; true: just jump to the position
 */
Slider.prototype.SlideTo = function(index,jump) {
	if(!this.padded || this.deleting) return;//if list loops, this function does not work
	if(this.sliding) {
		if(this.slider_interval < 0) {
			//in this case, sliding has started but hasn't reached the setInterval call
			window.setTimeout(function() {me.SlideTo(index);},2);
			return;
		} else window.clearInterval(this.slider_interval);
	}
	this.slider_interval = -1;
	this.sliding = true;
	if(this.top <= index) index -= this.top;
	else index += this.list_size-this.top;
	this.Slide(index,jump);
}

/*
 * Moves the slider to the given pixel position relative to the top/left.
 * INPUT:
 * 	-pixel: place where to position the slider list
 */
Slider.prototype.MoveTo = function(pixel) {
	var list = document.getElementById(SLIDER_LIST_ID+this.slider_index);
	if(-pixel < this.slider_scroll_max || -pixel > 0) return;
	if(this.direction == direction.HORIZONTAL)
		list.style.left = -pixel+"px";
	else list.style.top = -pixel+"px";
}

/*
 * Slide the list to the desired position.  Used in conjunction with
 * SlideTo.
 * INPUT:
 * 	-index: which entry in the list to show in the beginning
 */
Slider.prototype.Slide = function(index,jump) {
	var current = this.GetListPosition();
	var goal = -index*this.width;
	if(this.centered) {
		if(this.direction == direction.HORIZONTAL) goal += document.getElementById(SLIDER_BODY_ID+this.slider_index).offsetWidth/2-this.width/2;
		else goal += document.getElementById(SLIDER_BODY_ID+this.slider_index).offsetHeight/2-this.width/2;
	}
	var distance = current-goal;//distance to travel
	if(Math.abs(distance) <= 1) {//we're there
		this.TurnSlidingOff();
		this.UpdateList(0);
		return;
	}
	//move only 10 pixels each time
	if(!jump) {
		if(distance > 10) distance = 10;
		if(distance < -10) distance = -10;
	}
	var newpos = current-distance;
	if(newpos < this.slider_scroll_max) {
		distance = this.slider_scroll_max-current;
		newpos = this.slider_scroll_max;
	}
	if(newpos > 0) newpos = 0;
	if(this.direction == direction.HORIZONTAL) $("#"+SLIDER_LIST_ID+this.slider_index).css("left",newpos);
	else $("#"+SLIDER_LIST_ID+this.slider_index).css("top",newpos);
	if(this.scrollbar != null)
		this.scrollbar.MoveButtonTo(parseInt(parseFloat(newpos)/parseFloat(this.slider_scroll_max)*parseFloat(this.scrollbar.pixels)));
	this.position_change -= current-newpos;
	if(newpos <= this.slider_scroll_max || newpos == 0) {
		this.TurnSlidingOff();
		this.UpdateList(0);
		return;
	}
	if(this.slider_interval < 0) {
		var me = this;
		this.slider_interval = window.setInterval(function() {me.Slide(index,jump);},10);
	}
}

/*
 * Turns sliding off.
 */
Slider.prototype.TurnSlidingOff = function() {
	this.sliding = false;
	if(this.slider_interval >= 0) window.clearInterval(this.slider_interval);
	this.slider_interval = -1;
}

/*
 * Deletes slider from the document.
 */
Slider.prototype.Delete = function() {
	this.deleting = true;
	this.TurnSlidingOff();
	this.StopScroll();
	this.StopDrag();
	var temp = document.getElementById(this.id);
	if(temp != null) this.anchor.removeChild(document.getElementById(this.id));
	if(this.scrollbar != null) this.scrollbar.Delete();
	this.scrollbar = null;
}

/*
 * Deletes the scrollbar.
 */
Slider.prototype.DisableScrollbar = function() {
	if(this.scrollbar == null) return;
	document.getElementById(this.scrollbar.id).style.display = "none";
}

/*
 * Creates the scrollbar.
 */
Slider.prototype.EnableScrollbar = function(id,pixel,offset) {
	if(this.scrollbar == null) 
		this.scrollbar = new Scrollbar(this,id,pixel,offset);
	else 
		document.getElementById(this.scrollbar.id).style.display = "inline";
}
