/*
Script: TextboxList.js
	Displays a textbox as a combination of boxes an inputs (eg: facebook tokenizer)

	Authors:
		Guillermo Rauch
		
	Note:
		TextboxList is not priceless for commercial use. See <http://devthought.com/projects/mootools/textboxlist/>. 
		Purchase to remove this message.
*/

var TextboxList = new Class({
          
  Implements: [Options, Events],

  plugins: [],

  options: {/*
    onFocus: $empty,
    onBlur: $empty,
    onBitFocus: $empty,
    onBitBlur: $empty,
    onBitAdd: $empty,
    onBitRemove: $empty,
    onBitBoxFocus: $empty,
    onBitBoxBlur: $empty,
    onBitBoxAdd: $empty,
    onBitBoxRemove: $empty,
    onBitEditableFocus: $empty,
    onBitEditableBlue: $empty,
    onBitEditableAdd: $empty,
    onBitEditableRemove: $empty,*/
    prefix: 'textboxlist',
    max: null,
		unique: false,
		uniqueInsensitive: true,
    endEditableBit: true,
		startEditableBit: true,
		hideEditableBits: true,
    inBetweenEditableBits: true,
		keys: {previous: Event.Keys.left, next: Event.Keys.right},
		bitsOptions: {editable: {}, box: {}},
    plugins: {},
		check: function(s){ return s.clean().replace(/,/g, '') != ''; },
		encode: function(o){ 
			return o.map(function(v){				
				v = ($chk(v[0]) ? v[0] : v[1]);
				return $chk(v) ? v : null;
			}).clean().join(','); 
		},
		decode: function(o){ return o.split(','); }
  },
  
  initialize: function(element, options){
		this.setOptions(options);		
		this.original = $(element).setStyle('display', 'none').set('autocomplete', 'off').addEvent('focus', this.focusLast.bind(this));
    this.container = new Element('div', {'class': this.options.prefix}).inject(element, 'after');
		this.container.addEvent('click', function(e){ 
			if ((e.target == this.list || e.target == this.container) && (!this.focused || $(this.current) != this.list.getLast())) this.focusLast(); 			
		}.bind(this));
    this.list = new Element('ul', {'class': this.options.prefix + '-bits'}).inject(this.container);		
		for (var name in this.options.plugins) this.enablePlugin(name, this.options.plugins[name]);		
		['check', 'encode', 'decode'].each(function(i){ this.options[i] = this.options[i].bind(this); }, this);
		this.afterInit();
  },

	enablePlugin: function(name, options){
		this.plugins[name] = new TextboxList[name.camelCase().capitalize()](this, options);
	},
	
	afterInit: function(){
		if (this.options.unique) this.index = [];
		if (this.options.endEditableBit) this.create('editable', null, {tabIndex: this.original.tabIndex}).inject(this.list);
		var update = this.update.bind(this);
		this.addEvent('bitAdd', update, true).addEvent('bitRemove', update, true);
		document.addEvents({
			click: function(e){
				if (!this.focused) return;
				if (e.target.className.contains(this.options.prefix)){
					if (e.target == this.container) return;
					var parent = e.target.getParent('.' + this.options.prefix);
					if (parent == this.container) return;
				}
				this.blur();
			}.bind(this),
			keydown: function(ev){
				if (!this.focused || !this.current) return;
				var caret = this.current.is('editable') ? this.current.getCaret() : null;
				var value = this.current.getValue()[1];
				var special = ['shift', 'alt', 'meta', 'ctrl'].some(function(e){ return ev[e]; });
				var custom = special || (this.current.is('editable') && this.current.isSelected());
				if ($chk(this.options.max) && this.list.getChildren('.' + this.options.prefix + '-bit-box').length + 1 > this.options.max) 
				{
					this.plugins['autocomplete'].options.disable = true;
					ev.stop();
				}else{
					this.plugins['autocomplete'].options.disable = false;
				}
				switch (ev.code){
					case Event.Keys.backspace:
						if (this.current.is('box')){ 
							ev.stop();
							return this.current.remove(); 
						}
					case this.options.keys.previous:
						if (this.current.is('box') || ((caret == 0 || !value.length) && !custom)){
							ev.stop();
							this.focusRelative('previous');
						}
						break;
					case Event.Keys['delete']:
						if (this.current.is('box')){ 
							ev.stop();
							return this.current.remove(); 
						}
					case this.options.keys.next: 
						if (this.current.is('box') || (caret == value.length && !custom)){
							ev.stop();
							this.focusRelative('next');
						}
				}
			}.bind(this)
		});		
		this.setValues(this.options.decode(this.original.get('value')));
	},
	
	create: function(klass, value, options){
		if (klass == 'box'){
			if ((!value[0] && !value[1]) || ($chk(value[1]) && !this.options.check(value[1]))) return false;
			if ($chk(this.options.max) && this.list.getChildren('.' + this.options.prefix + '-bit-box').length + 1 > this.options.max) 
			{
				
				window.alert("Sorry,Over the maximum limit permit for this field. (Max = "+ this.options.max +")");
				return false;
			}
			if (this.options.unique && this.index.contains(this.uniqueValue(value))) return false;		
		}		
		return new TextboxListBit[klass.capitalize()](value, this, $merge(this.options.bitsOptions[klass], options));		
	},
	
	uniqueValue: function(value){
		return $chk(value[0]) ? value[0] : (this.options.uniqueInsensitive ? value[1].toLowerCase() : value[1]);
	},
	
	onFocus: function(bit){
		if (this.current) this.current.blur();
		$clear(this.blurtimer);
		this.current = bit;
		this.container.addClass(this.options.prefix + '-focus');
		if (!this.focused){
			this.focused = true;
			this.fireEvent('focus', bit);
		}
	},
	
	onBlur: function(bit, all){
		this.current = null;
		this.container.removeClass(this.options.prefix + '-focus');		
		this.blurtimer = this.blur.delay(all ? 0 : 200, this);
	},
	
	onAdd: function(bit){
		if (this.options.unique && bit.is('box')) this.index.push(this.uniqueValue(bit.value));
		if (bit.is('box')){
			var prior = this.getBit($(bit).getPrevious());
			if ((prior && prior.is('box') && this.options.inBetweenEditableBits) || (!prior && this.options.startEditableBit)){				
				var b = this.create('editable').inject(prior || this.list, prior ? 'after' : 'top');
				if (this.options.hideEditableBits) b.hide();
			}
		}
	},
	
	onRemove: function(bit){
		if (!this.focused) return;
		if (this.options.unique && bit.is('box')) this.index.erase(this.uniqueValue(bit.value));
		var prior = this.getBit($(bit).getPrevious());
		if (prior && prior.is('editable')) prior.remove();
		this.focusRelative('next', bit);
		this.plugins['autocomplete'].options.disable = false;
	},
	
	focusRelative: function(dir, to){
		var b = this.getBit($($pick(to, this.current))['get' + dir.capitalize()]());
		if (b) b.focus();
		return this; 
	},
	
	focusLast: function(){		
		var lastElement = this.list.getLast();
		if (lastElement) this.getBit(lastElement).focus();
		return this;
	},
	
	blur: function(){		
		if (! this.focused) return this;
		if (this.current) this.current.blur();
		this.focused = false;
		return this.fireEvent('blur');
	},
	
	add: function(plain, id, html, afterEl){
		var b = this.create('box', [id, plain, html]);
		if (b){
			if (!afterEl) afterEl = this.list.getLast('.' + this.options.prefix + '-bit-box');
			b.inject(afterEl || this.list, afterEl ? 'after' : 'top');
		}
		return this;
	},
	
	getBit: function(obj){
		return ($type(obj) == 'element') ? obj.retrieve('textboxlist:bit') : obj;
	},
	
	getValues: function(){
		return this.list.getChildren().map(function(el){
			var bit = this.getBit(el);
			if (bit.is('editable')) return null;
			return bit.getValue();
		}, this).clean();
	},
	
	setValues: function(values){
		if (!values) return;
		values.each(function(v){
			if (v) this.add.apply(this, $type(v) == 'array' ? [v[1], v[0], v[2]] : [v]);
		}, this);		
	},
	
	update: function(){
		this.original.set('value', this.options.encode(this.getValues()));
	}
  
});

