/**
 * @xmlimageflow
 *
 * XMLImageFlow is a jQuery plugin created by Esteban Acevedo / Y2K Services, Inc. / Next Horizon
 * This plugin will create a dynamic image gallery based on a list of images or an XML list of images.
 * The image gallery will look like a 3D carrousel where images will rotate left and right as if they 
 * were over a rotating platform.
 * Keep in mind that this plugin will only work for images that share the same dimensions.  If images with
 * different dimensions are used, clipping will occur.
 * Reflection is not supported.  If you want to give your gallery a reflection effect then you need to add 
 * the reflection manually to the image using any image manipulation software.
 *
 * Depends on:
 * 1) jquery-1.4.2.min.js
 * 2) jquery.easing.1.3.js
 * 3) jquery.mousewheel.js
 *
 * Usage:
 * 1) Include this source file in an html page via
 *    <script type="text/javascript" src="/path/to/xmlimageflow.js"></script>
 *    Make sure you call this js file after all other dependency files have been called.
 *	  i.e.
 *    <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>
 *    <script type="text/javascript" src="js/jquery.easing.1.3.js"></script>
 *    <script type="text/javascript" src="js/jquery.mousewheel.js"></script>
 *    <script type="text/javascript" src="js/xmlimageflow.js"></script>
 *	
 * 2) In your page script section or your custom actions js file add the following
 *    initialization line: var yourglobalhandler = $("yourselector").xmlimageflow();
 * 3) You can pass your own options: i.e. $("#xif").xmlimageflow({width:'300px',height:'100px'});
 *	  Default Options:
 * 	  		width: '100%', //Width of the XMLImageFlow object frame.  Can be any CSS value. -String
 *			height: '800px', //Height of the XMLImageFlow object frame.  Can be any CSS value. -String
 *			imageWidth: 415, //Width of the images.  -Integer
 *			imageHeight: 700, //Height of the images.  -Integer
 *			loadFromXML: false, //Flag to indicate of the image data should be loaded from an external XML file.  -Boolean
 *			dataSource: '', //URL of the XML file.  String
 *			startImage: 4, //Which image to show on load.  Should be ((numSectors + 1) / 2). -Integer.
 *			numSectors: 7, //Should always be an odd value starting from 3.  -Integer.
 *			speed: 50, //Animation duration of the glide effect. -Integer.
 *			webImageFolder: 'img/', //Folder where all the web ready images are stored. Must include the / at the end. -String.
 *			webImageExt: '.png', //File Type of all web ready images. Must include the dot.  Must be lowercase. -String.
 *			posterFolder: 'poster/', //Folder where all the original images are stored. Must include the / at the end. -String.
 *			posterExt: '.pdf', //File Type of all original images. Must include the dot.  Must be lowercase. -String.
 *			totalFieldID: '', //ID of the span that will be used to display the total.
 *			overlayID: '' //ID of the DIV containing the overlay HTML for the focused image on mouse over. -String.
 *			overlayImage: '' //File path of the image to use instead of the overlay div color. -String.
 * 4) You can bind any XMLImageFlow commands to your controls.  i.e. yourglobalhandler.moveLeft();
 *    will cause the carousel to glide to the left.
 *
 * Markup:
 * 1) If the images are not stored in an external XML file use this markup
 * <div id="xif" class="xif">
 *    <div class="xif-image"><p>imagepath/imagename1.ext</p><p>Keywords</p></div>
 *    <div class="xif-image"><p>imagepath/imagename2.ext</p><p>Keywords</p></div>
 * </div>
 *
 * 2) If the images are stored in an external XML file use this markup
 * <div id="xif" class="xif"></div>
 * 
 * Special Thanks And Recognition:
 * Inspired on the original ImageFlow by Finn Rudolph
 *
 * This solution is known as the Cover Flow technique, which has been developed by the artist 
 * Andrew Coulter Enright. Now - after it has been bought by Apple - it is used in 
 * iTunes and the file browser of Apples OSX.
 *
 * Special thanks to John O. Eze and Brett Callaghan for their ideas, suggestions
 * and troubleshooting!
 *		
 * Change log:
 * Esteban Acevedo, 2010_06_03
 *   Version 1.0.0.0 is created.
 * Esteban Acevedo, 2010_06_09
 *	 Version 1.0.0.1 is created.
 *   Change - Replaced img tags for div tags to speed up the loading process.
 *   Addition - Added the search functionality.
 * Esteban Acevedo, 2010_06_10
 *   Version 1.0.0.2 is created.
 *   Addition - Added support to read images from an XML file.
 * Esteban Acevedo, 2010_06_15
 *   Version 1.0.0.3 is created.
 *   Addition - Added overlay gray out effect for non-focused images.
 *	 Addition - Added mouse over overlay display functionality.
 *	 Change - Lock the mouse wheel to the XMLImageFlow object if triggered when on top of 
 *	 		  the object to prevent browser window scrolling while browsing images.
 * Esteban Acevedo, 2010_06_16
 *   Version 1.0.0.4 is created.
 *	 Change - Made the image positioning calculation dynamic instead of static for 7 images.
 *	 Fix - Fixed the numSectors error.
 * Esteban Acevedo, 2010_06_17
 *   Version 1.0.0.5 is created.
 *	 Addition - Added custom overlay functionality.
 */

