argos.require("classes");
argos.require("utils");
argos.check("widgets");


argos.widgets.Scroller = function($items, options) {
//console.groupCollapsed("create scroller...");
	// Block scroller of contained content. Moves up, down, left or right, shifting an added container within the target element.
	// Uses jQuery

	// Helpers
	var _helpers = {
		height : argos.utils.height,
		width : argos.utils.width,
		unique : argos.utils.generateUniqueStr
	}
	
	// External Classes
	var _classes = {
		Button : argos.classes.Button,
		Collection : argos.classes.Collection,
		Element : argos.classes.Element
	}

	// Private variables
	var _scroller = this;
	var _$items = $items;
	var _automated = false; // Not currently implemented.
	var _moving = false;
	var _shiftTimer = null;
	var _position;

	
	// Externally configurable options.
	var _options = _updateOptions.call(new Object({
		"buttonTextDown" : "down",
		"buttonTextLeft" : "left",
		"buttonTextRight" : "right",
		"buttonTextUp" : "up",
		"container" : "<div />",
		"continuous" : true, // if false, button action will move by one child item (height or width depending on direction)
		"controller" : "<div />",
		"cssClassScroller" : "scroller",
		"cssClassDisabled" : "disabled",
		"duration" : 500,
		"interval" : 500,
		"offset" : 0,
		"smooth" : true, // if false, steps through content
		"step" : 0,
		"supportIE6HoverOnButtons" : true,
		"titleTextDown" : "Scroll Down",
		"titleTextLeft" : "Scroll Left",
		"titleTextRight" : "Scroll Right",	  
		"titleTextUp" : "Scroll Up",
		"vertical" : true, // Horizontal not yet implemented
		"visibleItemCount" : 2 // Control number of items showing.
	}), options);

	// Inheritance
	_classes.Element.call(this, "<div class=\"" + _options.cssClassScroller + "\"></div>");
	
	
	// PRIVATE:
	function _init() {
		var id = "scroller_" + _helpers.unique();
		if(_$items.length > 0) {
			var height = _helpers.height(_$items.eq(0));
			var margin = _$items.eq(0).css("margin-bottom");
			height = height + Number(margin.replace("px", ""));

			this.container = new Container();
			this.controller = new Controller();	

			this.$node.insertBefore(this.container.node);
			this.$node.append(this.container.node);
			this.$node.append(this.controller.node);
			this.$node.attr("id", id);

			_position = _initialisePosition(_$items);
			if(_options.visibleItemCount > 1) {
				this.$node.css("height", height * _options.visibleItemCount);
			}

			this.property("firstVisibleIndex", _position.current); // Initialise as zero.
			this.exists = true;
		}
		else {
			this.exists = false;
		}
	};

	function _updateOptions(customisations) {
		// Update options with new values.
		for(var c in customisations) {
			this[c] = customisations[c];
		}
		return this;
	}
	
	function _initialisePosition($items) {
		var position = {
			current : 0,
			values : new Array(),
			x : 0,
			y : 0
		}

		$items.each(function() {
			var $t = $(this);
			position.values.push({x: $t.position().top, y: $t.position().left});
		});

		return position; 
	}
	
	function _updatePosition(button) {
		var xPlusOrMinus, yPlusOrMinus = "-";
		var moved = false;
		var state = true;

		_moving = true;
		switch(button.Element.direction) {
			case "down" : 
				if(button.Element.clickable()) {
					_position.current++;
					moved = true;
				}
				break;
			case "left" : 
				// Not yet implemented.
				break;
			case "right" :
				// Not yet implemented.
				break;
			case "up" :
				if(button.Element.clickable()) {
					_position.current--;
					moved = true;
				}
				break;
			default : ; // what?
		}

		_position.x = _position.values[_position.current].x;  
		_position.y = _position.values[_position.current].y;
		_scroller.property("firstVisibleIndex", _position.current);  
		return moved;
	}
	
	function _updateButtons() {
		// Check and update button states (i.e. add/remove disabled classes);
		var buttons = _scroller.controller.buttons();
		var disabled = _options.cssClassDisabled;
		var specificDisabled, $node;
		for(var i=0; i<buttons.length; ++i) {
			specificDisabled = _specificDisabledClass(buttons[i].$node.attr("class"));
			$node = buttons[i].$node;
			if(buttons[i].clickable()) {
				$node.removeClass(disabled);
				$node.removeClass(specificDisabled);
			}
			else {
				if(!$node.hasClass(specificDisabled)) $node.addClass(specificDisabled);
				if(!$node.hasClass(disabled)) $node.addClass(disabled);
			}
		}
	}

	function _specificDisabledClass(disabledClass) {
		return disabledClass.replace(/^([\w]*).*/, "$1") + "_" + _options.cssClassDisabled;
	}
	
	function _shiftPosition(button) {
		// Jumps between each child element.
		if(_updatePosition(button)) {
			_scroller.container.$node.css("left", (~_position.y + 1) + "px");
			_scroller.container.$node.css("top", (~_position.x + 1) + "px");
			_stop();
			_updateButtons();
			if(_options.continuous) {
				// Recurses until mouseup event stops it.
				_shiftTimer = setTimeout(function(){
					_shiftPosition(button);
				}, _options.interval);
			}
		}
	}
	
	function _scrollPosition(button) {
		// Scrolls between each child element.
		var xAmount = 0;
		var yAmount = _position.y;
		var xUpdated = 0;
		var xPrevious = ~_position.x + 1 > 0 ? ~_position.x + 1 : _position.x;
		var xPlusOrMinus, yPlusOrMinus;

		if(_updatePosition(button)) {
			xUpdated = _position.x;
			switch(button.Element.direction) {
				case "down" : 
					xPlusOrMinus = "-";
					xAmount = xUpdated > xPrevious ? xUpdated - xPrevious : xUpdated;
					break;
				case "left" : 
					// Not yet implemented.
					break;
				case "right" : 
					// Not yet implemented.
					break;
				default : // up...
					xPlusOrMinus = "+";
					xAmount = xPrevious > xUpdated ? xPrevious - xUpdated : xPrevious;
			}

			_scroller.container.property("scrollAmount", xAmount);
			_scroller.container.property("direction", button.Element.direction); 
			_scroller.container.$node.animate({
	//		    "left" : yPlusOrMinus + "=" + _position.y,
				"top" : xPlusOrMinus + "=" + xAmount
			}, _options.duration, function() {
				_updateButtons();
				if(_options.continuous && _moving) {
					_scrollPosition(button);
				}
			});
		}
	}
	
	function _move(target) {
		if(!_moving) {
			_moving = true;   
			if(_options.smooth) {
				_scrollPosition(this);
			}
			else {
				_shiftPosition(this);
			}
		}
	}
	
	function _stop() {
		_moving = false;  
		clearTimeout(_shiftTimer);
	}

	function _resetToZeroPosition() {
		// Reset to zero position
		_position.current = 0;
		_position.x = 0;
		_position.y = 0;

		_scroller.container.$node.css("left", "0px");
		_scroller.container.$node.css("top", "0px");
		_scroller.property("firstVisibleIndex", _position.current);
	}


	// PUBLIC
	this.focusOn = function(number, align) {
		// Have the scroller move items forward and try to align as requested.
		// number = item number.
		var button = _options.vertical ? _scroller.controller.buttons("down") : _scroller.controller.buttons("right");
		var continuous = _options.continuous;
		var align =  align ? align : 0;
		var visible = _options.visibleItemCount ? _options.visibleItemCount : 0;
		var check;

		_resetToZeroPosition();

		// Make sure continuous is off.
		_options.continuous = false;

		switch(align) { 
			case "bottom" : // fall through.
			case "left" : 
				check = function(n) { return n - visible; }
				break;
			case "top" : // fall through.
			case "right" :
				check = function(n) { return n - 1; }
				break;
			default :
				// middle.
				check = function(n) { return (n - Math.ceil(visible / 2)); } 
		}

		while(check(number) > 0) {
			_shiftPosition(button.node);
			number--;
		} 

		// Reset back to previous setting.
		_options.continuous = continuous;
	}

	
	// CLASSES
	function Container() {
		// Basically adds a container elements around target items.
		_classes.Element.call(this, _options.container);

		(function _init() {
			this.$node.addClass("container");
			this.$node.insertBefore(_$items.eq(0));
			this.$node.append(_$items);
			this.$node.css("position", "relative");
		}).call(this);

	}
	Container.prototype = {
		getItem : function(num) {
			// Return found item based on passed number, or empty collection.
			var $children = this.$node.children();
			var $child = new _classes.Collection();
			if(num >= 0 && $children.length >= num) {
				$child = $children.eq(num); 
			}
			return $child;
		}
	}


	function Controller() {
		_classes.Element.call(this, _options.controller);
		var _events = {
			mousedown : _move,
			mouseup : _stop
		}
	
		var _clickable = function() {
			var button = this;
			var clickable = false;
			switch(button.property("index")) {
				case 0:
					clickable = _position.current < _position.values.length - 1 && (_scroller.container.$node.height() - _scroller.$node.height()) - _position.values[_position.current].x > 0; 
					break;
				case 1:
					clickable = false; // not yet implemented. 
					break;
				case 2:
					clickable = false; // not yet implemented. 
					break;
				case 3:
					clickable = _position.current > 0; 
					break;
				default: ; // do nothing.
			}
			return clickable;
		}

		var _buttons = [
			new _classes.Button({
				cssClass : "down",
				events : _events,
				title : _options.titleTextDown, 
				text : _options.buttonTextDown
			}),
			new _classes.Button({
				cssClass : "left " + _specificDisabledClass("left") + " " + _options.cssClassDisabled,  
				events : _events,
				title : _options.titleTextLeft, 
				text : _options.buttonTextLeft
			}),
			new _classes.Button({
				cssClass : "right",
				events : _events,
				title : _options.titleTextRight, 
				text : _options.buttonTextRight
			}),
			new _classes.Button({
				cssClass : "up " + _specificDisabledClass("up") + " " + _options.cssClassDisabled, 
				events : _events,
				title : _options.titleTextUp, 
				text : _options.buttonTextUp
			})
		];

		for(var i=0; i<_buttons.length; ++i) {
			_buttons[i].property("index", i);
			_buttons[i].clickable = _clickable;
			_buttons[i].direction = _buttons[i].$node.attr("class").replace(/^(\w*\b).*/,"$1");
			this.$node.append(_buttons[i].node);
		}
		
		this.$node.addClass("controller");
		this.$node.append($("<p class=\"status\"></p>"));
		
		this.buttons = function(direction) {
			var button = null;
			if(arguments.length > 0) {
				for(var i=0; i<_buttons.length; ++i) {
					if(_buttons[i].direction == direction) {
						button = _buttons[i];
						break; 
					} 
				}
			}
			return button ? button : _buttons;
		}

	}

	// Since jQuery triggers script tag content when it appends the object to something, 
	// this will fire when the created scroller is appended to an element in the DOM. 
	// This allows us to trigger an onload event for the element.
	// e.g. $("#myelement").append(new Scroller()); // will fire the script but
	// var myscroller = new Scroller(); // will not.
//    return $("<script>$(this).trigger(\"" + this.property("loadedEventName") + "\");</script>");

	_init.call(this);
//console.groupEnd();
}