var TextboxListBit = new Class({
  
  Implements: Options,  

  initialize: function(value, textboxlist, options){
		this.name = this.type.capitalize();
		this.value = value;
    this.textboxlist = textboxlist;
    this.setOptions(options);            
    this.prefix = this.textboxlist.options.prefix + '-bit';
		this.typeprefix = this.prefix + '-' + this.type;
    this.bit = new Element('li').addClass(this.prefix).addClass(this.typeprefix).store('textboxlist:bit', this);
		this.bit.addEvents({
			mouseenter: function(){ 
				this.bit.addClass(this.prefix + '-hover').addClass(this.typeprefix + '-hover'); 
			}.bind(this),
			mouseleave: function(){
				this.bit.removeClass(this.prefix + '-hover').removeClass(this.typeprefix + '-hover'); 
			}.bind(this)
		});
  },

	inject: function(element, where){
		this.bit.inject(element, where);	
		this.textboxlist.onAdd(this);	
		return this.fireBitEvent('add');
	},

	focus: function(){
		if (this.focused) return this;
		this.show();
		this.focused = true;
		this.textboxlist.onFocus(this);
		this.bit.addClass(this.prefix + '-focus').addClass(this.prefix + '-' + this.type + '-focus');
		return this.fireBitEvent('focus');
	},

	blur: function(){
		if (!this.focused) return this;
		this.focused = false;
		this.textboxlist.onBlur(this);
		this.bit.removeClass(this.prefix + '-focus').removeClass(this.prefix + '-' + this.type + '-focus');
		return this.fireBitEvent('blur');
	},
	
	remove: function(){
		this.blur();		
		this.textboxlist.onRemove(this);
		this.bit.destroy();
		return this.fireBitEvent('remove');
	},
	
	show: function(){
		this.bit.setStyle('display', 'block');
		return this;
	},
	
	hide: function(){
		this.bit.setStyle('display', 'none');
		return this;
	},
	
	fireBitEvent: function(type){
		type = type.capitalize();
		this.textboxlist.fireEvent('bit' + type, this).fireEvent('bit' + this.name + type, this);
		return this;
	},
	
  is: function(t){
    return this.type == t;
  },

	setValue: function(v){
		this.value = v;
		return this;
	},

	getValue: function(){
		return this.value;
	},

	toElement: function(){
		return this.bit;
	}
  
});

