/**
 * Select
 * @extends EventDispatcher
 * @constructor
 * @param {Object} target An instance of SelectTarget.
 */
function Select(target)
{
	// Call the prototype's constructor
	EventDispatcher.call(this);

	// Private
	var self		= this,
		dom			= new DOM(),
		input		= null,
		selection	= null,
		options		= null,
		select		= null;

	// Public
	this.getValue	= getValue;

	// Constructor
	if (target instanceof SelectTarget == false)
	{
		throw new Error("Invalid argument: target is not an instance of SelectTarget");
	}

	createElements();
	replaceTarget();

	/**
	 * Create the required elements
	 */
	function createElements()
	{
		// Hidden input field, contains the selected value
		input = dom.create(
			"input",
			{
				type	: "hidden"
			}
		);

		// If the target element has a name attribute, use it for the hidden input field
		if (target.getElement().name != undefined && target.getElement().name != "")
		{
			input.name = target.getElement().name;
		}

		// The "face" of the select, shows the selected value
		selection = dom.create(
			"a",
			{
				href		: "#",
				onclick		: toggleOptions,
				className	: "selection"
			},
			[
				"Maak een keuze"
			]
		);

		// The list of the available options
		options = dom.create(
			"ul",
			{
				className	: "options"
			}
		);

		// The Select container
		select = dom.create(
			"div",
			{
				className : "select"
			},
			[
				input,
				selection,
				options
			]
		);
	}

	/**
	 * Replace the target with the newly created Select
	 */
	function replaceTarget()
	{
		var targetOptions = target.getOptions();

		// Loop through the options from the target, and build the options list
		for (var i = 0; i < targetOptions.length; i++)
		{
			// Create the list item
			var option = dom.create(
				"li",
				null,
				[
					dom.create(
						"a",
						{
							href		: "#",
							onclick		: (function(text, value)
							{
								return function(event)
								{
									if (this.hasClass("disabled") == false)
									{
										setValue(text, value);
										toggleOptions();
									}

									return false;
								}
							})(targetOptions[i].getText(), targetOptions[i].getValue()),
							className	: (targetOptions[i].getDisabled() == true ? "disabled" : null),
							title		: targetOptions[i].getTitle()
						},
						[
							targetOptions[i].getText()
						]
					)
				]
			);

			options.appendChild(option);

			// Update the value if the processed option was selected
			if (targetOptions[i].getSelected() == true)
			{
				setValue(targetOptions[i].getText(), targetOptions[i].getValue());
			}
		}

		// Replace the target element with the new Select
		target.getElement().parentNode.replaceChild(select, target.getElement());
	}

	/**
	 * Close the options when a click outside of the Select is detected
	 * @event
	 * @param {Event} event The event dispatched by the listener.
	 */
	function outsideClickListener(event)
	{
		if (event == undefined)
		{
			event = window.event;
		}

		if (event.target == undefined)
		{
			event.target = event.srcElement;
		}

		var target = event.target;

		// Check if the clicked target was not the Select
		while (target != null && target != select && target.parentNode != null)
		{
			target = target.parentNode;
		}

		if (target != select)
		{
			toggleOptions();
		}
	}

	/**
	 * Toggle the visiblity op the options
	 * @return {Boolean} Always false, so it cancels click events.
	 */
	function toggleOptions()
	{
		if (select.hasClass("active") == true)
		{
			dom.getFirstByTagName("html").removeEventListener("click", outsideClickListener, false);

			select.removeClass("active");
		}
		else
		{
			select.addClass("active");

			dom.getFirstByTagName("html").addEventListener("click", outsideClickListener, false);
		}

		return false;
	}

	/**
	 * Set the selected value
	 * @param {String} text The description of the selected option.
	 * @param {String} value The selected value.
	 */
	function setValue(text, value)
	{
		// If the value actually changed, update the hidden field and dispatch an event
		if (input.value != value)
		{
			input.value = value;

			self.dispatchEvent(new CustomEvent(Select.CHANGED, self));
		}

		// Update the selection element
		selection.firstChild.nodeValue = text;

		// Reset the selected class
		var optionAnchors = options.getElementsByTagName("a");

		for (var i = 0; i < optionAnchors.length; i++)
		{
			dom.extend(optionAnchors[i]).removeClass("selected");

			if (optionAnchors[i].firstChild.nodeValue == text)
			{
				optionAnchors[i].addClass("selected");
			}
		}
	}

	/**
	 * Get the current selected value
	 * @return {String} The current selected value.
	 */
	function getValue()
	{
		return input.value;
	}
}

