/**
 * @author tfuhlroth
 * @version 0.1
 */

/*
 * TODO:
 * - internalValidate: maybe rewrite how the tags, elements and values are stored?
 */

var Tags = new Class({
	
	Implements: [Options, Events],
	
	options: {
		tagSeparators: ['enter'],
		valueSeparator: ';',
		tagName: 'span',
		className: 'tag',
		caseSensitive: false,
		externalValidator: $lambda(true),
		mapValue: $arguments(0),
		mapLabel: $arguments(0)
	},
	
	initialize: function (element, target, options) {
		this.element = $(element);
		this.target = $(target);
		this.setOptions(options);
		
		this.keys = [];
		this.tags = new Hash();
		this.build();
	},
	
	build: function () {
		// unfortunately IE does not like changing the type-attribute on the fly, so we cannot do this the easy way: this.values = this.element.set('type', 'hidden');
		this.values = this.element.clone(false, true).cloneEvents(this.element).set('type', 'hidden').replaces(this.element);
		this.values.erase('id');
		this.element.set('name', this.values.get('name') + '_input').inject(this.values, 'after');
		
		// preload tags from input-value
		this.addTags(this.element.get('value').split(this.options.valueSeparator).map(this.options.mapLabel, this));
		this.element.set('value', '');
		
		this.element.addEvents({
			'keypress': this.blockFormSubmit.bind(this), // use keypress instead of keydown to stop the event also in opera
			'keyup': this.checkInput.bind(this)
		});
	},
		
	blockFormSubmit: function (event) {
		if (this.options.tagSeparators.contains('enter') && event.key == 'enter') {
			event.stop();
		}
	},
	
	checkInput: function (event) {
		var value = this.element.get('value').trim();
		var lastChar = Array.pop(value);
		
		var separatorString;
		var isSeparator = this.options.tagSeparators.some(function (separator) {
			separatorString = (separator.length > 1) ? event.key : lastChar;
			return (separator == separatorString);
		}, this);
				
		if (isSeparator) {
			if (separatorString.length == 1) value = value.substring(0, value.length - 1);
			if (this.isValid(value)) {
				this.fireEvent('success', [value, this.element]);
				this.addTag(value);
				this.element.set('value', '');
			} else {
				this.fireEvent('failure', [value, this.element]);
			}
		}
		
	},
	
	internalValidator: function (tag) {
		var key = this.getKey(tag);
		var tagValue = this.options.mapValue(tag);
		var hasValue = this.tags.some(function (tagData) {
			return (tagData.value == tagValue);
		}, this);
		return (key.length && !this.keys.contains(key) && !hasValue);
	},
	
	isValid: function (tag) {
		return (this.internalValidator(tag) && this.options.externalValidator(tag));
	},
	
	getKey: function (tag) {
		return (this.options.caseSensitive) ? tag : tag.toLowerCase();
	},
	
	addTag: function (tag) {
		if (this.isValid(tag)) {
			
			var key = this.getKey(tag);
			
			// add tag to internal array & hash
			this.keys.push(key);
			this.tags.set(key, {
				element: new Element(this.options.tagName, {
					'text': tag,
					'class': this.options.className,
					'events': {
						'click': this.removeTag.pass(key, this)
					}
				}),
				value: this.options.mapValue(tag)
			});
						
			// inject tag-element
			this.tags.get(key).element.inject(this.target);
			
			// update values
			this.updateValues();
			
			this.fireEvent('add');
		}
	},
	
	removeTag: function (tag) {
		var key = this.getKey(tag);
		if (this.keys.contains(key)) {
			
			// destroy tag-element
			this.tags.get(key).element.destroy();
			
			// remove tag from internal array & hash
			this.keys.erase(key);
			this.tags.erase(key);
			
			// update values
			this.updateValues();
			
			this.fireEvent('remove');
		}
	},
	
	addTags: function () {
		$A(arguments).flatten().forEach(this.addTag, this);
	},
	
	removeTags: function () {
		var tags = (arguments.length) ? $A(arguments).flatten() : $A(this.tags);
		tags.forEach(this.removeTag, this);
	},
	
	updateValues: function () {
		var value = '';
		this.tags.forEach(function (tag) {
			value += this.options.valueSeparator + tag.value;
		}, this);
		this.values.set('value', value.substring(1));
	}
	
});
