/**
 * manages multiple tags
 * @namespace INTRIGO
 * @requires INTRIGO
 * @param {String|DOM} container: a container the tags will be outputed in
 * @param {String} formName[optional]: the name outputed to the post (by default: tag[])
 */
INTRIGO.Tags = function(container, formName){
	if (typeof(container) == 'string') 
		var container = document.getElementById(container);
	var self = this;
	
	INTRIGO.addClassName(container, 'tags');
	
	this._container = container;
	this._limit = 0;
	this._limitReached = false;
	this._labelLimit = false;
	
	if (typeof(formName) != 'undefined') 
		this._formName = formName;
	else this._formName = 'tag[]';
	this._tags = [];
}
INTRIGO.Tags.prototype = {
	// tags properties
	_container: null,
	_limit: null,
	_limitReached: null,
	
	// tag properties
	_tags: null,
	_formName: null,
	_labelLimit: null,
	
	_tagSelected: null,
	_tagsOnClick: null,
	_tagsOnRemove: null,
	_disableRemove: null,
	
	/**
	 * sets the label limit for long label names
	 * @param {Integer} value
	 * @return {INTRIGO.Tags} instance of self
	 */
	setLabelLimit: function(value){
		this._labelLimit = value;
		return this;
	},
	
	/**
	 * sets the limit for allowed number of tags
	 * @param {Integer} value
	 * @return {INTRIGO.Tags} instance of self
	 */
	setLimit: function(value){
		this._limit = value;
		return this;
	},
	
	/**
	 * checks to see if limit has been reached
	 * @return Boolean
	 */
	isLimitReached: function(){
		if (this._limit != 0 && this._tags.length >= this._limit) {
			INTRIGO.addClassName(this._container, 'limit');
			this._limitReached = true;
		}
		else {
			INTRIGO.removeClassName(this._container, 'limit');
			this._limitReached = false;
		}
		
		return this._limitReached;
	},
	
	/**
	 * adds a tag
	 * @param {String|DOM} label: label displayed in tag
	 * @param {String} value [optional]: value outputed to post (by default: label)
	 * @param {Object} otherData [optional]: data stored with tag
	 * @return {INTRIGO.Tag} the tag newly inserted
	 */
	addTag: function(label, value, otherData){
		try {
			if (this.isLimitReached()) {
				return;
			}
			else {
				if (this._labelLimit && typeof(label) == 'string') 
					label = label.substring(0, this._labelLimit) + '...';
				
				var tag = new INTRIGO.Tag(label, otherData, this);
				
				if (value) 
					tag.setValue(value);
				else tag.setValue(label);
				
				tag.setName(this._formName);
				
				if (this._disableRemove)
					tag.disableRemove();
				
				if (this._tagsOnClick) 
					tag.onSelect(this._tagsOnClick);
				
				if (this._radio) {
					tag.radio();
					if (this._radio !== true && tag.getValue() == this._radio) {
						tag.select();
					}
				}
				
				this._tags[this._tags.length] = tag;
				
				this._container.appendChild(tag.getContainer());
				
				this.isLimitReached();
				
				return tag;
			}
		} 
		catch (e) {
			alert('INTRIGO.Tags.addTag error: ' + e.message);
		}
	},
	
	/**
	 * @return INTRIGO.Tag: tag inserted last
	 */
	getLastTag: function(){
		if (this._tags.length > 0) 
			return this._tags[this._tags.length - 1];
		else 			
			return false;
	},
	
	/**
	 * returns tag currently selected
	 * @param {INTRIGO.Tag} tag
	 */
	getSelected: function(){
		if (this._tagSelected) {
			return this._tagSelected;
		}
	},
	
	/**
	 * removes a tag
	 * @param {INTRIGO.Tag} tag
	 */
	removeTag: function(tag){
		if (tag) 
			tag.remove();
	},
	
	/**
	 * function called from INTRIGO.Tag upon remove
	 * @param {INTRIGO.Tag} tag
	 */
	tagRemoved: function(tag){
		var array = [];
		var tags = this._tags;
		var j = 0;
		
		for (var i = 0; i < tags.length; i++) {
			if (tags[i] != tag) {
				array[j] = tags[i];
				j++;
			}
		}
		
		// if custom remove function was set, call it
		if (this._tagsOnRemove) 
			this._tagsOnRemove(tag);
		
		this._tags = array;
		this.isLimitReached();
	},
	
	/**
	 * function called from INTRIGO.Tag upon select
	 * @param {INTRIGO.Tag} tag
	 */
	tagSelected: function(tag){
		// check to see if this tag is independent of others
		if (this.getSelected() == tag) 
			return;
		else if (this.getSelected()) {
			this.getSelected().deselect();
		}
		this._tagSelected = tag;
	},
	
	/**
	 * sets the onclick function for every tag added
	 * @param {Function} name
	 * @return {INTRIGO.Tags} instance of self
	 */
	onSelect: function(name){
		if (INTRIGO.isFunction(name)) {
			this._tagsOnClick = name;
		}
		return this;
	},
	
	/**
	 * sets the onremove function for every tag added
	 * @param {Function} name
	 * @return {INTRIGO.Tags} instance of self
	 */
	onRemove: function(name){
		if (INTRIGO.isFunction(name)) {
			this._tagsOnRemove = name;
		}
		return this;
	},
	
	/**
	 * disables remove for all tags added
	 * @return {INTRIGO.Tags} instance of self
	 */
	disableRemove: function() {
		this._disableRemove = true;
		return this;
	},
	
	/**
	 * sets the tag with radio emulation
	 * @param {String} defaultValue [optional]: which is selected on creation (only works with no custom onSelect)
	 * @return {INTRIGO.Tags} instance of self
	 */
	radio: function(defaultValue){
		this._radio = defaultValue || true;
		return this;
	},
	
	/**
	 * search a tag by value
	 * @return {INTRIGO.Tag|False}
	 */
	findByValue: function(value){
		if (value) {
			for (var i = 0; i < this._tags.length; i++) {
				if (this._tags[i].getValue() == value) {
					return this._tags[i];
				}
			}
		}
		return false;
	}
}