// Inheritance
Select.prototype = new EventDispatcher();

// Static
Select.CHANGED = "changed";



/**
 * SelectOption
 * @extends Select
 * @constructor
 * @param {String} value The select option value
 * @param {String} text The select option value
 */
function SelectOption(value, text)
{
	// Private
	var self		= this,
		dom			= new DOM(),
		title		= null,
		selected	= false,
		disabled	= false;

	// Public
	this.setValue		= setValue;
	this.setText		= setText;
	this.setTitle		= setTitle;
	this.setSelected	= setSelected;
	this.setDisabled	= setDisabled;
	this.getValue		= getValue;
	this.getText		= getText;
	this.getTitle		= getTitle;
	this.getSelected	= getSelected;
	this.getDisabled	= getDisabled;

	// Constructor
	setValue(value);
	setText(text);

	/**
	 * Set the value
	 * @param {String} v The value of value.
	 */
	function setValue(v)
	{
		if (typeof v != "string") 
		{
			throw new Error("Invalid argument: value is missing or not a string");
		}

		value = v;
	}

	/**
	 * Set the text
	 * @param {String} t The value of text.
	 */
	function setText(t)
	{
		if (typeof t != "string") 
		{
			throw new Error("Invalid argument: text is missing or not a string");
		}

		text = t;
	}

	/**
	 * Set the title
	 * @param {String} v The value of title.
	 */
	function setTitle(t)
	{
		if (typeof t != "string") 
		{
			throw new Error("Invalid argument: title is missing or not a string");
		}

		title = t;
	}

	/**
	 * Set the selected state
	 * @param {String} s The value of selected.
	 */
	function setSelected(s)
	{
		if (typeof s != "boolean") 
		{
			throw new Error("Invalid argument: selected is missing or not a boolean");
		}

		selected = s;
	}

	/**
	 * Set the disabled state
	 * @param {String} d The value of disabled.
	 */
	function setDisabled(d)
	{
		if (typeof d != "boolean") 
		{
			throw new Error("Invalid argument: disabled is missing or not a boolean");
		}

		disabled = d;
	}

	/**
	 * Get the value
	 * @return {String} The value of value.
	 */
	function getValue()
	{
		return value;
	}

	/**
	 * Get the text
	 * @return {String} The value of text.
	 */
	function getText()
	{
		return text;
	}

	/**
	 * Get the title
	 * @return {String} The value of title.
	 */
	function getTitle()
	{
		return title;
	}

	/**
	 * Get the selected state
	 * @return {Boolean} The value of selected.
	 */
	function getSelected()
	{
		return selected;
	}

	/**
	 * Get the disabled state
	 * @return {Boolean} The value of disabled.
	 */
	function getDisabled()
	{
		return disabled;
	}
}




/**
 * SelectTarget
 * @constructor
 * @param {Object} element The select target element
 */
function SelectTarget(element)
{
	// Private
	var self	= this,
		dom		= new DOM();

	// Public
	this.getElement	= getElement;
	this.getOptions	= getOptions;

	/**
	 * Get the target element
	 * @return {Object}	Target element.
	 */
	function getElement()
	{
		return element;
	}

	/**
	 * Get the options from the target
	 * @return {Array} An array with instances of Option.
	 */
	function getOptions()
	{
		return [];
	}
}



/**
 * SelectSelectTarget
 * @extends  SelectTarget
 * @constructor
 * @param {Object} element The select select target element
 */
