/**
 * LinkedSelectors
 * @author Daniel Ramirez <daniel@ensitech.com>
 * @copyrights Ensitech S.C. 2008
 * @requires Prototype JS Library >= 1.6.0.2 
 * @class LinkedSelectors
 * 
 * Crea la arquitectura de front-end para poder tener combos con valores enlazados esto es triggervalue->filteredvalues
 * ejemplo: Pais: Mexico -> Estados: Nuevo Leon, Coahuila, Tamaulipas, Campeche, Puebla.
 *
 * Usage:
 * <script type='text/javascript'>
 *    document.observe('dom:loaded',function(){
 *    		var iwt_selectors = new LinkedSelectors();
 *    		iwt_selectors.addSelect($('estado'));
 *    		iwt_selectors.addSelect($('municipio'));
 *    		iwt_selectors.addTrigger('estado','municipio','nuevo leon',['monterrey','guadalupe','san nicolas',...]);
 *    });
 * </script>
 */
 
LinkedSelectors = Class.create({
	//-- Atributos
	_selects: 	[], 		//-- arreglo de elementos general
	_triggers: 	[], 		//-- diccionario de ids con el que se conoce cuales se activan con un combo fuente
	_values:	[], 		//-- valores originales de todos los combos que componen el componente de ligas encadenadas
	_trigger_order: [], 	//-- orden en que se van filtrando.
	_trigger_by:	[],
	_last_values:	[],
	_affected:		[],
	_defaults:		[],
	_debug:			false,
	_fetched:		[],
	_limitedvalues: [],
	_onEmptySet:	null,
	_onNonEmptySet: null,
	
	default_alias: "-- Seleccione --",
	attribute_alias: [],
	maxwidth:	'150px', 	//maxwidth de los selects.
	
	initialize: function(properties, attribute_alias) {
	   	this._selects = 	[];
		this._triggers= 	new Hash();//[];
		this._values  =	[]; 
		this._trigger_order	= [];
		this._trigger_by	= [];
		this._fetched	=	new Hash();//para saber de quienes se ha sacado un ajax request.
		this._limitedvalues = new Hash();
		this._defaults	= [];
		this._affected	= [];
		this._last_values = [];
		this._onEmptySet = function(){};
		this._onNonEmptySet = function(){};
		
		if(properties)
		{	
			this.default_alias 	= (properties.default_alias !== undefined)?properties.default_alias:"-- Seleccione --";
			this.maxwidth	 	= (properties.maxwidth !== undefined)?properties.maxwidth:"150px";
		}
		
		if(attribute_alias) {
			this.setAttributeAlias(attribute_alias);
		}
	},
	
	/**
	 * Agrega un default, es necesario que se realicen despues de la carga de triggers
	 * para poder que se obtenga bajo cual esta, y poder seleccionarlo.
	 * @access	public
	 * @param	string	id_attribute
	 * @param	string	value
	 */
	setDefault: function(id_attribute, value){
		this._defaults[id_attribute]	=	value;
		
		try {
			//-- como es relacion 1-1 podemos saber quien es el que lo triggerea.
			var id_source	= this._trigger_by[id_attribute];
			var triggers	= [];
						
			if(id_source === undefined) { return; }
						
						//alert('Setting default: '+id_attribute+' = '+value + ', source = ' + id_source);

						this._triggers.get(id_source).each(function(trigger_value){
							for(var j = 0; j < trigger_value.value.length; j++) {
								if(trigger_value.value[j].get('target') == id_attribute){
									var options = trigger_value.value[j].get('values');
									
									var inValues	= options.find(function(x) { return (x[0] == value); } );
									
									if(inValues === undefined) {
										continue;
									}
									
									//-- Agregar opcion de informacion
									var alias	=	this.default_alias;
									this.clearSelect(this._selects[id_attribute]);
									this.addOption(this._selects[id_attribute], alias, "");
									
									//-- Agregar opciones 
									options.each(function(option){
										this.addOption(this._selects[id_attribute], option[1], option[0]);
									}.bind(this));
									
									//-- seleccionar el default.
									this._selectDefault(id_attribute);
									
									//-- Ahora, veremos si el default de esta opcion acciona otro, para dejarle sus valores abiertos.
									//alert(this._triggers[id_attribute][value].length);
									if(this._triggers.get(id_attribute).get(value) !== undefined) {
										this._triggers.get(id_attribute).get(value).each(
											function(t){
												var target 	= 	t.get('target');//target;
												var values	=	t.get('values');//values;
												
												this.clearSelect(this._selects[target]);
												this.addOption(this._selects[target], this.default_alias, "");
												
												//-- Agregar opciones 
												values.each(function(option){
													this.addOption(this._selects[target], option[1], option[0]);
												}.bind(this));
											}.bind(this)
										);
									} 
									break;
								}	
							}
						}.bind(this));
						
					} catch(e){ }
				},
			
	/**
	 * Selecciona un valor del combo
	 * @access 	private
	 * @param	string	id_attribute
	 */
	_selectDefault: function(id_attribute) {
			var _default = "";
			try { _default	= this._defaults[id_attribute]; } catch(e) { _default = "";}
			
			for(var i = 0; i < this._selects[id_attribute].options.length; i++) {
				var opt_value	=	this._selects[id_attribute].options[i].value;
				if(opt_value == _default) {
					this._selects[id_attribute].selectedIndex	=	i;
					if(this._selects[id_attribute]._change){
						this._selects[id_attribute]._change();
					}
					break;
				}
			}
			 
	},
				
	/**
	 * Guarda los valores/alias de los atributos en una variable para poder usarlos 
	 * como leyenda cuando un combo no haya sido activado
	 * @access 	public
	 * @param 	array	Arreglo de aliases, formato [value, alias]
	 */
	setAttributeAlias: function(aliases) {
		for(var i = 0; i < aliases.length; i++) {
			var alias = aliases[i][1];
			var value = aliases[i][0];
			this.attribute_alias[value] = alias;
		}
	},
	
	/**
	 * Agrega un select al componente:
	 * 	1. lo guarda en un arreglo de selects para mantenerlo disponible y saber cuales son los que forman parte del componente
	 *  2. Guarda los valores que tenia inicialmente, para en caso de necesitarlos volver a resetear el combo como estaba.
	 *  
	 *  @param 	HTMLSelectElement	element
	 *  @return boolean				Regresa si se pudo agregar correctamente o no
	 *  @access public
	 */
	addSelect:  function(element){
		if(element === undefined || element === null) return false;
		
		var total 	= element.options?element.options.length:0;
		var _total 	= 0;
		var valores = [];
		
		//-- Evitar agregar de nuevo el select.
		if(this._selects[element.getAttribute('id')] ==! undefined 
			&& this._values[element.getAttribute('id')] ==! undefined) { return true; }
		
		
		for(var i = 0; i < total; i++){//-- just in case omitimos los valores vacios y contamos si realmente tiene opciones [para evitar opciones como -- Seleccione -- etc..
			if(element.options[i].value !== ""){
				_total++;
			}
			//-- Guardar de backup la opcion
			valores[element.options[i].value] = [element.options[i].value, element.options[i].text];
		}
		if(_total === 0){
			//alert('El atributo: ' + element.getAttribute('id') + ' no tiene valores, no fue agregado');
			//return false;
		}

		this._selects[element.getAttribute('id')] = element;
		
		//-- Guardamos en los valores originales que tenian.
		this._values[element.getAttribute('id')] = valores;

		this._last_values[element.getAttribute('id')] = valores;
		
		this._selects[element.getAttribute('id')].setStyle({maxWidth:this.maxwidth,overflow:'hidden'});
		
		return true;
	},
	
	/**
	 * Agrega los valores para un select por medio de un request de ajax.
	 * @access	public
	 * @param	HTMLSelectElement	
	 * @param	string				URL donde se le mandara el request de los elementos
	 * @param	params				Parametros adicionales para el request.
	 */
	addSelectByRequest: function(element, src, params){
		this.addSelect(element);
		this.element = element; //tmp
		var send = new Ajax.Request(src, {
			method: 'POST', 
			asynchronous: false,
			parameters: params,
			onComplete: this._addselectresponse.bind(this)
		});
	},
	
	/**
	 * Interpreta la respuesta del request de valores para el select.
	 * @access private
	 */
	_addselectresponse: function(request){
		var jsonText = request.responseText.evalJSON(); 
		var values = jsonText;
		element = this.element;
		
		this._values[element.id] = values;	
					
		values.each(function(val){
				this.addOption(element, val[1], val[0]); ///-- Agregar los valores.
		}.bind(this));
	},
	
	/**
	 * Indica los unicos valores que podra tomar un combo destino
	 * 
	 * @param	string	source
	 * @param	string	target
	 * @param	string	src_value
	 * @param	string	target_values
	 */
	limitValues: function(source, target, src_value, target_values)
	{
		this._limitedvalues.set(source+"."+target+"."+src_value,target_values);
	},
	
	/**
	 * Establece un chain entre dos combos en base a un request.
	 * 
	 * @param	string	source
	 * @param	string	target
	 * @param	string	url
	 * @param	function	params		funcion que regresa los parametros a enviar por el caso donde sean dinamicos.
	 * @param	function	id			funcion generadora del id unico para cada valor del combo elegido. debe regresar un string; sirve para saber cuales se han agregado  cuales no.
	 */
	setOnchangeSourceRequest: function(source, target, url, params, id)
	{
		var fx_req = function(){
			var id_fetch = Object.isFunction(id)?id():id;
			_params	 = Object.isFunction(params)?params():params;
			
			if(_params === false) { //-- en caso de que algun parametro falte etc, cancelar req.
				return;
			}
			
			_params.source = source;
			_params.target = target;
			
			var fetched	=	this._fetched.get(source);
			if(fetched == null){
				fetched = new Hash();
			}
			
			if(fetched.get(id_fetch) === undefined){
				fetched.set(id_fetch, true);// 
				
				this.addTriggersByRequest(url, 
										_params,
										{onSuccess:function(response){  
														this._fetched.set(source, fetched); 
														this.trigger($(source)); 
														var _default = "";
														
														try { _default	= this._defaults[target]; } catch(ee) { _default = ""; }
														if(_default != "") {
															this._selectDefault(target);
															
															if($(target).request) {  
																$(target).request(); 
															}
														}
													}.bind(this)
										});
			}
		}.bind(this);
		
		$(source).observe('change', fx_req);
		$(source).request = fx_req;
		if($F(source) != "") 
		{
			fx_req();
		}
		
		return this;
	},
	
	/**
	 * Agrega las relaciones al componente por medio de un request de Ajax
	 * @access	public
	 * @param	string	src		Liga donde se traera un JSON-string 
	 * @param	string	params	Parametros adicionales al request
	 * 
	 * El formato del string de retorno debe de ser un arreglo de elementos en formato JSON con los 
	 * siguientes elementos
	 * {source:'', target:'', trigger_value:'', values:[[value,alias],[...]]}
	 * 
	 * Ejemplo:
	 * [{source: "pais", target: "estados", trigger: "MX", values: [["2","Aguascalientes"]]},
	 * 	{source...}]
	 */
	addTriggersByRequest: function(src, params, fx){//-- trae todos los triggers de manera sucia
		var onSuc = (fx && fx.onSuccess)?fx.onSuccess:function(){};
		
		var send = new Ajax.Request(src, {
			method: 'POST',
			parameters: params,
			asynchronous: true,
			onCreate: function(){ if(params.loader){ $(params.loader).show(); }},
			onComplete: function(){ if(params.loader){ $(params.loader).hide(); }},
			onSuccess: function(request){
							var jsonText = request.responseText;
							var values = jsonText.evalJSON();
							var _params= params;
							
							if(values.length === 0) {
								try { this._onEmptySet(_params.source); } catch(oes) { alert(oes.message); }
							}
							values.each(function(t){
											var source = t.source;
											var target = t.target;
											var trigger_value = t.trigger;
											var values = t.values;
											var result = false;
											if(source == "" && _params.source) {
												source = _params.source;
											}
											if(target == "" && _params.target) {
												target = _params.target;
											}
											
											result = this.addTrigger(source, target, trigger_value, values);
	
											if(!result && this._debug){
												alert('No se pudo agregar trigger para:'+ source+' - '+target+', con el trigger:'+trigger_value);
											}
											
										}.bind(this));
							if(values.length > 0) {
								onSuc(request);
							}
						}.bind(this)
		});	
		
		return this;
	},
	
	/**
	 * Es el binding del evento, este es accionado cuando un Select cambia de valor. 
	 * Es agregado por el metodo addTrigger();
	 * 
	 * @access	private/public
	 * @params	Event			event
	 * @params 	LinkedLists		object	Se manda a trabes del Element.bind() del prototype para mantener presente el objeto.
	 */
	trigger: function(event){
					//TODO cambiar este modelo de agregar eventos uno por uno en el trigger, y cargar un solo evento y manejar todos dentro del onchange.
			var args = $A(arguments);
			var object, src_attribute;
			
			//-- Hacemos publica la funcion de manera que se pueda hacer el trigger de la misma mediante ls.trigger(htmlselectelement);
			if(args.length == 2) {
				object			= args[1];
				src_attribute 	= Event.element(event);
			} else if(args.length == 1){
				object			= this;
				src_attribute	= event;
			}
			
			var trigger_value	= src_attribute.value;
			var id_source		= src_attribute.getAttribute('id');
			
			//-- Agregar el evento al chaining
			object._trigger_order.push({src:src_attribute.getAttribute('id'), 
										selected: src_attribute.options
													?src_attribute.options.selectedIndex
													:0,
										trigger: trigger_value});
			
			//-- Si el atributo que se acaba de cambiar ya se habia cambiado antes.. 
			//-- resetear todos los combos que no se han triggereado antes de el
			//-- y limpiar el cache de triggers.
			object.resetUntil(src_attribute.getAttribute('id')); 
			
			if(object._triggers.get(id_source).get(trigger_value) === undefined //object._triggers[id_source][trigger_value] === undefined
					|| object._triggers.get(id_source).get(trigger_value) === null){ //object._triggers[id_source][trigger_value] === null){
				object._trigger_order.pop();//-- eliminar el ultimo elemento de trigger porque no es valido
				//-- Indicar en los combos que afecta el elemento que no hay resultados.
				for(it in object._trigger_by){
					var is = object._trigger_by[it];
					if(typeof is === "string" && is == id_source && trigger_value !== "") {
						object.addOption(object._selects[it], "No se encontraron valores", "","font-size:10px; color:red;text-align:center;");
					}
				}
				try {
					object._onEmptySet(id_source, trigger_value);
				} catch(oes){}
				
				return;
			}
			
			try {
				object._onNonEmptySet(id_source, trigger_value);
			} catch(ones){}
			
			//-- Copiar nuevos atributos, para cada atributo que invoca el valor del source.
			var tlen = object._triggers.get(id_source).get(trigger_value).length;//object._triggers[id_source][trigger_value].length;
		
			
			for(var t = 0; t < tlen; t++ ){
				var t_valpair = object._triggers.get(id_source).get(trigger_value)[t]; // object._triggers[id_source][trigger_value][t];
				var target = t_valpair.get('target'); //t_valpair.target;
				var values = t_valpair.get('values'); //t_valpair.values;
				
				//-- Verificar que no exista uno en direccion inversa ya triggereado, si lo hizo, entonces se ignora para no interferir con filtros posteriores.
				if(object.hasBeenAffectedBy(id_source, target)){
					continue;
				}
				
				var first = false;
				if(object._trigger_by[target] === undefined) {
					object._trigger_by[target] = [];
					first = true;
				}
				
				//-- Borrar valores anteriores
				object.clearSelect(object._selects[target]);
				
				//-- Insertar nuevos.
				var selector = object._selects[target];
				var innerHtml = "<option value=''>" + object.default_alias + "</option>";
				
				var id_pv = id_source+"."+target+"."+trigger_value;
				var possible_values = object._limitedvalues.get(id_pv) || [];
				
				//-- Cortamos los resultados si existen limites
				if(possible_values.length > 0) {
					aux_values = [];
					for(var i = 0; i < values.length; i++){
						var value = values[i];
						if(possible_values.length > 0 && possible_values.indexOf(value[0]) === -1){
							continue;
						}
						aux_values.push(value);
					}
					values = aux_values;
				}
				
				if(values.length == 1) {
					innerHtml = "";
				}
				
				for(var i = 0; i < values.length; i++){
					var value = values[i];
					
					//HERE
					if(Object.isArray(value)) {
						innerHtml += "<option value='"+value[0] +"'>"+value[1]+"</option>";
					}
				}
				
				//Actualizar el elemento al final
				selector.update(innerHtml);
				
				if(selector._change){
					selector._change();
				}
			}
	},

	
	/**
	 * Trae las opciones de un combo actual en formato de arreglo, 
	 * hace caso omiso de los valores vacios
	 * @access	private
	 * @param	HTMLSelectElement	combo
	 * @return	array
	 */
	getListOptions: function(combo){
		var values = [];
		
		for(var i = 0; i < combo.options.length; i++) {
			if(combo.options[i].value === '') { continue; }
			values.push([combo.options[i].value,combo.options[i].text]);
		}	
		return values;
	},
	
	/**
	 * Verifica si una relacion ya existe o no
	 * @access	private
	 * @param	string	id_source
	 * @param	string	id_target
	 * @param	string	trigger_value
	 * @return 	boolean
	 */
	triggerExists: function(id_source, id_target, trigger_value){
		//if(this._triggers[id_source] === undefined || this._triggers[id_source] === null) { return false; }
		//if(this._triggers[id_source][trigger_value] === undefined || this._triggers[id_source][trigger_value] === null) { return false; }
		
		if(this._triggers.get(id_source) === undefined || this._triggers.get(id_source) === null) { return false; }
		if(this._triggers.get(id_source).get(trigger_value) === undefined || this._triggers.get(id_source).get(trigger_value) === null) { return false; }
		
		var tlen = this._triggers.get(id_source).get(trigger_value).length;
		for(var t = 0; t < tlen; t++ ){
			var t_valpair = this._triggers.get(id_source).get(trigger_value)[t];
			var target = t_valpair.get('target');//target;
			if(target == id_target) {
				return true;
			}
		}
		return false;
			
	},
	
	/**
	 * Elimina los triggers asignados a un determinado combo fuente.
	 */
	deleteTriggerFromSource: function(id_source){
		try {
			var t = this._triggers.get(id_source);
			delete t;//para evitar memory leaks..
		}catch(e){}
		
		this._triggers.set(id_source, new Hash());
	},
	
	/**
	 * Agrega una relacion al componente.
	 * 
	 * @access	public
	 * 
	 * @param 	string	id_source
	 * @param 	string	id_target
	 * @param	string	trigger_value
	 * @param	array	target_values
	 * @return	boolean	true si se pudo agregar correctamente, false/alert si existio algun error
	 */
	addTrigger: function(id_source, id_target, trigger_value, target_values){
		//-- Verificar que exista el select
		
		if(id_source === "" || id_target === "") { return false; }
		if(id_source == id_target){
			if(this._debug) { alert('No puede generar una liga hacia el mismo elemento'); }
			return false;
		}
		if(!this.exists(id_source) && (this._selects[id_source] === null || this._selects[id_source] === undefined)) {
			if(this._debug) { alert('El atributo fuente: '+id_source+' no existe, relacion no agregada al componente.');}
			return false;
		}
		
		if(!this.exists(id_target) && (this._selects[id_target] === null) || this._selects[id_target] === undefined) {
			if(this._debug) { alert('El atributo destino: '+id_target+' no existe, relacion no agregada al componente.');}
			return false;
		}
		
		if(this.triggerExists(id_source, id_target, trigger_value)){
			if(this._debug) { alert('La liga que se intenta generar ya existe. Att fuente: '+id_source+', destino: '+id_target+', valor:'+trigger_value);}
			return false;
		}
		
		target_values = this.formatValues(target_values);
		
		//-- Agregar el trigger actual al historial de triggers sobre ese atributo.
		this._trigger_by[id_target] = id_source;
		
		//-- Agregar al cache de triggers
		//-- que, con que valor, invoca que, determinados atributos
		var first = false;
		if(this._triggers.get(id_source) === undefined) {
			this._triggers.set(id_source, new Hash());
		}
		
		if(this._triggers.get(id_source).get(trigger_value) === undefined) {
			this._triggers.get(id_source).set(trigger_value, []);
		}
		
		//Agregar los valores del trigger en si a la estructura principal
		
		this._triggers.get(id_source).get(trigger_value).push(new Hash({target:id_target, values:target_values}));
		
		//Contar el numero de triggers afectan a este campo
		first = (this._affected[id_target] === undefined);
		
		if(first) {
			this._affected[id_target] = true;
			this.resetSelect(this._selects[id_target]);	//Resetear, solo es necesario con el primer trigger

			//-- Agregar alias de seleccion
			var alias = (this.attribute_alias[id_source] !== undefined)?"-- Seleccione "+this.attribute_alias[id_source]+" --":this.default_alias;
			this.addOption(this._selects[id_target], alias, "");
			
			//-- Agregar a el evento de onchange solo agregar un observe al elemento, si no cada vez que se agregara un trigger se pondrian muchos.
			Event.observe(this._selects[id_source], 'change', this.trigger.bindAsEventListener(this, this)); //ends event.observe, this._selects[id_source]
		}
		
		//-- Se le agrega una clase para identificarlo posteriormente y poder resetearlo.
		$(id_target).addClassName('ls_triggeredby_'+id_source);
		
		//-- Se le agregan las clases del fuente, para que pueda ser reseteado en cadena.
		$(id_source).classNames().each(function(c){ $(id_target).addClassName(c);});
		
		return this;
	},
	
	getRoute: function(attribute) {
		var id_source	= this._trigger_by[attribute];
		if(id_source == null || id_source == undefined) {
			return '';
		}
		
		return id_source + ',' + this.getRoute(id_source);
	},
	
	
	execute: function(){
				var routes = new Hash();
				
				this._triggers.each(function(source){
					source.value.each(function(trigger_value){
						trigger_value.value.each(function(h) {
							var target = h.get('target');
							if(routes.get(target) == null || routes.get(target) == undefined){
								routes.set(target, this.getRoute(target));
							} 
							route = routes.get(target);
							
							route.split(",").each(function(element){
								if(element != '' && !$(target).hasClassName('ls_triggeredby_'+element)) { 
									$(target).addClassName('ls_triggeredby_'+element);
								}
							});
							//$(target).addClassName('ls_triggeredby_'+source.key);
							
						}.bind(this));
					}.bind(this));
				}.bind(this));
			}
	, 
	/**
	 * Verifica si un componente ya ha sido afectado por otro
	 * @access private
	 * 
	 * @param	string	source		atributo del que se quiere verificar si ha sido afectado
	 * @param 	string	by			atributo fuente
	 * @return	boolean
	 */
	hasBeenAffectedBy: function (source, by) {
		var l = this._trigger_order.length;
		for(var i = 0; i < l; i++){
			var event = this._trigger_order[i];
			if(event.src == by){
				var t = this._triggers.get(by).get(event.trigger);
				var len = t.length;//[by][event.trigger].length;
				for(var j = 0; j < len; j++){
					//-- Si el atributo ha sido accionado antes, y modifica al que se le va a ligar un nuevo trigger, entonces marcar error.
					if(t[j].get('target') == source){//[by][event.trigger][j].target == source){
						return true;
					}
				}
			}
		}
		return false;
	},
	
	/**
	 * Borra los valores de un elemento
	 * @access 	private
	 * @param	HTMLSelectElement	element
	 */
	clearSelect: function(element){
		$(element).update();
	},
	
	/**
	 * Resetea los valores de los combos que fueron seleccionados despues del atributo que se le manda en el argumento
	 * @access	private
	 * @param 	string	src
	 */
	resetUntil: function(src){
			//-- Forma nueva [Caso en el que los enlaces sean separados y no importe tanto el orden de ejecucion/relacion
			//-- en la forma anterior si seleccionaban varios se guardaba el orden en el que eran filtrados, y aunque se seleccionara uno que no tuviera relacion con los anteriores se reseteaba de todas maneras si se seleccionaba uno anterior.
			$$('.ls_triggeredby_'+src).each(function(element){
				this.resetSelect(element);
				var id_target = element.getAttribute("id");
				var id_source = this._trigger_by[id_target];
				var alias  = (this.attribute_alias[id_source] !== undefined)?"-- Seleccione "+this.attribute_alias[id_source]+" --":this.default_alias;
				this.addOption(element, alias, "");
			}.bind(this));
		return;
	},
	
	/**
	 * Resetea los valores de un combo
	 * @access	private
	 * @param 	HTMLSelectElement	element
	 */
	
	resetSelect: function(element){
		
		this.clearSelect(element);
		if(this._affected[element.getAttribute('id')] !== undefined) { return;}
		
		var values = this._values[element.getAttribute('id')];
		
		//FIXME: Agregar opciones en batch y llamar update solo una vez
		for(var i in values){
				if(typeof values[i][0] == "undefined") { continue; }
				this.addOption(element, values[i][1], values[i][0]);
		} 
	},
	
	/**
	 * Verifica si un elemento existe o no en el DOM
	 * @access	private
	 * 
	 * @param	string	id 	id del elemento
	 * @return	boolean
	 */
	exists: function(id){
		//-- Si existe regresar verdadero
		if(this._selects[id] !== null && this._selects[id] !== undefined){
			return true;
		}
		
		//-- de lo contrario verficar que exista el elemento en el arbol del dom y agregarlo
		if(Object.isElement($(id)) != null){
			this.addSelect($(id));
			return true;
		}
		return false;
	},
	
	/**
	 * Le da formato a los valores en caso de que sea un string, o solo haya puesto el value
	 * los valores tiene que ser value,alias
	 * @access	private
	 * @param 	array	values
	 * @return	array
	 */
	formatValues: function(values){
		if(!Object.isArray(values)){
			alert('Los valores no estan en formato de arreglo incialmente');
			return false;
		}
		final = [];
		for(var i in values){
			if(typeof values[i] == "string"){
				value = values[i].split("=");
				if(value.length == 2){
					e = [value[0],value[1]];
				} else {
					e = [value[0],value[0]];
				}
				final.push(e);
			} else if(Object.isArray(values[i])) {
				if(values[i].length == 1) {
					final.push([values[i],values[i]]);
				} else {
					final.push(values[i]);
				}
			}
		}
		
		return final;
	},
	
	/**
	 * Agrega una opcion al combo
	 * @access	private
	 * 
	 * @param	HTMLSelectElement	selector
	 * @param	string				n			Alias
	 * @param	string				v			Value
	 * @param	string				s			Style
	 */
	addOption: function(selector, n, v, s){
		if(s === undefined) { s = ""; }
		selector.insert(new Element('option',{name:n,value:v,style:s}).update(n));
	}, 
	
	/**
	 * Borra una relacion del componente
	 * @access	public
	 * 
	 * @param	string	id_source
	 * @param	string	id_target
	 * @param	string	trigger_value
	 * @return	boolean
	 */
	deleteTrigger: function(id_source, id_target, trigger_value){
		var tlen = this._triggers.get(id_source).get(trigger_value).length;//this._triggers[id_source][trigger_value].length;
		var res = false;
		for(var t = 0; t < tlen; t++ ){
			var t_valpair = this._triggers.get(id_source).get(trigger_value)[t];//this._triggers[id_source][trigger_value][t];
			var target = t_valpair.get('target');//t_valpair.target;
			if(target == id_target) {
				this._triggers.get(id_source).get(trigger_value).remove(t);
				//this._triggers[id_source][trigger_value].remove(t);
				res = true;
				break;
			}	
		}
		if(!res) { return false;}
		
		//-- reinicializar los valores de los selects.
		this._trigger_order = [];
		this.resetUntil(""); //todos
		return true;
	}, 
	
	
	
	//-- Metodos pendientes
	//hace un match entre dos listas para obtener los valores posibles
	//-- por ejemplo este caso:  A->B A->C con el mismo trigger. pero B->C se activa despues y existen atributos similares, se hace match de los similares
	intersect: function(list1, list2){
		return list1.findAll(function(t) { return list2.include(t); });
	},
	
	//match de una lista imbricada l1:[[val,alias],[val,alias],...], l2: [[val,alias],[val,alias],..]
	match: function(list1, list2){
		var l1 = [];
		var r1 = [];
		var l2 = [];
		var r2 = [];
		for(var i = 0; i < list1.length; i++){ l1[list1[i][0]] = list1[i]; r1[i] = list1[i][0]; }
		for(var i2 = 0; i2 < list2.length; i2++){ l2[list2[i2][0]] = list2[i2]; r2[i2] = list2[i2][0]; }
		
		var intersected = this.intersect(r1,r2);
		if(intersected.length === 0) { return false; }
		for(var i3 = 0; i3 < intersected.length; i3++) {
			intersected[i3] = l1[intersected[i3]];
		}
		return intersected;
	}
});

/**
 * Funcion para remover elementos de un arreglo.
 * @see Array element remove: <a href='http://ejohn.org/blog/javascript-array-remove/'>http://ejohn.org/blog/javascript-array-remove/</a>
 */
Array.prototype.remove = function(from, to) {
  var rest = this.slice((to || from) + 1 || this.length);
  this.length = from < 0 ? this.length + from : from;
  return this.push.apply(this, rest);
};


