(function(){

	function Panel(controller)
	{
		this.controller = controller;
		this.itemArr = [];
	}
	Panel.prototype = {
		controller: null,
		itemArr: null,
		
		switchTo: function(newId, duration)
		{
			var _this = this,
				curId = this.controller.getCurId(),
				dir = this.controller.getDir(),
				width = this.controller.get$().outerWidth();
				
			if (dir != 0 && curId != newId && this.itemArr[newId]) {
				var $curSlide = this.itemArr[curId];
				// newSlide is the jQuery object from the array
				var $newSlide = this.itemArr[newId];
				// called regardless of direction 
				var animationCallback = function() {
					// to avoid interference on further animations get rid of right margin and current slide
					$newSlide.css({marginLeft: "", marginRight: ""});
					$curSlide.remove();
					// let know the controller that switch has finished
					_this.controller.onSwitchEnd();
				}
				// slide from right to left (forward)
				if (dir == 1) {
					// initialize newSlide's and curSlide's position
					$newSlide.css({marginRight: -width + "px", marginLeft: ""});				
					$curSlide.css({marginLeft: "", marginRight: ""});
					// we move newSlide after curSlide
					$curSlide.after($newSlide);
					// animate curSlide out
					$curSlide.animate({marginLeft: -width + "px"}, duration, "swing", animationCallback);
				// slide from left to right (backward)
				} else if (dir == -1) {
					$curSlide.css({marginRight: -width + "px", marginLeft: ""});
					$newSlide.css({marginRight: "", marginLeft: -width + "px"});
					// we move newSlide before curSlide
					$curSlide.before($newSlide);
					$newSlide.animate({marginLeft: "0px"}, duration, "swing", animationCallback);
				}
			} else {
				this.controller.onSwitchEnd();
			}
		},
		
		addNew: function($slide)
		{
			this.itemArr.push($slide);
		}
	}
	
	function Pagination(controller)
	{
		var _this = this;
		
		this.itemArr = [];
		this.controller = controller;
		this.$node = $("<div class=\"pagination\"><ul class=\"nav-scroll\"><li class=\"first\"><a href=\"#\"><span>&nbsp;</span></a></li><li class=\"last\"><a href=\"#\"><span>&nbsp;</span></a></li></ul></div>");
		this.$node.find("li.first a")
			.click(function(e){
				_this.controller.switchToPrev();
				$(this).blur();
				return false;
			});
		this.$node.find("li.last a")
			.click(function(e){
				_this.controller.switchToNext();
				$(this).blur();
				return false;
			});
	}
	Pagination.prototype = {
		itemArr: null,
		controller: null,
		$item: null,
		$node: null,
		
		switchTo: function(id)
		{
			var $paginationItems = this.$node.find("li").not(".first").not(".last");
			$paginationItems.removeClass("act");
			$paginationItems.eq(id).addClass("act");
			this.controller.onSwitchEnd();
		},
		
		addNew: function($slide)
		{
			var _this = this;

			// prepare the item
			var $item = $("<li></li>");
			this.itemArr.push($item);
			$item.html("<a href=\"#\"><span>" + this.itemArr.length + "</span>");
			this.$node.find("li.last").before($item);
			
			// prepare event handler for the item
			var id = this.itemArr.length - 1;
			$item.find("a").click(function(e){
				_this.controller.switchTo(id);
				$(this).blur();
				return false;
			});
		},
		
		get$: function()
		{
			return (this.itemArr.length > 1) ? this.$node : $();
		},
		
		setReady: function()
		{
			this.$node.addClass("ready");
		}
	}
	
	function Controller($node, selector, duration)
	{
		// prepare private this for functions called in different scope
		var _this = this,
			$slides = $node.find(selector),
			// create pagination and slides panel objects
			panel = new Panel(this),
			pagination = new Pagination(this);
		
		// don't rotate slides when mouse is over the panel
		$node
			.mouseenter(function(e) {
				_this.stopRotation();
				$node.addClass("hover");
			})
			.mouseleave(function(e) {
				_this.startRotation();
				$node.removeClass("hover");
		});
		
		
		// add slides to pagination and slides panel
		$slides.each(function(id, node) {
			var $node = $(node);
			pagination.addNew($node);
			panel.addNew($node);
		});
		// add pagination
		$node.append(pagination.get$());
		pagination.setReady();
		
		// remove all slides except the first one
		$slides.not(":first").remove();
		
		this.maxId = $slides.size() - 1;
		this.panel = panel;
		this.pagination = pagination;
		this.$node = $node;
		this.duration = duration
		
		this.switchTo(0);
		this.startRotation();
	}
	Controller.prototype = {
		curId: 0,
		maxId: 0,
		timeoutId: 0,
		switchInProgress: 0,
		switchDirection: 0,
		panel: null,
		pagination: null,
		duration: 500,
		$node: null,
		
		getCurId: function()
		{
			return this.curId;
		},
		
		getDir: function()
		{
			return this.switchDirection;
		},
		
		get$: function()
		{
			return this.$node;
		},
		
		switchTo: function(id)
		{
			if (this.switchInProgress) {
				return;
			}
			// retain = 3 for this function + slide panel + pagination
			this.onSwitchBegin(3);
			
			// determine switch direction and sanitized id 
			this.switchDirection = id == this.curId ? 0 : (id < this.curId ? -1 : 1);
			if (id > this.maxId) {
				id = 0;
				this.switchDirection = 1;
			} else if (id < 0) {
				id = this.maxId;
				this.switchDirection = -1;
			}
			
			// run switch on panel and pagination
			this.panel.switchTo(id, this.duration);
			this.pagination.switchTo(id);
			this.curId = id;
			// release for this function
			this.onSwitchEnd();
		},
		
		switchToNext: function()
		{
			this.switchTo(this.curId + 1);
		},
		
		switchToPrev: function()
		{
			this.switchTo(this.curId - 1);
		},
		
		startRotation: function()
		{
			var _this = this;
			this.timeoutId = setTimeout(function(){
				_this.switchToNext();
				_this.startRotation();
			}, 5000);
		},
		
		stopRotation: function()
		{
			clearTimeout(this.timeoutId);
		},
		
		onSwitchBegin: function(retain)
		{
			retain = retain == undefined ? 1 : retain;
			this.switchInProgress += retain;
		},
		
		onSwitchEnd: function(release)
		{
			release = release == undefined ? 1 : release;
			this.switchInProgress -= release;
		}
	}
	
	// makes class available within the WN.Carousel namespace
	WN.ns("Carousel.Controller", Controller);
})();