(function($){

	$.fn.xmlimageflow = function(options) {
		// support mutltiple elements
		if (this.length > 1){
			this.each(function() { $(this).xmlimageflow(options) });
			return this;
		}	
		// private variables
		
		var empty = {};
		var defaults = {
			width: '100%',
			height: '422px',
			imageWidth: 365,
			imageHeight: 422,
			loadFromXML: false,
			dataSource: '',
			startImage: 2,
			numSectors: 3,
			speed: 50,
			webImageFolder: 'img/',
			webImageExt: '.png',
			posterFolder: 'poster/',
			posterExt: '.pdf',
			totalFieldID: '',
			overlayID: '',
			overlayImage: ''
		};
			
		var $obj = this;
	
		//public variables
		this.xifrawdata = new Array();
		this.xifimage = new Array();
		this.xifimagedata = new Array();
		this.xifsearchkeys = new Array();
		this.moving = new Array();
		this.searching = false;
		this.automove = -1;
		this.targetImageID = -1;
		this.currentImage = -1;
		
		this.settings = $.extend(empty, defaults, options);
		
		// public methods
		this.ie8SafePreventEvent = function(e){
			if (e.preventDefault) { 
				e.preventDefault(); 
			} else { 
				e.returnValue = false; 
			}
		}
		
		this.init = function() {
			var $this = this;
			var o = this.settings;
			
			var initStatus = 1;
			//Init Status Codes:
			//1: OK
			//2: Error Loading Data (see data option)
			//3: Error Loading Markup (see html markup)
			
			var markup = $this.html();
			$this.contents().remove();
			$("body").append("<div class=\"xif-temp\"></div>");
			$(".xif-temp").html(markup);
			$this.resize();
			
			if (o.loadFromXML == true) {
				//Load data from xml
				if (o.data == "") {
					initStatus = 2;
				} else {
					this.loadImageDataFromXML();
				}			
			} else {
				//Convert img tags to placeholders
				if($(".xif-temp").find("div[class='xif-image']").length == 0) {
					initStatus = 3;
				} else {
					this.loadImageDataFromMarkup();
				}
			}
			return this;
		};
	
		//public method initSectors
		this.loadImageDataFromXML = function() {
			var $this = this;
			var o = this.settings;
			
			this.xifrawdata = new Array();
			this.xifimage = new Array();
			this.xifsearchkeys = new Array();
			
			$(".xif-temp").contents().remove();
			$(".xif-temp").remove();			
			
			$this.displayLoading();
			
			$.ajax(
				{ 
					type: "GET", 
					url: o.dataSource, 
					dataType: ($.browser.msie) ? "text" : "xml",
					beforeSend: function(XMLHttpRequest){
						// Handle the beforeSend event
					},
					complete: function(XMLHttpRequest, textStatus){
						// Handle the complete event
					},
					error: function(XMLHttpRequest, textStatus, errorThrown){
						// Handle the error event
					},
					success: function(data, textStatus, XMLHttpRequest){
						var xml;
						var imageData = {};
						var result = new Array();
						if (typeof data == "string") { 
							xml = new ActiveXObject("Microsoft.XMLDOM"); 
							xml.async = false; 
							xml.loadXML(data); 
						} else { 
							xml = data; 
						}
						var _id = -1;
						var _src = "";
						var _key  = "";
						var _keys = new Array();
						$("posters poster",xml).each(function(){
							imageData = {};
							_id++;
							_src = $("src",this).text();
							_keys = new Array();
							$(this).find("keys key").each(function(){
								_key = $(this).text();
								_keys.push(_key);
							});
							imageData = {i:_id,src:_src,keys:_keys};
							$this.xifrawdata.push(imageData);
							$this.xifimage.push(imageData);
						});
						$this.xifsearchkeys = $this.buildKeys();
						
						$this.find(".xif-loading").first().fadeOut("slow",function(){
							$this.loadImages();
							$(this).remove();					
						});
					}
				}
			);
			
			return this;
		}
		
		this.loadImageDataFromMarkup = function() {
			var $this = this;
			var o = this.settings;
			
			this.xifrawdata = new Array();
			this.xifimage = new Array();
			this.xifsearchkeys = new Array();
			
			$this.displayLoading();
			
			$(".xif-temp").find("div[class='xif-image']").each(function(index){
				var numProperties = $(this).find("p").length;
				var imageKeys = new Array();
				var imageSrc = "#";
				if(numProperties>0) {
					imageSrc = jQuery.trim($(this).find("p").eq(0).text());
					if(numProperties > 1) {
						var allKeys = "";
						$(this).find("p").eq(0).nextAll().each(function(){
							allKeys = allKeys + jQuery.trim($(this).text()) + " ";
						});
						allKeys = jQuery.trim(allKeys);
						imageKeys = allKeys.split(" ");
					}
					$this.xifrawdata[index] = {i:index,src:imageSrc,keys:imageKeys};
					$this.xifimage[index] = {i:index,src:imageSrc,keys:imageKeys};
				}
				
			});
			$this.xifsearchkeys = $this.buildKeys();
			
			$(".xif-temp").contents().remove();
			$(".xif-temp").remove();
			$this.find(".xif-loading").first().fadeOut("slow",function(){
				$this.loadImages();
				$(this).remove();					
			});
			
			return this;
		}
		
		this.initSectors = function() {
			var $this = this;
			var o = this.settings;
			
			$this.xifimagedata = new Array();
			$this.moving = new Array();
		
			for(var i=0;i<o.numSectors;i++) {
				$this.moving[i] = false;
				$this.xifimagedata[i] = {x:0, y:0, z:0, w:0, h:0};
			}
			$this.setImageData();
			
			return this;
		}
		
		//public method resize
		this.resize = function() {
			var $this = this;
			var o = this.settings;
			$this.css("width",o.width);
			$this.css("height",o.height);
			return this;
		}
		
		//public method displayLoading
		this.displayLoading = function() {
			var $this = this;
			var o = this.settings;
			$this.html("<div class=\"xif-loading\"></div>");
			$this.find(".xif-loading").css("width",o.width);
			$this.find(".xif-loading").css("height",o.height);
			return this;
		}
		
		//public method loadImages
		this.loadImages = function() {
			var $this = this;
			var o = this.settings;
			
			this.initSectors();
			
			o.numSectors = Number(o.numSectors);
			var numImages = $this.xifimage.length;
			
			if(!o.totalFieldID == "") {
				$("#"+o.totalFieldID).text(jQuery.trim(String(numImages)));
			}
			
			this.currentImage = o.startImage;
			if(this.currentImage > numImages) {
				if(numImages == 0) {
					this.currentImage = 0;
				} else {
					this.currentImage = 1;
				}
			}
			
			var startSector = 1;
			
			if (numImages>0) {
				if(numImages < ((o.numSectors+1)/2)) {
					startSector = ((o.numSectors+1)/2);
				} else {
					startSector = 1;
				}
				var imageIndex = -1;
				for(var sector=startSector;sector<=o.numSectors;sector++){
					imageIndex++;
					if(sector == ((o.numSectors+1)/2)) {
						this.currentImage = imageIndex+1;
					}
					//var imageIndex = (o.startImage - Math.floor((o.numSectors+1)/2) + sector)-1;
					
					if((imageIndex+1)>0 && (imageIndex+1)<=numImages){
						var xifpos = "xifpos"+jQuery.trim(String(sector));
						var overlayImageMarkup = "";
						if(!(o.overlayImage == "")) {
							 overlayImageMarkup = "<img src=\""+o.overlayImage+"\" class=\"xif-overlay-image\"/>";
						}
						$this.append("<div id=\"xif"+jQuery.trim(String(imageIndex))+"\" class=\"xif-placeholder "+xifpos+"\"><div class=\"xif-image-overlay\">"+overlayImageMarkup+"</div><img class=\"xif-image\"/></div>");
						$this.find("#xif"+jQuery.trim(String(imageIndex))).css({
							"top" : $this.xifimagedata[sector-1].y,
							"left" : $this.xifimagedata[sector-1].x,
							"z-index" : $this.xifimagedata[sector-1].z,
							"width" : $this.xifimagedata[sector-1].w,
							"height" : $this.xifimagedata[sector-1].h
						});
						//$this.find("#xif"+jQuery.trim(String(imageIndex))).children().css({
						//	"width" : $this.xifimagedata[sector-1].w,
						//	"height" : $this.xifimagedata[sector-1].h
						//});
						//Set overlay color depending on sector
						var centerSector = (Number(o.numSectors)+1)/2;
						var deltaSector = Math.abs(centerSector - sector);
						var overlayFade = deltaSector*.25;
						$this.find("#xif"+jQuery.trim(String(imageIndex))).children().eq(0).fadeTo(o.speed,overlayFade);
						
						$this.find("#xif"+jQuery.trim(String(imageIndex))).children().eq(1).attr("src",$this.xifimage[imageIndex].src);
						$this.find("#xif"+jQuery.trim(String(imageIndex))).children().eq(1).load(function(){
							$(this).fadeIn(o.speed,function(){
								$(this).css("background-image","");
							});
						});
						$this.find("#xif"+jQuery.trim(String(imageIndex))).click(function(e) {
							var index = Number(String($(this).attr("id")).replace("xif",""));
							if(($this.currentImage-1) == index) {
								$this.interactWithImage(index);
							} else {
								$this.moveToIndex(index);
							}
						});
						$this.find("#xif"+jQuery.trim(String(imageIndex))).mouseenter(function(e) {
							var index = Number(String($(this).attr("id")).replace("xif",""));
							if(($this.currentImage-1) == index) {
								if(!(o.overlayID == "") && !($this.isMoving())) {
									$(this).children().eq(0).html($("#"+o.overlayID).html()).stop(true,true).addClass("xif-transparent").fadeTo(o.speed,1);
								}
							}
						});
						$this.find("#xif"+jQuery.trim(String(imageIndex))).mouseleave(function(e) {
							var index = Number(String($(this).attr("id")).replace("xif",""));
							if(($this.currentImage-1) == index) {
								if(!(o.overlayID == "") && !($this.isMoving())) {
									var overlayImageMarkup = "";
									if(!(o.overlayImage == "")) {
										 overlayImageMarkup = "<img src=\""+o.overlayImage+"\" class=\"xif-overlay-image\"/>";
									}
									$(this).children().eq(0).html("").stop(true,true).fadeTo(o.speed,0,function(overlayImageMarkup){$(this).removeClass("xif-transparent").html(overlayImageMarkup);});
								}
							}
						});
					}
				}
			}
			
			return this;
		};
		
		//public method moveLeft
		this.moveLeft = function() {
			
			var $this = this;
			var o = this.settings;
			var numImages = $this.xifimage.length;
			var currentMaxID = -1;
			var maxID = -1;
			var numPlaceholders = $this.children().length;
			if($this.children().length > 0) {
				var id = $this.children().last().attr("id");
				if (id.indexOf("xif")>-1) {
					i = id.substring(3);
					currentMaxID = Number(i);
				}
			} else {
				//alert("XMLImageFlow IS EMPTY...");
				return this;
			}
			
			
			
			//Move the rest
			if(!$this.isMoving()) {
				if((o.numSectors>0 && numImages > 0) && $this.currentImage < numImages) {
					var firstSector = 1;
					var lastSector = o.numSectors;
					for(var sector=1;sector<=o.numSectors;sector++) {
						var xifpos = ".xifpos"+jQuery.trim(String(sector));
						
						//First sector
						if(sector == firstSector) {
							if($this.find(xifpos).length > 0) {
								$this.moving[sector-1] = true;
								var newz = Number($this.find(xifpos).css("z-index"))-1;
								$this.find(xifpos).css("z-index",newz);
								$this.find(xifpos).fadeOut(o.speed,function() {
									var i = "";
									var cs = $(this).attr("class").split(" ");
									for(var c in cs) {
										var inc = cs[c].indexOf("xifpos");
										if (cs[c].indexOf("xifpos")>-1) {
											i = cs[c].substring(inc+6);
											$this.moving[Number(i)-1] = false;
										}
									}
									$(this).remove();
								});
							}
						}
						
						//Last sector
						if (sector == lastSector && lastSector > firstSector) {
							if($this.find(xifpos).length > 0) {
								$this.moving[sector-1] = true;
								var nextSectorIndex = sector - 2;
								var middleSector = (Number(o.numSectors) + 1) / 2;
								var deltaSector = Math.abs(middleSector - sector);
								var overlayFade = deltaSector*.25;
								//Remove Overlay
								if(sector == middleSector) {
									$this.find(xifpos).children().eq(0).stop(true,true).hide()
								}
								var overlayImageMarkup = "";
								if(!(o.overlayImage == "")) {
									 overlayImageMarkup = "<img src=\""+o.overlayImage+"\" class=\"xif-overlay-image\"/>";
								}
								$this.find(xifpos).children().eq(0).html(overlayImageMarkup).stop(true,true).removeClass("xif-transparent");
								//Set overlay color depending on sector
								$this.find(xifpos).children().eq(0).stop(true,true).fadeTo(o.speed,overlayFade);
								//z-index
								$this.find(xifpos).css("z-index",$this.xifimagedata[nextSectorIndex].z);
								
								//Overlay Fade to next sector opacity
								//Set overlay color depending on sector
								var nextSector = nextSectorIndex+1;
								deltaSector = Math.abs(middleSector - nextSector);
								overlayFade = deltaSector*.25;
								$this.find(xifpos).children().eq(0).fadeTo(o.speed,overlayFade);
								
								//Glide
								$this.find(xifpos).animate({
									"top": $this.xifimagedata[nextSectorIndex].y,
									"left": $this.xifimagedata[nextSectorIndex].x,
									"width": $this.xifimagedata[nextSectorIndex].w,
									"height": $this.xifimagedata[nextSectorIndex].h
								},
								o.speed,
								"jswing",
								function() {
									var i = "";
									var cs = $(this).attr("class").split(" ");
									for(var c in cs) {
										var inc = cs[c].indexOf("xifpos");
										if (cs[c].indexOf("xifpos")>-1) {
											i = cs[c].substring(inc+6);
											$this.moving[Number(i)-1] = false;
										}
									}
									if(!i == ""){
										var middleSector = (Number(o.numSectors) + 1) / 2;
										var nextSectorIndex = Number(i) - 1;
										if(nextSectorIndex == middleSector) {
											$this.currentImage = Number(String($(this).attr("id")).replace("xif",""))+1;
										}
										$(this).removeClass("xifpos"+jQuery.trim(String(i)));
										$(this).addClass("xifpos"+jQuery.trim(String((nextSectorIndex))));
									}
								});
							}
						}

						//All other sectors
						if(sector > firstSector && sector < lastSector) {
							if($this.find(xifpos).length > 0) {
								$this.moving[sector-1] = true;
								var nextSectorIndex = sector - 2;
								var middleSector = (Number(o.numSectors) + 1) / 2;
								var deltaSector = Math.abs(middleSector - sector);
								var overlayFade = deltaSector*.25;
								//Remove Overlay
								if(sector == middleSector) {
									$this.find(xifpos).children().eq(0).stop(true,true).hide()
								}
								var overlayImageMarkup = "";
								if(!(o.overlayImage == "")) {
									 overlayImageMarkup = "<img src=\""+o.overlayImage+"\" class=\"xif-overlay-image\"/>";
								}
								$this.find(xifpos).children().eq(0).html(overlayImageMarkup).stop(true,true).removeClass("xif-transparent");
								//Set overlay color depending on sector								
								$this.find(xifpos).children().eq(0).stop(true,true).fadeTo(o.speed,overlayFade);
								//z-index
								$this.find(xifpos).css("z-index",$this.xifimagedata[nextSectorIndex].z);
								
								//Overlay Fade to next sector opacity
								//Set overlay color depending on sector
								var nextSector = nextSectorIndex+1;
								deltaSector = Math.abs(middleSector - nextSector);
								overlayFade = deltaSector*.25;
								$this.find(xifpos).children().eq(0).fadeTo(o.speed,overlayFade);
								
								//Glide
								$this.find(xifpos).animate({
									"top": Number($this.xifimagedata[nextSectorIndex].y),
									"left": Number($this.xifimagedata[nextSectorIndex].x),
									"width": Number($this.xifimagedata[nextSectorIndex].w),
									"height": Number($this.xifimagedata[nextSectorIndex].h)
								},
								o.speed,
								"jswing",
								function() {
									var i = "";
									var cs = $(this).attr("class").split(" ");
									for(var c in cs) {
										var inc = cs[c].indexOf("xifpos");
										if (cs[c].indexOf("xifpos")>-1) {
											i = cs[c].substring(inc+6);
											$this.moving[Number(i)-1] = false;
										}
									}
									if(!i == ""){
										var middleSector = (Number(o.numSectors) + 1) / 2;
										var nextSectorIndex = Number(i) - 1;
										if(nextSectorIndex == middleSector) {
											$this.currentImage = Number(String($(this).attr("id")).replace("xif",""))+1;
										}
										$(this).removeClass("xifpos"+jQuery.trim(String(i)));
										$(this).addClass("xifpos"+jQuery.trim(String((nextSectorIndex))));
									}
								});
							}
						}
					}
					

					//Add new placeholder for the next image and give it last position id
					if(numImages > 0 && currentMaxID < (numImages-1) && $this.currentImage < numImages && !$this.moving[o.numSectors]) {
						
						var imageIndex = currentMaxID+1;
						var sector = o.numSectors;
						var xifpos = "xifpos"+jQuery.trim(String(sector));
						var overlayImageMarkup = "";
						if(!(o.overlayImage == "")) {
							 overlayImageMarkup = "<img src=\""+o.overlayImage+"\" class=\"xif-overlay-image\"/>";
						}
						$this.append("<div id=\"xif"+jQuery.trim(String(imageIndex))+"\" class=\"xif-placeholder "+xifpos+"\"><div class=\"xif-image-overlay\">"+overlayImageMarkup+"</div><img class=\"xif-image\"/></div>");
						$this.find("#xif"+jQuery.trim(String(imageIndex))).css({
							"top" : $this.xifimagedata[sector-1].y,
							"left" : $this.xifimagedata[sector-1].x,
							"z-index" : $this.xifimagedata[sector-1].z,
							"width" : $this.xifimagedata[sector-1].w,
							"height" : $this.xifimagedata[sector-1].h
						});
						//$this.find("#xif"+jQuery.trim(String(imageIndex))).children().css({
						//	"width" : $this.xifimagedata[sector-1].w,
						//	"height" : $this.xifimagedata[sector-1].h
						//});
						//Set overlay color depending on sector
						var centerSector = (Number(o.numSectors)+1)/2;
						var deltaSector = Math.abs(centerSector - sector);
						var overlayFade = deltaSector*.25;
						$this.find("#xif"+jQuery.trim(String(imageIndex))).children().eq(0).fadeTo(o.speed,overlayFade);
						
						$this.find("#xif"+jQuery.trim(String(imageIndex))).children().eq(1).attr("src",$this.xifimage[imageIndex].src);
						$this.find("#xif"+jQuery.trim(String(imageIndex))).children().eq(1).load(function(){
							$(this).fadeIn(o.speed,function(){
								$(this).css("background-image","");
							});
						});
						
						$this.moving[sector] = true;
						$this.find("#xif"+jQuery.trim(String(imageIndex))).fadeIn(o.speed,function() {
							$this.moving[o.numSectors] = false;
							$(this).click(function(e) {
								var index = Number(String($(this).attr("id")).replace("xif",""));
								if(($this.currentImage-1) == index) {
									$this.interactWithImage(index);
								} else {
									$this.moveToIndex(index);
								}
							});
							$(this).mouseenter(function(e) {
								var index = Number(String($(this).attr("id")).replace("xif",""));
								if(($this.currentImage-1) == index) {
									if(!(o.overlayID == "") && !($this.isMoving())) {
										$(this).children().eq(0).html($("#"+o.overlayID).html()).stop(true,true).addClass("xif-transparent").fadeTo(o.speed,1);
									}
								}
							});
							$(this).mouseleave(function(e) {
								var index = Number(String($(this).attr("id")).replace("xif",""));
								if(($this.currentImage-1) == index) {
									if(!(o.overlayID == "") && !($this.isMoving())) {
										if(!(o.overlayImage == "")) {
											 overlayImageMarkup = "<img src=\""+o.overlayImage+"\" class=\"xif-overlay-image\"/>";
										}
										$(this).children().eq(0).html("").stop(true,true).fadeTo(o.speed,0,function(overlayImageMarkup){$(this).removeClass("xif-transparent").html(overlayImageMarkup);});
									}
								}
							});
						});
					}
					
				}
			} else {
				//alert("STILL MOVING...");
			}
			

			
			return this;
		}
		
		//public method moveRight
		this.moveRight = function() {
			var $this = this;
			var o = this.settings;
			var numImages = $this.xifimage.length;
			var currentMinID = -1;
			var currentMaxID = -1;
			var maxID = -1;
			var numPlaceholders = $this.children().length;
			if($this.children().length > 0) {
				var id = $this.children().last().attr("id");
				if (id.indexOf("xif")>-1) {
					var i = id.substring(3);
					currentMaxID = Number(i);
				}
				var id = $this.children().first().attr("id");
				
				if (id.indexOf("xif")>-1) {
					var i = id.substring(3);
					currentMinID = Number(i);
				}
			} else {
				//alert("XMLImageFlow IS EMPTY...");
				return this;
			}
			
			//Move the rest
			if(!$this.isMoving()) {
				if(o.numSectors>0 && numImages > 0 && $this.currentImage > 1) {
					var firstSector = 1;
					var middleSector = (Number(o.numSectors) - 1) / 2;
					var lastSector = o.numSectors;
					for(var sector=1;sector<=o.numSectors;sector++) {
						var xifpos = ".xifpos"+jQuery.trim(String(sector));
						//First sector
						if (sector == lastSector && lastSector > firstSector) {
							if($this.find(xifpos).length > 0) {
								$this.moving[sector-1] = true;
								var newz = Number($this.find(xifpos).css("z-index"))-1;
								$this.find(xifpos).css("z-index",newz);
								$this.find(xifpos).fadeOut(o.speed,function() {
									var i = "";
									var cs = $(this).attr("class").split(" ");
									for(var c in cs) {
										var inc = cs[c].indexOf("xifpos");
										if (cs[c].indexOf("xifpos")>-1) {
											i = cs[c].substring(inc+6);
											$this.moving[Number(i)-1] = false;
										}
									}
									$(this).remove();
								});
							}
						}
						//Last sector
						if(sector == firstSector) {
							if($this.find(xifpos).length > 0) {
								$this.moving[sector-1] = true;
								var nextSectorIndex = sector;
								var middleSector = (Number(o.numSectors) + 1) / 2;
								var deltaSector = Math.abs(middleSector - sector);
								var overlayFade = deltaSector*.25;
								//Remove Overlay
								if(sector == middleSector) {
									$this.find(xifpos).children().eq(0).stop(true,true).hide()
								}
								var overlayImageMarkup = "";
								if(!(o.overlayImage == "")) {
									 overlayImageMarkup = "<img src=\""+o.overlayImage+"\" class=\"xif-overlay-image\"/>";
								}
								$this.find(xifpos).children().eq(0).html(overlayImageMarkup).stop(true,true).removeClass("xif-transparent");
								//Set overlay color depending on sector
								
								$this.find(xifpos).children().eq(0).stop(true,true).fadeTo(o.speed,overlayFade);
								//z-index
								$this.find(xifpos).css("z-index",$this.xifimagedata[nextSectorIndex].z);
								
								//Overlay Fade to next sector opacity
								//Set overlay color depending on sector
								var nextSector = nextSectorIndex+1;
								deltaSector = Math.abs(middleSector - nextSector);
								overlayFade = deltaSector*.25;
								$this.find(xifpos).children().eq(0).fadeTo(o.speed,overlayFade);								
								
								//Glide
								$this.find(xifpos).animate({
									"top": $this.xifimagedata[nextSectorIndex].y,
									"left": $this.xifimagedata[nextSectorIndex].x,
									"width": $this.xifimagedata[nextSectorIndex].w,
									"height": $this.xifimagedata[nextSectorIndex].h
								},
								o.speed,
								"jswing",
								function() {
									var i = "";
									var cs = $(this).attr("class").split(" ");
									for(var c in cs) {
										var inc = cs[c].indexOf("xifpos");
										if (cs[c].indexOf("xifpos")>-1) {
											i = cs[c].substring(inc+6);
											$this.moving[Number(i)-1] = false;
										}
									}
									if(!i == ""){
										var middleSector = (Number(o.numSectors) + 1) / 2;
										var nextSectorIndex = Number(i) + 1;
										if(nextSectorIndex == middleSector) {
											$this.currentImage = Number(String($(this).attr("id")).replace("xif",""))+1;
										}
										//alert($(this).attr("id")+" : remove class : "+"xifpos"+jQuery.trim(String(i)));
										$(this).removeClass("xifpos"+jQuery.trim(String(i)));
										//alert($(this).attr("id")+" : add class : "+"xifpos"+jQuery.trim(String((nextSectorIndex))));
										$(this).addClass("xifpos"+jQuery.trim(String((nextSectorIndex))));
									}
								});
							}
						}
						//All other sectors
						if(sector > firstSector && sector < lastSector) {
							if($this.find(xifpos).length > 0) {
								$this.moving[sector-1] = true;
								var nextSectorIndex = sector;
								var middleSector = (Number(o.numSectors) + 1) / 2;
								var deltaSector = Math.abs(middleSector - sector);
								var overlayFade = deltaSector*.25;
								//Remove Overlay
								if(sector == middleSector) {
									$this.find(xifpos).children().eq(0).stop(true,true).hide()
								}
								var overlayImageMarkup = "";
								if(!(o.overlayImage == "")) {
									 overlayImageMarkup = "<img src=\""+o.overlayImage+"\" class=\"xif-overlay-image\"/>";
								}
								$this.find(xifpos).children().eq(0).html(overlayImageMarkup).stop(true,true).removeClass("xif-transparent");
								//Set overlay color depending on sector
								
								$this.find(xifpos).children().eq(0).stop(true,true).fadeTo(o.speed,overlayFade);
								//z-index
								$this.find(xifpos).css("z-index",$this.xifimagedata[nextSectorIndex].z);
								
								//Overlay Fade to next sector opacity
								//Set overlay color depending on sector
								var nextSector = nextSectorIndex+1;
								deltaSector = Math.abs(middleSector - nextSector);
								overlayFade = deltaSector*.25;
								$this.find(xifpos).children().eq(0).fadeTo(o.speed,overlayFade);								
								
								//Glide
								$this.find(xifpos).animate({
									"top": $this.xifimagedata[nextSectorIndex].y,
									"left": $this.xifimagedata[nextSectorIndex].x,
									"width": $this.xifimagedata[nextSectorIndex].w,
									"height": $this.xifimagedata[nextSectorIndex].h
								},
								o.speed,
								"jswing",
								function() {
									var i = "";
									var cs = $(this).attr("class").split(" ");
									for(var c in cs) {
										var inc = cs[c].indexOf("xifpos");
										if (cs[c].indexOf("xifpos")>-1) {
											i = cs[c].substring(inc+6);
											$this.moving[Number(i)-1] = false;
										}
									}
									if(!i == ""){
										var middleSector = (Number(o.numSectors) + 1) / 2;
										var nextSectorIndex = Number(i) + 1;
										if(nextSectorIndex == middleSector) {
											$this.currentImage = Number(String($(this).attr("id")).replace("xif",""))+1;
										}
										//alert($(this).attr("id")+" : remove class : "+"xifpos"+jQuery.trim(String(i)));
										$(this).removeClass("xifpos"+jQuery.trim(String(i)));
										//alert($(this).attr("id")+" : add class : "+"xifpos"+jQuery.trim(String((nextSectorIndex))));
										$(this).addClass("xifpos"+jQuery.trim(String((nextSectorIndex))));
									}
								});
							}
						}
						
					}
					
					//Add new placeholder for the next image and give it first position id
					if(numImages > 0 && currentMinID > 0 && $this.currentImage > 1 && !$this.moving[o.numSectors]) {
						
						var imageIndex = Number(currentMinID)-1;
						var sector = 1;
						var xifpos = "xifpos"+jQuery.trim(String(sector));
						//alert("adding xif"+jQuery.trim(String(imageIndex))+ " : " + xifpos);
						var overlayImageMarkup = "";
						if(!(o.overlayImage == "")) {
							 overlayImageMarkup = "<img src=\""+o.overlayImage+"\" class=\"xif-overlay-image\"/>";
						}
						$this.prepend("<div id=\"xif"+jQuery.trim(String(imageIndex))+"\" class=\"xif-placeholder "+xifpos+"\"><div class=\"xif-image-overlay\">"+overlayImageMarkup+"</div><img class=\"xif-image\"/></div>");
						$this.find("#xif"+jQuery.trim(String(imageIndex))).css({
							"top" : $this.xifimagedata[sector-1].y,
							"left" : $this.xifimagedata[sector-1].x,
							"z-index" : $this.xifimagedata[sector-1].z,
							"width" : $this.xifimagedata[sector-1].w,
							"height" : $this.xifimagedata[sector-1].h
						});
						//$this.find("#xif"+jQuery.trim(String(imageIndex))).children().css({
						//	"width" : $this.xifimagedata[sector-1].w,
						//	"height" : $this.xifimagedata[sector-1].h
						//});
						//Set overlay color depending on sector
						var centerSector = (Number(o.numSectors)+1)/2;
						var deltaSector = Math.abs(centerSector - sector);
						var overlayFade = deltaSector*.25;
						$this.find("#xif"+jQuery.trim(String(imageIndex))).children().eq(0).fadeTo(o.speed,overlayFade);
						
						$this.find("#xif"+jQuery.trim(String(imageIndex))).children().eq(1).attr("src",$this.xifimage[imageIndex].src);
						$this.find("#xif"+jQuery.trim(String(imageIndex))).children().eq(1).load(function(){
							$(this).fadeIn(o.speed,function(){
								$(this).css("background-image","");
							});
						});
						
						$this.moving[o.numSectors] = true;
						$this.find("#xif"+jQuery.trim(String(imageIndex))).fadeIn(o.speed,function() {
							var i = "";
							var cs = $(this).attr("class").split(" ");
							for(var c in cs) {
								var inc = cs[c].indexOf("xifpos");
								if (cs[c].indexOf("xifpos")>-1) {
									i = cs[c].substring(inc+6);
									$this.moving[o.numSectors] = false;
								}
							}
							$(this).click(function(e) {
								var index = Number(String($(this).attr("id")).replace("xif",""));
								if(($this.currentImage-1) == index) {
									$this.interactWithImage(index);
								} else {
									$this.moveToIndex(index);
								}
							});
							$(this).mouseenter(function(e) {
								var index = Number(String($(this).attr("id")).replace("xif",""));
								if(($this.currentImage-1) == index) {
									if(!(o.overlayID == "") && !($this.isMoving())) {
										$(this).children().eq(0).html($("#"+o.overlayID).html()).stop(true,true).addClass("xif-transparent").fadeTo(o.speed,1);
									}
								}
							});
							$(this).mouseleave(function(e) {
								var index = Number(String($(this).attr("id")).replace("xif",""));
								if(($this.currentImage-1) == index) {
									if(!(o.overlayID == "") && !($this.isMoving())) {
										var overlayImageMarkup = "";
										if(!(o.overlayImage == "")) {
											 overlayImageMarkup = "<img src=\""+o.overlayImage+"\" class=\"xif-overlay-image\"/>";
										}
										$(this).children().eq(0).html("").stop(true,true).fadeTo(o.speed,0,function(overlayImageMarkup){$(this).removeClass("xif-transparent").html(overlayImageMarkup);});
									}
								}
							});
						});
					}
				}
			} else {
				//alert("STILL MOVING...");
			}
			

			
			return this;
		}
		
		//public method setImageData
		this.setImageData = function() {
			var $this = this;
			var o = this.settings;
			
			for(var i=0;i<(o.numSectors);i++){
				var sectionWidth = Math.floor($this.outerWidth()/(o.numSectors+1));
				var height = o.imageHeight;
				var width = o.imageWidth;
				
				var middleSector = (o.numSectors+1)/2;
				var distance = middleSector-(i+1);
				var deltaSector = Math.abs(distance);
				var perSector = 1/middleSector;
				var separator = deltaSector * perSector;
				
				width = width - (width * separator);
				height = height - (height * separator);
				
				var top = ($this.outerHeight()/2) - (height/2);
				var left = (sectionWidth*(i+1)) - (width/2);
				
				//limit 1/x^2
				var xInterval = .50;
				var depth = deltaSector * xInterval;
				var fX = 1.0/depth;
				if(deltaSector==0) {
					fX = 4.0;
				}
				var distanceFromMiddleX = 4.0-fX;
				var perDistanceFromMiddle = distanceFromMiddleX * .25;
				var middleSectorX = ($this.outerWidth()/2);
				var distanceFromMiddleSector = middleSectorX * perDistanceFromMiddle;
				if(distance>0) {
					left = middleSectorX - distanceFromMiddleSector - (width/2);
				}
				if(distance<0) {
					left = middleSectorX + distanceFromMiddleSector - (width/2);
				}
			
				//circle
				//var circleOriginX = $this.outerWidth()/2;
				//var radius = circleOriginX;
				//var deg = 90 / ((o.numSectors+1) / 2);
				//var secDeg = (i+1) * deg;
				//var secRadians = secDeg * 0.0174532925; //Degree to Radians
				//var x = radius * Math.cos(secRadians);
				//left = circleOriginX - x - ((width)/2);
				
	
				//percent
				//var distance = middleSector-(i+1);
				//var deltaSector = Math.abs(distance);
				//var perSector = 1/middleSector;
				//var separator = deltaSector * perSector;
				//if (distance<0) {
				//	left = left - (sectionWidth * separator);
				//}
				//if (distance>0) {
				//	left = left + (sectionWidth * separator);
				//}
				
				//static 7
				//switch((i+1))
				//{
				//	case 2:		left = left - (sectionWidth * .55357142857142854);
				//				break;
				//	case 3:		left = left - (sectionWidth * .8303571428571429);
				//				break;
				//	case 5:		left = left + (sectionWidth * .8303571428571429);
				//				break;
				//	case 6:		left = left + (sectionWidth * .55357142857142854);
				//				break;
				//}
				var zindex = 10000 - deltaSector;
				$this.xifimagedata[i] = {x:left, y:top, z:zindex, w:width, h:height};
			}
			
			return this;
		}
		
		//public method moveToIndex
		//this.moveToIndex = function(index) {
//			var $this = this;
//			var o = this.settings;
//			var currentImages = new Array();
//			
//			if(!$this.isMoving()) {
//				$this.find(".xif-placeholder").each(function(i) {
//					currentImages[i] = Number(String($(this).attr("id")).replace("xif",""));
//				});
//				
//				var exists = false;
//				for(var i in currentImages) {
//					if(currentImages[i] == index) {
//						exists = true;
//					}
//				}
//				
//				if(exists) {
//					if(!$this.isMoving()) {
//						if((Number($this.currentImage)-1) > Number(index)) {
//							$this.moveRight();
//						}
//						if((Number($this.currentImage)-1) < Number(index)) {
//							$this.moveLeft();
//						}
//					}
//				}
//				if(!((Number($this.currentImage)-1) == Number(index))) {
//					if($this.automove == "-1") {
//						$this.targetImageID = index;
//						$this.automove = setInterval(function(){
//							$this.moveToIndex($this.targetImageID);
//						},
//						5);
//					}
//				} else {
//					clearInterval($this.automove);
//					$this.automove = -1;
//					$this.targetImageID = -1;
//				}
//			}
//			return this;
//		}
		
		//public method isMoving
		this.isMoving = function() {
			var $this = this;
			var o = this.settings;
			
			var result = false;
			
			for(var i in $this.moving) {
				if($this.moving[i]) {
					result = true;
				}
			}
			
			return result;
		}
		
		//public method interactWithImage
		this.interactWithImage = function(index) {
			var $this = this;
			var o = this.settings;
			
			var target = $this.xifimage[index].src.toLowerCase().replace(o.webImageExt.toLowerCase(),o.posterExt.toLowerCase()).replace(o.webImageFolder.toLowerCase(),o.posterFolder.toLowerCase());
			//window.open(target,"LMMP_Poster");
			
			return this;
		}
		
		this.searchImage = function(term) {
			var $this = this;
			var o = this.settings;
			
			//if(!($this.searching)) {
				$this.searching = true;
				$this.xifimage = new Array();
				$this.contents().remove();
				$this.displayLoading();
				$this.resize();
			
				if(!(term == undefined) && typeof(term) == "string") {
					if(term.length > 0) {
						var imageIndex = -1;
						var newArray = new Array();
						var terms = jQuery.trim(term).split(" ");
						for(var ia in $this.xifrawdata) {
							var imageSrc = $this.xifrawdata[ia].src;
							var imageKeys = $this.xifrawdata[ia].keys;
							var found = false;
							for(var ib in imageKeys) {
								for(var ic in terms) {
									if(imageKeys[ib].toUpperCase().indexOf(terms[ic].toUpperCase()) > -1) {
										found = true;
									}
								}
							}
							if(found) {
								imageIndex++;
								newArray[imageIndex] = {i:imageIndex,src:imageSrc,keys:imageKeys};
							}
						}
						
						if(imageIndex>-1) {
							$this.xifimage = newArray;
						}
					} else {
						$this.xifimage = $this.xifrawdata;
					}
				}
				
				if($this.xifimage.length < (Number(o.startImage))) {
					if($this.xifimage.length == 0) {
						$this.currentImage = 0;	
					} else {
						$this.currentImage = 1;
					}
				} else {
					$this.currentImage = o.startImage;
				}
				
				$this.find(".xif-loading").first().fadeOut("slow",function(){
					$this.contents().remove();
					$this.loadImages();
					$this.searching = false;
					$(this).remove();					
				});
			//}
			
			return this;
		};
		
		this.buildKeys = function() {
			var $this = this;
			var o = this.settings;
			var result = new Array();
			var keys = -1;
			
			for(var ia in $this.xifrawdata) {
				var imageKeys = $this.xifrawdata[ia].keys;
				for(var ib in imageKeys) {
					var found = false;
					for(var ic in result) {
						if(imageKeys[ib].toUpperCase() == String(result[ic]).toUpperCase()) {
							found = true;
						}
					}
					if(!found){
						keys++;
						result[keys] = imageKeys[ib].toUpperCase();
					}
				}
			}
			
			return result.sort();
		}
		
		this.getKeys = function(term) {
			var $this = this;
			var o = this.settings;
			
			var keys = -1;
			var result = new Array();
			
			if(!(term == undefined) && typeof(term) == "string") {
				if(term.length > 0) {
					var imageIndex = -1;
					var newArray = new Array();
					var terms = jQuery.trim(term).split(" ");
					for(var ia in $this.xifrawdata) {
						var imageSrc = $this.xifrawdata[ia].src;
						var imageKeys = $this.xifrawdata[ia].keys;
						for(var ib in imageKeys) {
							for(var ic in terms) {
								if(imageKeys[ib].toUpperCase().indexOf(terms[ic].toUpperCase()) > -1) {
									var found = false;
									for(var id in result) {
										if (result[id].toUpperCase() == imageKeys[ib].toUpperCase()) {
											found = true;
										}
									}
									if(!found) {
										keys++;
										result[keys] = imageKeys[ib].toUpperCase();
									}
								}
							}
						}
					}
				}
			}
			
			return result.sort();
		};
		
		//public method windowResizeEvent
		this.windowResizeEvent = function() {
			var o = $obj.settings;
			
			$obj.contents().remove();
			$obj.displayLoading();
			$obj.resize();
			$obj.find(".xif-loading").first().fadeOut("slow",function(){
				$obj.contents().remove();
				$obj.loadImages();
				$(this).remove();					
			});
					
			return this;
		}
		
		//Events
		$(document).ready(function(){
			$(window).bind("resize", function() {
				$obj.windowResizeEvent();
			});
		
			
			
		});
		
		//Initialize plugin for current element
		return this.init();
	}
	
	/*!
 * jCarousel - Riding carousels with jQuery
 *   http://sorgalla.com/jcarousel/
 *
 * Copyright (c) 2006 Jan Sorgalla (http://sorgalla.com)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * Built on top of the jQuery library
 *   http://jquery.com
 *
 * Inspired by the "Carousel Component" by Bill Scott
 *   http://billwscott.com/carousel/
 */
 
 
 /**
     * Creates a carousel for all matched elements.
     *
     * @example $("#mycarousel").jcarousel();
     * @before <ul id="mycarousel" class="jcarousel-skin-name"><li>First item</li><li>Second item</li></ul>
     * @result
     *
     * <div class="jcarousel-skin-name">
     *   <div class="jcarousel-container">
     *     <div class="jcarousel-clip">
     *       <ul class="jcarousel-list">
     *         <li class="jcarousel-item-1">First item</li>
     *         <li class="jcarousel-item-2">Second item</li>
     *       </ul>
     *     </div>
     *     <div disabled="disabled" class="jcarousel-prev jcarousel-prev-disabled"></div>
     *     <div class="jcarousel-next"></div>
     *   </div>
     * </div>
     *
     * @method jcarousel
     * @return jQuery
     * @param o {Hash|String} A set of key/value pairs to set as configuration properties or a method name to call on a formerly created instance.
     */

	

 $.fn.jcarousel = function(o) {
        if (typeof o == 'string') {
            var instance = $(this).data('jcarousel'), args = Array.prototype.slice.call(arguments, 1);
            return instance[o].apply(instance, args);
        } else
            return this.each(function() {
                $(this).data('jcarousel', new $jc(this, o));
            });
    };

    // Default configuration properties.
    var defaults = {
        vertical: false,
        start: 2,
        offset: 1,
        size: null,
        scroll: 1,
        visible: null,
        animation: 'normal',
        easing: 'swing',
        auto: 0,
        wrap: null,
        initCallback: null,
        reloadCallback: null,
        itemLoadCallback: null,
        itemFirstInCallback: null,
        itemFirstOutCallback: null,
        itemLastInCallback: null,
        itemLastOutCallback: null,
        itemVisibleInCallback: null,
        itemVisibleOutCallback: null,
        buttonNextHTML: '<div></div>',
        buttonPrevHTML: '<div></div>',
        buttonNextEvent: 'click',
        buttonPrevEvent: 'click',
        buttonNextCallback: null,
        buttonPrevCallback: null
    };

    /**
     * The jCarousel object.
     *
     * @constructor
     * @class jcarousel
     * @param e {HTMLElement} The element to create the carousel for.
     * @param o {Object} A set of key/value pairs to set as configuration properties.
     * @cat Plugins/jCarousel
     */
    $.jcarousel = function(e, o) {
        this.options    = $.extend({}, defaults, o || {});

        this.locked     = false;

        this.container  = null;
        this.clip       = null;
        this.list       = null;
        this.buttonNext = null;
        this.buttonPrev = null;

        this.wh = !this.options.vertical ? 'width' : 'height';
        this.lt = !this.options.vertical ? 'left' : 'top';

        // Extract skin class
        var skin = '', split = e.className.split(' ');

        for (var i = 0; i < split.length; i++) {
            if (split[i].indexOf('jcarousel-skin') != -1) {
                $(e).removeClass(split[i]);
                skin = split[i];
                break;
            }
        }

        if (e.nodeName == 'UL' || e.nodeName == 'OL') {
            this.list = $(e);
            this.container = this.list.parent();

            if (this.container.hasClass('jcarousel-clip')) {
                if (!this.container.parent().hasClass('jcarousel-container'))
                    this.container = this.container.wrap('<div></div>');

                this.container = this.container.parent();
            } else if (!this.container.hasClass('jcarousel-container'))
                this.container = this.list.wrap('<div></div>').parent();
        } else {
            this.container = $(e);
            this.list = this.container.find('ul,ol').eq(0);
        }

        if (skin != '' && this.container.parent()[0].className.indexOf('jcarousel-skin') == -1)
            this.container.wrap('<div class=" '+ skin + '"></div>');

        this.clip = this.list.parent();

        if (!this.clip.length || !this.clip.hasClass('jcarousel-clip'))
            this.clip = this.list.wrap('<div></div>').parent();

        this.buttonNext = $('.jcarousel-next', this.container);

        if (this.buttonNext.size() == 0 && this.options.buttonNextHTML != null)
            this.buttonNext = this.clip.after(this.options.buttonNextHTML).next();

        this.buttonNext.addClass(this.className('jcarousel-next'));

        this.buttonPrev = $('.jcarousel-prev', this.container);

        if (this.buttonPrev.size() == 0 && this.options.buttonPrevHTML != null)
            this.buttonPrev = this.clip.after(this.options.buttonPrevHTML).next();

        this.buttonPrev.addClass(this.className('jcarousel-prev'));

        this.clip.addClass(this.className('jcarousel-clip')).css({
            overflow: 'hidden',
            position: 'relative'
        });
        this.list.addClass(this.className('jcarousel-list')).css({
            overflow: 'hidden',
            position: 'relative',
            top: 0,
            left: 0,
            margin: 0,
            padding: 0
        });
        this.container.addClass(this.className('jcarousel-container')).css({
            position: 'relative'
        });

        var di = this.options.visible != null ? Math.ceil(this.clipping() / this.options.visible) : null;
        var li = this.list.children('li');

        var self = this;

        if (li.size() > 0) {
            var wh = 0, i = this.options.offset;
            li.each(function() {
                self.format(this, i++);
                wh += self.dimension(this, di);
            });

            this.list.css(this.wh, wh + 'px');

            // Only set if not explicitly passed as option
            if (!o || o.size === undefined)
                this.options.size = li.size();
        }

        // For whatever reason, .show() does not work in Safari...
        this.container.css('display', 'block');
        this.buttonNext.css('display', 'block');
        this.buttonPrev.css('display', 'block');

        this.funcNext   = function() { self.next(); };
        this.funcPrev   = function() { self.prev(); };
        this.funcResize = function() { self.reload(); };

        if (this.options.initCallback != null)
            this.options.initCallback(this, 'init');

        if ($.browser.safari) {
            this.buttons(false, false);
            $(window).bind('load.jcarousel', function() { self.setup(); });
        } else
            this.setup();
    };

    // Create shortcut for internal use
    var $jc = $.jcarousel;

    $jc.fn = $jc.prototype = {
        jcarousel: '0.2.4'
    };

    $jc.fn.extend = $jc.extend = $.extend;

    $jc.fn.extend({
        /**
         * Setups the carousel.
         *
         * @method setup
         * @return undefined
         */
        setup: function() {
            this.first     = null;
            this.last      = null;
            this.prevFirst = null;
            this.prevLast  = null;
            this.animating = false;
            this.timer     = null;
            this.tail      = null;
            this.inTail    = false;

            if (this.locked)
                return;

            this.list.css(this.lt, this.pos(this.options.offset) + 'px');
            var p = this.pos(this.options.start);
            this.prevFirst = this.prevLast = null;
            this.animate(p, false);

            $(window).unbind('resize.jcarousel', this.funcResize).bind('resize.jcarousel', this.funcResize);
        },

        /**
         * Clears the list and resets the carousel.
         *
         * @method reset
         * @return undefined
         */
        reset: function() {
            this.list.empty();

            this.list.css(this.lt, '0px');
            this.list.css(this.wh, '10px');

            if (this.options.initCallback != null)
                this.options.initCallback(this, 'reset');

            this.setup();
        },

        /**
         * Reloads the carousel and adjusts positions.
         *
         * @method reload
         * @return undefined
         */
        reload: function() {
            if (this.tail != null && this.inTail)
                this.list.css(this.lt, $jc.intval(this.list.css(this.lt)) + this.tail);

            this.tail   = null;
            this.inTail = false;

            if (this.options.reloadCallback != null)
                this.options.reloadCallback(this);

            if (this.options.visible != null) {
                var self = this;
                var di = Math.ceil(this.clipping() / this.options.visible), wh = 0, lt = 0;
                $('li', this.list).each(function(i) {
                    wh += self.dimension(this, di);
                    if (i + 1 < self.first)
                        lt = wh;
                });

                this.list.css(this.wh, wh + 'px');
                this.list.css(this.lt, -lt + 'px');
            }

            this.scroll(this.first, false);
        },

        /**
         * Locks the carousel.
         *
         * @method lock
         * @return undefined
         */
        lock: function() {
            this.locked = true;
            this.buttons();
        },

        /**
         * Unlocks the carousel.
         *
         * @method unlock
         * @return undefined
         */
        unlock: function() {
            this.locked = false;
            this.buttons();
        },

        /**
         * Sets the size of the carousel.
         *
         * @method size
         * @return undefined
         * @param s {Number} The size of the carousel.
         */
        size: function(s) {
            if (s != undefined) {
                this.options.size = s;
                if (!this.locked)
                    this.buttons();
            }

            return this.options.size;
        },

        /**
         * Checks whether a list element exists for the given index (or index range).
         *
         * @method get
         * @return bool
         * @param i {Number} The index of the (first) element.
         * @param i2 {Number} The index of the last element.
         */
        has: function(i, i2) {
            if (i2 == undefined || !i2)
                i2 = i;

            if (this.options.size !== null && i2 > this.options.size)
                i2 = this.options.size;

            for (var j = i; j <= i2; j++) {
                var e = this.get(j);
                if (!e.length || e.hasClass('jcarousel-item-placeholder'))
                    return false;
            }

            return true;
        },

        /**
         * Returns a jQuery object with list element for the given index.
         *
         * @method get
         * @return jQuery
         * @param i {Number} The index of the element.
         */
        get: function(i) {
            return $('.jcarousel-item-' + i, this.list);
        },

        /**
         * Adds an element for the given index to the list.
         * If the element already exists, it updates the inner html.
         * Returns the created element as jQuery object.
         *
         * @method add
         * @return jQuery
         * @param i {Number} The index of the element.
         * @param s {String} The innerHTML of the element.
         */
        add: function(i, s) {
            var e = this.get(i), old = 0, add = 0;

            if (e.length == 0) {
                var c, e = this.create(i), j = $jc.intval(i);
                while (c = this.get(--j)) {
                    if (j <= 0 || c.length) {
                        j <= 0 ? this.list.prepend(e) : c.after(e);
                        break;
                    }
                }
            } else
                old = this.dimension(e);

            e.removeClass(this.className('jcarousel-item-placeholder'));
            typeof s == 'string' ? e.html(s) : e.empty().append(s);

            var di = this.options.visible != null ? Math.ceil(this.clipping() / this.options.visible) : null;
            var wh = this.dimension(e, di) - old;

            if (i > 0 && i < this.first)
                this.list.css(this.lt, $jc.intval(this.list.css(this.lt)) - wh + 'px');

            this.list.css(this.wh, $jc.intval(this.list.css(this.wh)) + wh + 'px');

            return e;
        },

        /**
         * Removes an element for the given index from the list.
         *
         * @method remove
         * @return undefined
         * @param i {Number} The index of the element.
         */
        remove: function(i) {
            var e = this.get(i);

            // Check if item exists and is not currently visible
            if (!e.length || (i >= this.first && i <= this.last))
                return;

            var d = this.dimension(e);

            if (i < this.first)
                this.list.css(this.lt, $jc.intval(this.list.css(this.lt)) + d + 'px');

            e.remove();

            this.list.css(this.wh, $jc.intval(this.list.css(this.wh)) - d + 'px');
        },

        /**
         * Moves the carousel forwards.
         *
         * @method next
         * @return undefined
         */
        next: function() {
            this.stopAuto();

            if (this.tail != null && !this.inTail)
                this.scrollTail(false);
            else
                this.scroll(((this.options.wrap == 'both' || this.options.wrap == 'last') && this.options.size != null && this.last == this.options.size) ? 1 : this.first + this.options.scroll);
        },

        /**
         * Moves the carousel backwards.
         *
         * @method prev
         * @return undefined
         */
        prev: function() {
            this.stopAuto();

            if (this.tail != null && this.inTail)
                this.scrollTail(true);
            else
                this.scroll(((this.options.wrap == 'both' || this.options.wrap == 'first') && this.options.size != null && this.first == 1) ? this.options.size : this.first - this.options.scroll);
        },

        /**
         * Scrolls the tail of the carousel.
         *
         * @method scrollTail
         * @return undefined
         * @param b {Boolean} Whether scroll the tail back or forward.
         */
        scrollTail: function(b) {
            if (this.locked || this.animating || !this.tail)
                return;

            var pos  = $jc.intval(this.list.css(this.lt));

            !b ? pos -= this.tail : pos += this.tail;
            this.inTail = !b;

            // Save for callbacks
            this.prevFirst = this.first;
            this.prevLast  = this.last;

            this.animate(pos);
        },

        /**
         * Scrolls the carousel to a certain position.
         *
         * @method scroll
         * @return undefined
         * @param i {Number} The index of the element to scoll to.
         * @param a {Boolean} Flag indicating whether to perform animation.
         */
        scroll: function(i, a) {
            if (this.locked || this.animating)
                return;

            this.animate(this.pos(i), a);
        },

        /**
         * Prepares the carousel and return the position for a certian index.
         *
         * @method pos
         * @return {Number}
         * @param i {Number} The index of the element to scoll to.
         */
        pos: function(i) {
            var pos  = $jc.intval(this.list.css(this.lt));

            if (this.locked || this.animating)
                return pos;

            if (this.options.wrap != 'circular')
                i = i < 1 ? 1 : (this.options.size && i > this.options.size ? this.options.size : i);

            var back = this.first > i;

            // Create placeholders, new list width/height
            // and new list position
            var f = this.options.wrap != 'circular' && this.first <= 1 ? 1 : this.first;
            var c = back ? this.get(f) : this.get(this.last);
            var j = back ? f : f - 1;
            var e = null, l = 0, p = false, d = 0, g;

            while (back ? --j >= i : ++j < i) {
                e = this.get(j);
                p = !e.length;
                if (e.length == 0) {
                    e = this.create(j).addClass(this.className('jcarousel-item-placeholder'));
                    c[back ? 'before' : 'after' ](e);

                    if (this.first != null && this.options.wrap == 'circular' && this.options.size !== null && (j <= 0 || j > this.options.size)) {
                        g = this.get(this.index(j));
                        if (g.length)
                            this.add(j, g.children().clone(true));
                    }
                }

                c = e;
                d = this.dimension(e);

                if (p)
                    l += d;

                if (this.first != null && (this.options.wrap == 'circular' || (j >= 1 && (this.options.size == null || j <= this.options.size))))
                    pos = back ? pos + d : pos - d;
            }

            // Calculate visible items
            var clipping = this.clipping();
            var cache = [];
            var visible = 0, j = i, v = 0;
            var c = this.get(i - 1);

            while (++visible) {
                e = this.get(j);
                p = !e.length;
                if (e.length == 0) {
                    e = this.create(j).addClass(this.className('jcarousel-item-placeholder'));
                    // This should only happen on a next scroll
                    c.length == 0 ? this.list.prepend(e) : c[back ? 'before' : 'after' ](e);

                    if (this.first != null && this.options.wrap == 'circular' && this.options.size !== null && (j <= 0 || j > this.options.size)) {
                        g = this.get(this.index(j));
                        if (g.length)
                            this.add(j, g.find('>*').clone(true));
                    }
                }

                c = e;
                var d = this.dimension(e);
                if (d == 0) {
                    alert('jCarousel: No width/height set for items. This will cause an infte loop. Aborting...');
                    return 0;
                }

                if (this.options.wrap != 'circular' && this.options.size !== null && j > this.options.size)
                    cache.push(e);
                else if (p)
                    l += d;

                v += d;

                if (v >= clipping)
                    break;

                j++;
            }

             // Remove out-of-range placeholders
            for (var x = 0; x < cache.length; x++)
                cache[x].remove();

            // Resize list
            if (l > 0) {
                this.list.css(this.wh, this.dimension(this.list) + l + 'px');

                if (back) {
                    pos -= l;
                    this.list.css(this.lt, $jc.intval(this.list.css(this.lt)) - l + 'px');
                }
            }

            // Calculate first and last item
            var last = i + visible - 1;
            if (this.options.wrap != 'circular' && this.options.size && last > this.options.size)
                last = this.options.size;

            if (j > last) {
                visible = 0, j = last, v = 0;
                while (++visible) {
                    var e = this.get(j--);
                    if (!e.length)
                        break;
                    v += this.dimension(e);
                    if (v >= clipping)
                        break;
                }
            }

            var first = last - visible + 1;
            if (this.options.wrap != 'circular' && first < 1)
                first = 1;

            if (this.inTail && back) {
                pos += this.tail;
                this.inTail = false;
            }

            this.tail = null;
            if (this.options.wrap != 'circular' && last == this.options.size && (last - visible + 1) >= 1) {
                var m = $jc.margin(this.get(last), !this.options.vertical ? 'marginRight' : 'marginBottom');
                if ((v - m) > clipping)
                    this.tail = v - clipping - m;
            }

            // Adjust position
            while (i-- > first)
                pos += this.dimension(this.get(i));

            // Save visible item range
            this.prevFirst = this.first;
            this.prevLast  = this.last;
            this.first     = first;
            this.last      = last;

            return pos;
        },

        /**
         * Animates the carousel to a certain position.
         *
         * @method animate
         * @return undefined
         * @param p {Number} Position to scroll to.
         * @param a {Boolean} Flag indicating whether to perform animation.
         */
        animate: function(p, a) {
            if (this.locked || this.animating)
                return;

            this.animating = true;

            var self = this;
            var scrolled = function() {
                self.animating = false;

                if (p == 0)
                    self.list.css(self.lt,  0);

                if (self.options.wrap == 'circular' || self.options.wrap == 'both' || self.options.wrap == 'last' || self.options.size == null || self.last < self.options.size)
                    self.startAuto();

                self.buttons();
                self.notify('onAfterAnimation');
            };

            this.notify('onBeforeAnimation');

            // Animate
            if (!this.options.animation || a == false) {
                this.list.css(this.lt, p + 'px');
                scrolled();
            } else {
                var o = !this.options.vertical ? {'left': p} : {'top': p};
                this.list.animate(o, this.options.animation, this.options.easing, scrolled);
            }
        },

        /**
         * Starts autoscrolling.
         *
         * @method auto
         * @return undefined
         * @param s {Number} Seconds to periodically autoscroll the content.
         */
        startAuto: function(s) {
            if (s != undefined)
                this.options.auto = s;

            if (this.options.auto == 0)
                return this.stopAuto();

            if (this.timer != null)
                return;

            var self = this;
            this.timer = setTimeout(function() { self.next(); }, this.options.auto * 1000);
        },

        /**
         * Stops autoscrolling.
         *
         * @method stopAuto
         * @return undefined
         */
        stopAuto: function() {
            if (this.timer == null)
                return;

            clearTimeout(this.timer);
            this.timer = null;
        },

        /**
         * Sets the states of the prev/next buttons.
         *
         * @method buttons
         * @return undefined
         */
        buttons: function(n, p) {
            if (n == undefined || n == null) {
                var n = !this.locked && this.options.size !== 0 && ((this.options.wrap && this.options.wrap != 'first') || this.options.size == null || this.last < this.options.size);
                if (!this.locked && (!this.options.wrap || this.options.wrap == 'first') && this.options.size != null && this.last >= this.options.size)
                    n = this.tail != null && !this.inTail;
            }

            if (p == undefined || p == null) {
                var p = !this.locked && this.options.size !== 0 && ((this.options.wrap && this.options.wrap != 'last') || this.first > 1);
                if (!this.locked && (!this.options.wrap || this.options.wrap == 'last') && this.options.size != null && this.first == 1)
                    p = this.tail != null && this.inTail;
            }

            var self = this;

            this.buttonNext[n ? 'bind' : 'unbind'](this.options.buttonNextEvent + '.jcarousel', this.funcNext)[n ? 'removeClass' : 'addClass'](this.className('jcarousel-next-disabled')).attr('disabled', n ? false : true);
            this.buttonPrev[p ? 'bind' : 'unbind'](this.options.buttonPrevEvent + '.jcarousel', this.funcPrev)[p ? 'removeClass' : 'addClass'](this.className('jcarousel-prev-disabled')).attr('disabled', p ? false : true);

            if (this.buttonNext.length > 0 && (this.buttonNext[0].jcarouselstate == undefined || this.buttonNext[0].jcarouselstate != n) && this.options.buttonNextCallback != null) {
                this.buttonNext.each(function() { self.options.buttonNextCallback(self, this, n); });
                this.buttonNext[0].jcarouselstate = n;
            }

            if (this.buttonPrev.length > 0 && (this.buttonPrev[0].jcarouselstate == undefined || this.buttonPrev[0].jcarouselstate != p) && this.options.buttonPrevCallback != null) {
                this.buttonPrev.each(function() { self.options.buttonPrevCallback(self, this, p); });
                this.buttonPrev[0].jcarouselstate = p;
            }
        },

        /**
         * Notify callback of a specified event.
         *
         * @method notify
         * @return undefined
         * @param evt {String} The event name
         */
        notify: function(evt) {
            var state = this.prevFirst == null ? 'init' : (this.prevFirst < this.first ? 'next' : 'prev');

            // Load items
            this.callback('itemLoadCallback', evt, state);

            if (this.prevFirst !== this.first) {
                this.callback('itemFirstInCallback', evt, state, this.first);
                this.callback('itemFirstOutCallback', evt, state, this.prevFirst);
            }

            if (this.prevLast !== this.last) {
                this.callback('itemLastInCallback', evt, state, this.last);
                this.callback('itemLastOutCallback', evt, state, this.prevLast);
            }

            this.callback('itemVisibleInCallback', evt, state, this.first, this.last, this.prevFirst, this.prevLast);
            this.callback('itemVisibleOutCallback', evt, state, this.prevFirst, this.prevLast, this.first, this.last);
        },

        callback: function(cb, evt, state, i1, i2, i3, i4) {
            if (this.options[cb] == undefined || (typeof this.options[cb] != 'object' && evt != 'onAfterAnimation'))
                return;

            var callback = typeof this.options[cb] == 'object' ? this.options[cb][evt] : this.options[cb];

            if (!$.isFunction(callback))
                return;

            var self = this;

            if (i1 === undefined)
                callback(self, state, evt);
            else if (i2 === undefined)
                this.get(i1).each(function() { callback(self, this, i1, state, evt); });
            else {
                for (var i = i1; i <= i2; i++)
                    if (i !== null && !(i >= i3 && i <= i4))
                        this.get(i).each(function() { callback(self, this, i, state, evt); });
            }
        },

        create: function(i) {
            return this.format('<li></li>', i);
        },

        format: function(e, i) {
            var $e = $(e).addClass(this.className('jcarousel-item')).addClass(this.className('jcarousel-item-' + i)).css({
                'float': 'left',
                'list-style': 'none'
            });
            $e.attr('jcarouselindex', i);
            return $e;
        },

        className: function(c) {
            return c + ' ' + c + (!this.options.vertical ? '-horizontal' : '-vertical');
        },

        dimension: function(e, d) {
            var el = e.jquery != undefined ? e[0] : e;

            var old = !this.options.vertical ?
                el.offsetWidth + $jc.margin(el, 'marginLeft') + $jc.margin(el, 'marginRight') :
                el.offsetHeight + $jc.margin(el, 'marginTop') + $jc.margin(el, 'marginBottom');

            if (d == undefined || old == d)
                return old;

            var w = !this.options.vertical ?
                d - $jc.margin(el, 'marginLeft') - $jc.margin(el, 'marginRight') :
                d - $jc.margin(el, 'marginTop') - $jc.margin(el, 'marginBottom');

            $(el).css(this.wh, w + 'px');

            return this.dimension(el);
        },

        clipping: function() {
            return !this.options.vertical ?
                this.clip[0].offsetWidth - $jc.intval(this.clip.css('borderLeftWidth')) - $jc.intval(this.clip.css('borderRightWidth')) :
                this.clip[0].offsetHeight - $jc.intval(this.clip.css('borderTopWidth')) - $jc.intval(this.clip.css('borderBottomWidth'));
        },

        index: function(i, s) {
            if (s == undefined)
                s = this.options.size;

            return Math.round((((i-1) / s) - Math.floor((i-1) / s)) * s) + 1;
        }
    });

    $jc.extend({
        /**
         * Gets/Sets the global default configuration properties.
         *
         * @method defaults
         * @return {Object}
         * @param d {Object} A set of key/value pairs to set as configuration properties.
         */
        defaults: function(d) {
            return $.extend(defaults, d || {});
        },

        margin: function(e, p) {
            if (!e)
                return 0;

            var el = e.jquery != undefined ? e[0] : e;

            if (p == 'marginRight' && $.browser.safari) {
                var old = {'display': 'block', 'float': 'none', 'width': 'auto'}, oWidth, oWidth2;

                $.swap(el, old, function() { oWidth = el.offsetWidth; });

                old['marginRight'] = 0;
                $.swap(el, old, function() { oWidth2 = el.offsetWidth; });

                return oWidth2 - oWidth;
            }

            return $jc.intval($.css(el, p));
        },

        intval: function(v) {
            v = parseInt(v);
            return isNaN(v) ? 0 : v;
        }
    });







})







(jQuery);