TextboxListBit.Editable = new Class({
  
	Extends: TextboxListBit,

  options: {
		tabIndex: null,
		growing: true,
		growingOptions: {},
		stopEnter: true,
		addOnBlur: false,
		addKeys: Event.Keys.enter
  },
  
  type: 'editable',
  
  initialize: function(value, textboxlist, options){
    this.parent(value, textboxlist, options);
    this.element = new Element('input', {type: 'text', 'class': this.typeprefix + '-input', autocomplete: 'off', value: this.value ? this.value[1] : ''}).inject(this.bit);		
		if ($chk(this.options.tabIndex)) this.element.tabIndex = this.options.tabIndex;
		if (this.options.growing) new GrowingInput(this.element, this.options.growingOptions);		
		this.element.addEvents({
			focus: function(){ this.focus(true); }.bind(this),
			blur: function(){
				this.blur(true);
				if (this.options.addOnBlur) this.toBox(); 
			}.bind(this)
		});
		if (this.options.addKeys || this.options.stopEnter){
			this.element.addEvent('keydown', function(ev){
				if (!this.focused) return;
				if (this.options.stopEnter && ev.code === Event.Keys.enter) ev.stop();
				if ($splat(this.options.addKeys).contains(ev.code)){
					ev.stop();
					this.toBox();
				}
			}.bind(this));
		}
  },

	hide: function(){
		this.parent();
		this.hidden = true;
		return this;
	},
  
	focus: function(noReal){
		this.parent();
		if (!noReal) this.element.focus();	
		return this;
	},
	
	blur: function(noReal){
		this.parent();
		if (!noReal) this.element.blur();
		if (this.hidden && !this.element.value.length) this.hide();
		return this;
	},
	
	getCaret: function(){
		if (this.element.createTextRange){
	    var r = document.selection.createRange().duplicate();		
	  	r.moveEnd('character', this.element.value.length);
	  	if (r.text === '') return this.element.value.length;
	  	return this.element.value.lastIndexOf(r.text);
	  } else return this.element.selectionStart;
	},
	
	getCaretEnd: function(){
		if (this.element.createTextRange){
			var r = document.selection.createRange().duplicate();
			r.moveStart('character', -this.element.value.length);
			return r.text.length;
		} else return this.element.selectionEnd;
	},
	
	isSelected: function(){
		return this.focused && (this.getCaret() !== this.getCaretEnd());
	},

	setValue: function(val){
		this.element.value = $chk(val[0]) ? val[0] : val[1];
		if (this.options.growing) this.element.retrieve('growing').resize();
		return this;
	},

	getValue: function(){
		return [null, this.element.value, null];
	},
	
	toBox: function(){
		var value = this.getValue();				
		var b = this.textboxlist.create('box', value);
		if (b){
			b.inject(this.bit, 'before');
			this.setValue([null, '', null])
			return b;
		}
		return null;
	}
	
});

TextboxListBit.Box = new Class({
  
	Extends: TextboxListBit,

  options: {
		deleteButton: true
  },
  
  type: 'box',
  
  initialize: function(value, textboxlist, options){
    this.parent(value, textboxlist, options);
		this.bit.set('html', $chk(this.value[2]) ? this.value[2] : this.value[1]);
		this.bit.addEvent('click', this.focus.bind(this));
		if (this.options.deleteButton){
			this.bit.addClass(this.typeprefix + '-deletable');
			this.close = new Element('a', {href: '#', 'class': this.typeprefix + '-deletebutton', events: {click: this.remove.bind(this)}}).inject(this.bit);
		}
		this.bit.getChildren().addEvent('click', function(e){ e.stop(); });
  }
  
});