/**
 * @class Tag
 * @namespace INTRIGO
 * @requires INTRIGO
 * @param {DOM|String} label: the label to be displayed in the tag
 * @param {Object} data [optional]: whatever data you want to store with the tag
 * @param {INTRIGO.Tags} tagsRef [optional]: required when managed
 */
INTRIGO.Tag = function(label, data, tagsRef){
	try {
		var container = document.createElement('a');
		var span1 = document.createElement('span');
		var span2 = document.createElement('span');
		var span3 = document.createElement('span');
		var span4 = document.createElement('span');
		var nameElt = document.createElement('div');
		var hiddenInput = document.createElement('input');
		var removeElt = document.createElement('a');
		var self = this;
		
		if (typeof(label) == 'string') 
			nameElt.innerHTML = label;
		else nameElt.appendChild(label);
		
		nameElt.className += ' name';
		nameElt.onclick = function(event){
			INTRIGO.noBubble(event);
			self.select();
			return false;
		};
		hiddenInput.type = 'hidden';
		
		removeElt.href = '#';
		removeElt.innerHTML = 'x';
		removeElt.className = 'remove';
		removeElt.setAttribute('tabindex', '-1');
		removeElt.onclick = function(event){
			INTRIGO.noBubble(event);
			self.remove()
			return false;
		};
		
		clearBoth = document.createElement('div');
		clearBoth.style.fontSize = '0px';
		clearBoth.setAttribute('clear', 'both');
		clearBoth.style.lineHeight = '0px';
		
		container.className = 'tag';
		container.setAttribute('href', '#');
		container.setAttribute('tabindex', '-1');
		container.onclick = nameElt.onclick;
		
		span4.appendChild(hiddenInput);
		span4.appendChild(nameElt);
		span4.appendChild(removeElt);
		span4.appendChild(clearBoth);
		
		span3.appendChild(span4);
		span2.appendChild(span3);
		span1.appendChild(span2);
		
		container.appendChild(span1);
		
		if (tagsRef) 
			this._tagsRef = tagsRef;
		
		this._container = container;
		this._hiddenInput = hiddenInput;
		this._nameElt = nameElt;
		this._removeElt = removeElt;
		
		this._label = label;
		this._selected = false;
		
		this._data = data;
		
		return this;
	} 
	catch (e) {
		alert('INTRIGO.Tag error: ' + e.message);
	}
}

