(function(){
	function Panel(controller)
	{
		this.controller = controller;
		this.itemArr = [];
	}
	Panel.prototype = {
		controller: null,
		itemArr: null,
		$pause: null,
		
		switchTo: function(newId, duration, type)
		{
			var _this = this,
				curId = this.controller.getCurId(),
				dir = this.controller.getDir();

			// retain switch in progress
			this.controller.retainSwitch();

			if (dir !== 0 && curId != newId && this.itemArr[newId]) {
				// call transition
				this.transition[type]({
					controller: this.controller,
					curSlide: this.itemArr[curId],
					newSlide: this.itemArr[newId],
					duration: duration
				});
			} else {
				// inform controller that switch has finished
				this.controller.releaseSwitch();
			}
		},
		
		addNew: function($slide)
		{
			this.itemArr.push($slide);
		},
		
		// transition functions for switching between slides
		transition: {
			fade: function (paramObj) {
				var controller = paramObj.controller,
					$newSlide = paramObj.newSlide,
					$curSlide = paramObj.curSlide,
					duration = paramObj.duration;
				
				function animationCallback() {
					// to avoid interference on further animations get rid of right margin and current slide
					//$newSlide.css({marginLeft: "", marginRight: ""});
					$curSlide.remove();
					// inform controller that switch has finished
					controller.releaseSwitch();
				}
				
				// we move newSlide after curSlide
				$curSlide.after($newSlide);	    
				// animate curSlide out
				$newSlide.fadeIn(duration);
				// animate newSlide in			
				$curSlide.fadeOut(duration, animationCallback);
			},

			slide: function (paramObj) {
				var controller = paramObj.controller,
					$newSlide = paramObj.newSlide,
					$curSlide = paramObj.curSlide,
					width = controller.get$().outerWidth(),
					duration = paramObj.duration,
					dir = controller.getDir();
				 
				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
					controller.releaseSwitch();
				}
				// 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);
				}
			}
		} 
	};
	
	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");
			
			// retain switch in progress
			this.controller.retainSwitch();
			
			$paginationItems.removeClass("act");
			$paginationItems.eq(id).addClass("act");

			// inform controller that switch has finished
			this.controller.releaseSwitch();
		},
		
		addNew: function($slide)
		{
			var _this = this,
				// prepare the item
				$item = $("<li></li>"),
				id = 0;

			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
			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 > 0) ? this.$node : $();
		},
		
		setReady: function()
		{
			this.$node.addClass("ready");
		}
	};
	
	function Controller(paramObj)
	{
		if (paramObj.node === undefined) {
			throw new Error("Carousel.Controller constructor: node parameter has not been specified");
		}
		if (paramObj.node.size() === 0) {
			return null;
		}
		if (paramObj.selector === undefined) {
			throw new Error("Carousel.Controller constructor: selector parameter has not been specified");
		}
		
		var $node = paramObj.node,
			selector = paramObj.selector,
			duration = paramObj.duration || 200, 
			type = paramObj.type || "fade",
			$slides = $node.find(selector),
			// create pagination and slides panel objects
			panel = new Panel(this),
			pagination = new Pagination(this),
			listeners = paramObj.listeners,
			listener = "",
			// prepare private "this" for functions called in different scope
			_this = this;
		
		// first of all add listeners
		this.listeners = new (WN.ns("Patterns.Listeners"))();
		if (typeof listeners == "object") {
			for (listener in listeners) {
				this.listeners.add(listener, listeners[listener]);
			}
		}
		
		// 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);

			_this.listeners.run("onAddSlideBegin", this, [id, $node]);
			pagination.addNew($node);
			panel.addNew($node);
			_this.listeners.run("onAddSlideEnd", this, [id, $node]);
		});

		// add pagination to the DOM
		$node.append(pagination.get$());
		$node.append("<div class=\"pause\"><span>Paused</span></div>");
		pagination.setReady();
		
		// populate properties
		this.maxId = $slides.size() - 1;
		this.panel = panel;
		this.pagination = pagination;
		this.$node = $node;
		this.duration = duration;
		this.type = type;

		// remove all slides from the DOM except the first one
		$slides.not(":first").remove();		
		// initiate auto rotation
		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,
		listeners: null,
		
		getCurId: function()
		{
			return this.curId;
		},
		
		getDir: function()
		{
			return this.switchDirection;
		},
		
		get$: function()
		{
			return this.$node;
		},
		
		switchTo: function(id)
		{
			if (this.switchInProgress) {
				return;
			}
			this.retainSwitch();
			
			// determine switch direction and sanitized id 
			if (id > this.maxId) {
				id = 0;
				this.switchDirection = 1;
			} else if (id < 0) {
				id = this.maxId;
				this.switchDirection = -1;
			} else {
				this.switchDirection = id == this.curId ? 0 : (id < this.curId ? -1 : 1);
			}
			
			// swtich begins here but onSwitchEnd is run by releaseSwitch method
			this.listeners.run("onSwitchBegin", this, [this.curId, id]);

			// run switch on panel and pagination
			this.panel.switchTo(id, this.duration, this.type);
			this.pagination.switchTo(id);
			this.curId = id;
			
			// release switch in progress for this function
			this.releaseSwitch();
		},
		
		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);
		},
		
		retainSwitch: function(level)
		{
			var retain = level === undefined ? 1 : level;
			
			this.switchInProgress += retain;
		},
		
		releaseSwitch: function(level)
		{
			var release = level === undefined ? 1 : level;
			
			this.switchInProgress -= release;
			if (this.switchInProgress === 0) {
				this.listeners.run("onSwitchEnd", this, [this.curId]);
			}
		},
		
		addListener: function(name, handler)
		{
			this.listeners.add(name, handler);
		}
	};
	
	// makes class available within the WN.Carousel namespace
	WN.ns("Carousel.Controller", Controller);
})();