function SelectSelectTarget(element)
{
	// Call the prototype's constructor
	SelectTarget.call(this, element);

	// Private
	var self	= this,
		dom		= new DOM();

	// Public
	this.getOptions	= getOptions;

	// Constructor
	if (element == undefined || element.nodeName == undefined || element.nodeName.toLowerCase() != "select")
	{
		throw new Error("Invalid argument: element is missing or not a select element");
	}

	dom.extend(element);

	/**
	 * Get the options from the target
	 * @return {Array} An array with instances of Option.
	 */
	function getOptions()
	{
		var options = [];

		for (var i = 0; i < element.options.length; i++)
		{
			var option = new SelectOption(
				element.options[i].value,
				element.options[i].text
			);

			if (element.options[i].title != " ")
			{
				option.setTitle(element.options[i].title);
			}

			option.setSelected(element.options[i].selected);
			option.setDisabled(element.options[i].disabled);

			options.push(option);
		}

		return options;
	}
}

// Inheritance
SelectSelectTarget.prototype = new SelectTarget();




/**
 * ListSelectTarget
 * @extends  SelectTarget
 * @constructor
 * @param {Object} element The list select target element
 */
function ListSelectTarget(element)
{
	// Call the prototype's constructor
	SelectTarget.call(this, element);

	// Private
	var self	= this,
		dom		= new DOM();

	// Public
	this.getOptions	= getOptions;

	// Constructor
	if (element == undefined || element.nodeName == undefined || (element.nodeName.toLowerCase() != "ul" && element.nodeName.toLowerCase() != "ol"))
	{
		throw new Error("Invalid argument: element is missing, not a ul element or not an ol element");
	}

	dom.extend(element);

	/**
	 * Get the options from the target
	 * @return {Array} An array with instances of Option.
	 */
	function getOptions()
	{
		var options					= [],
			listItems				= element.getElementsByTagName("li"),
			selectedClassMissing	= (element.getByClassName("selected").length == 0 ? true : false);

		for (var i = 0; i < listItems.length; i++)
		{
			dom.extend(listItems[i]);

			var valueElements	= listItems[i].getByClassName("value"),
				textElements	= listItems[i].getByClassName("text"),
				value			= null,
				text			= null;

			// Check if there's an element with the class "value"
			if (valueElements.length == 1)
			{
				var valueElementChildNodes = valueElements[0].childNodes;

				// Look for text in the value element
				for (var j = 0; j < valueElementChildNodes.length; j++)
				{
					if (valueElementChildNodes[j].nodeType == 3)
					{
						value = valueElementChildNodes[j].nodeValue;

						break;
					}
				}
			}
			else
			{
				var listItemChildNodes = listItems[i].childNodes;

				// Look for text in the list item element
				for (var j = 0; j < listItemChildNodes.length; j++)
				{
					if (listItemChildNodes[j].nodeType == 3)
					{
						value = listItemChildNodes[j].nodeValue;

						break;
					}
				}
			}

			// Check if there's an element with the class "text"
			if (textElements.length == 1)
			{
				var textElementChildNodes = textElements[0].childNodes;

				// Look for text in the value element
				for (var j = 0; j < textElementChildNodes.length; j++)
				{
					if (textElementChildNodes[j].nodeType == 3)
					{
						text = textElementChildNodes[j].nodeValue;

						break;
					}
				}
			}
			else
			{
				text = value;
			}

			if (value != null & text != null)
			{
				var option = new SelectOption(value, text);

				if (listItems[i].title != "")
				{
					option.setTitle(listItems[i].title);
				}

				if ((selectedClassMissing == false && listItems[i].hasClass("selected")) || selectedClassMissing == true && i == 0)
				{
					option.setSelected(true);
				}

				if (listItems[i].hasClass("disabled"))
				{
					option.setDisabled(true);
				}

				options.push(option);
			}
		}

		return options;
	}
}

// Inheritance
ListSelectTarget.prototype = new SelectTarget();