INTRIGO.Tag.prototype = {
	// tag properties
	_label: null,
	_selected: null,
	_checkbox: null,
	_radio: null,
	
	// DOM elements
	_container: null,
	_nameElt: null,
	_removeElt: null,
	_hiddenInput: null,
	
	// parent reference
	_tagsRef: null,
	
	// user defined data
	_data: null,
	_selectFunction: null,
	_deselectFunction: null,
	
	/**
	 * returns true if tag is selected
	 * @return {Boolean}
	 */
	isSelected: function(){
		return this._selected;
	},
	
	/**
	 * returns the tag container
	 * @return DOM element for the link
	 */
	getContainer: function(){
		return this._container;
	},
	
	/**
	 * returns the input box
	 * @return DOM element for the input box
	 */
	getInputElt: function(){
		return this._hiddenInput;
	},
	
	/**
	 * @return {String} the tag label
	 */
	getLabel: function(){
		return this._label;
	},
	
	/**
	 * @return {String} the hidden input value
	 */
	getValue: function(){
		return this.getInputElt().value;
	},
	
	/**
	 * @return {Object} the tag's associated data
	 */
	getData: function(){
		return this._data;
	},
	
	/**
	 * sets the name outputed in post by setting the "name" attrib of the hidden input
	 * @param {String} name
	 * @return {INTRIGO.Tag} instance of self
	 */
	setName: function(name){
		this.getInputElt().name = name;
		return this;
	},
	
	/**
	 * sets the hidden input value
	 * @param {String} value
	 * @return {INTRIGO.Tag} instance of self
	 */
	setValue: function(value){
		this.getInputElt().value = value;
		return this;
	},
	
	/**
	 * sets the attributes for the name element
	 * @param {Hash} attr
	 * @return {INTRIGO.Tag} instance of self
	 */
	setAttributes: function(attr){
		INTRIGO.setAttributes(this._nameElt, attr);
		return this;
	},
	
	/**
	 * sets a tag to be toggable
	 * @return {INTRIGO.Tag} instance of self
	 */
	radio: function(){
		this._radio = this.getInputElt().name;
		this.disableRemove();
		this.getInputElt().name = '';
		return this;
	},
	
	/**
	 * sets a tag to emulate a checkbox
	 * @param {Array} values [optional] : by default = the tag value ex. [1, 0] (where 1 is selected and 0 is not selected)
	 * @param {String} defaultValue [optional]: determine if tag is selected by default ex. 1 would select
	 * @return {INTRIGO.Tag} instance of self
	 */
	checkbox: function(values, defaultValue){
		var self = this;
		INTRIGO.addClassName(this.getContainer(), 'toggable');
		
		if (typeof(values) == 'object') {
			this._checkbox = values;
		}
		else {
			this._checkbox = [1, 0];
		}
		if (defaultValue == this._checkbox[0]) {
			this.select();
		}
		else {
			this.setValue(this._checkbox[1]);
		}
		
		this.disableRemove();
		
		return this;
	},
	
	/**
	 * sets the onclick functionality when a tag is clicked on
	 * @param {Function} funcName
	 * @return {INTRIGO.Tag} instance of self
	 */
	onSelect: function(funcName){
		if (INTRIGO.isFunction(funcName)) {
			this._selectFunction = funcName;
		}
		return this;
	},
	
	/**
	 * sets the function to be called when a tag is deselected
	 * @param {Function} funcName
	 * @return {INTRIGO.Tag} instance of self
	 */
	onDeselect: function(funcName){
		if (INTRIGO.isFunction(funcName)) {
			this._deselectFunction = funcName;
		}
		return this;
	},
	
	/**
	 * disables the ability for a tag to be selected
	 * this doesn't mean the onclick feature is disabled it's only for display purposes
	 * @return {INTRIGO.Tag} instance of self
	 */
	disableSelect: function(){
		this._disableSelect = true;
		return this;
	},
	
	/**
	 * selects a tag
	 * if a parent reference is present, calls tagSelected() on parent
	 * @return {INTRIGO.Tag} instance of self
	 */
	select: function(){
		if (self._disableSelect) {
			return this;
		}
		// checkbox emulation
		else if (this._checkbox && this.isSelected()) {
			this.deselect();
			return this; // must return so onSelect isn't called again
		}
		else {
			if (this._checkbox) {
				this.setValue(this._checkbox[0]);
			}
			else if (this._radio) {
				this.getInputElt().name = this._radio;
			}
			
			this._selected = true;
			INTRIGO.addClassName(this._container, 'selected');
			
			if (this._tagsRef) {
				this._tagsRef.tagSelected(this);
			}
		}
		
		// call custom select function
		if (this._selectFunction) {
			this._selectFunction(this);
		}
		
		return this;
	},
	
	/**
	 * deselects a tag
	 * calls the deselect function if it exists
	 * @return {INTRIGO.Tag} instance of self
	 */
	deselect: function(){
		if (this._deselectFunction) 
			this._deselectFunction(this);
		this._selected = false;
		INTRIGO.removeClassName(this._container, 'selected');
		
		if (this._checkbox) {
			this.setValue(this._checkbox[1]);
		}
		else if (this._radio) {
			this.getInputElt().name = '';
		}
		
		return this;
	},
	
	/**
	 * removes a tag
	 * if a parent reference is present, calls tagRemoved() on parent
	 */
	remove: function(){
		this._container.parentNode.removeChild(this._container);
		if (this._tagsRef) 
			this._tagsRef.tagRemoved(this);
	},
	
	/**
	 * disables the remove feature
	 * @return {INTRIGO.Tag} instance of self
	 */
	disableRemove: function(){
		this._removeElt.style.display = 'none';
		return this;
	}
};
