var AutoCompleteDB = Class.create();
AutoCompleteDB.prototype = {
    Version: '1.0.0',
    REQUIRED_PROTOTYPE: '1.4.0',
 
    initialize: function (id, param) {
        // check whether we have the appropiate javascript libraries
        this.PROTOTYPE_CHECK();

        // get field
        this.fld = $(id);
	
        // Overlay Info
        this.iframe = id + "_iframe";
        this.needOverlay = this.checkOverlaySupport();

        if (!this.fld) { throw("AutoComplete requires a field id to initialize"); }
	
        // init variables
        this.sInp 	= ""; 		// input value 
        this.nInpC 	= 0;		// input value length
        this.aSug 	= []; 		// suggestions array with Tmp Data
        this.actualRoot = "";	// el valor de los caracteres en minchar;
        this.iHigh 	= 0;		// level of list selection 
        this.keyTimer = "";
        this.cache = new Array();	
        this.isMouseBlur = false;
        this.selected = true;
        this.noResultsFound = false;
	
        // parameters object
        this.options = param ? param : {};
	
        // defaults	
        var k, def = {
            preloadedOptions:new Array(), 
            valueSep:';', 
            minchars:1, 
            meth:"get", 
            varname:"input", 
            className:"autocomplete", 
            timeout:3000, 
            delay:500, 
            hOffset:-5, 
            vOffset:-5, 
            shownoresults: true, 
            noresults: "-", 
            maxheight: 250, 
            maxentries: 25, 
            onAjaxError:null, 
            setWidth: false, 
            minWidth: 100, 
            maxWidth: 200, 
            useNotifier: true, 
            textToDelete: ""
        };
	
        // vamos a utilizar minchar como valor para establecer la busqueda a bbdd
        // SOLO SE BUSCARA CUANDO EL INPUT DE ENTRADA TENGA minchars CARACTERES y A PARTIR DE AHI SE CACHEARA
        // CUANDO VUELVA A TENER minchars SE VUELVE A BUSCAR 
	
        for (k in def) {
            if (typeof(this.options[k]) != typeof(def[k]))
                this.options[k] = def[k];
        }
	
        //si tenemos preloadedOptions formateamos las llaves para poder realizar búsquedas
        var formattedPreloadedOptions = new Array();
        this.options.preloadedOptions.each
        (
            function(entry)
            {
                var key = this.cleanString(entry);
                formattedPreloadedOptions.push({
                    formatted:key,
                    value:entry
                });
            }.bind(this)
            );
        this.options.preloadedOptions = formattedPreloadedOptions;
	
        if (this.options.useNotifier) this.fld.addClassName('ac_field');
	
        // set keyup handler for field
        // and prevent AutoComplete from client
        // NOTE: not using addEventListener because UpArrow fired twice in Safari
        this.initializeEvents(this);
        this.fld.setAttribute("AutoComplete","off");
        if (this.fld.value != "")
            this.fld.setStyle({
                backgroundImage:'none'
            });

    },
  
    initializeEvents: function(objetoActual) {
        this.fld.onkeypress 	= function(ev){
            return objetoActual.onKeyPress(ev);
        };
        this.fld.onkeyup 		= function(ev){
            return objetoActual.onKeyUp(ev);
        };
        this.fld.onblur			= function(ev){
            return objetoActual.onBlur(ev);
        };
        this.fld.onclick		= function(ev){
            objetoActual.fld.focus();
        };	
        this.fld.onfocus		= function(ev){
            if (objetoActual.options.textToDelete != "" && objetoActual.fld.value==objetoActual.options.textToDelete) {
                objetoActual.fld.value = "";
            }
            objetoActual.selected=true;
            objetoActual.fld.select();
        }; 
    },
  
    convertVersionString: function (versionString){
        var r = versionString.split('.');
        return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
    },
  
    PROTOTYPE_CHECK: function() { 
        if((typeof Prototype=='undefined') || 
            (typeof Element == 'undefined') || 
            (typeof Element.Methods=='undefined') ||
            (this.convertVersionString(Prototype.Version) < 
                this.convertVersionString(this.REQUIRED_PROTOTYPE)))
            throw("AutoComplete requires the Prototype JavaScript framework >= " +
                this.REQUIRED_PROTOTYPE);
    },
  
    onBlur: function (e) {
        if (!this.isMouseBlur && !this.noResultsFound ) {
            this.iHigh = 1;
            this.setHighlight(1);	  		
            this.setHighlightedValue();
            this.selected = false;		
        }
    },
  
    onKeyPress: function (e) {
        if (!e) e = window.event;
        var key = e.keyCode || e.which;
  	
        // set responses to keypress events in the field
        // this allows the user to use the arrow keys to scroll through the results
        // ESCAPE clears the list
        // RETURN sets the current highlighted value
        // TAB sets the first item in list

        switch(key)
        {
            case Event.KEY_RETURN:
                this.setHighlightedValue();
                Event.stop(e);
                break;
            case Event.KEY_TAB: // Si tabuleamos se elige el primero;		
                this.fld.blur();		
                Event.stop(e);
                break;
            case Event.KEY_ESC:
                this.clearSuggestions();
                break;
        }
        return true;
    },
  
    onKeyUp: function (e) {
        if (!e) e = window.event;
        var key = e.keyCode || e.which;
  	
        if (key == Event.KEY_UP || key == Event.KEY_DOWN) {
            this.changeHighlight(key);
            Event.stop(e);
        } else {
            this.getSuggestions(this.fld.value,this);
        }
        return true;
    },
  
    cleanString: function(val)
    {
        //Formateando entrada para comparar
        var formattedVal = val.toLowerCase();
        formattedVal = formattedVal.gsub(/[áàäâ]/,'a');
        formattedVal = formattedVal.gsub(/[éèëê]/,'e');
        formattedVal = formattedVal.gsub(/[íìïî]/,'i');
        formattedVal = formattedVal.gsub(/[óòöô]/,'o');
        formattedVal = formattedVal.gsub(/[úùüû]/,'u');
        formattedVal = formattedVal.gsub(/[ñÑ]/,'n');		
        formattedVal = formattedVal.gsub(/[çÇ]/,'c');		
        return formattedVal;
    },
  
    getSuggestions: function(val,objetoActual) {
        this.noResultsFound = false;
  	
        // input the same? -> do nothing
        if(val==this.sInp) return false;
  	
        // kill the old list
        if($(this.acID)) $(this.acID).remove();
  	
        this.sInp = val;
  	
        if (val.length < this.options.minchars)
        {
            // input length is less than the min required to trigger a request -> do nothing  		
            this.aSug 	= [];
            this.aFullSug = [];
            this.nInpC	= val.length; 
            this.actualRoot = "";  		
        }
        else
        {
            //Buscamos en la lista precargada, si existe
            //LLamarla autocomplete_entries o similar, declarandola como un array en un js externo
            //Si existe, buscar en ella
            //De todos modos, disparar la Ajax Request para que llegue con la lista mas completa despues de un rato
            //Las opciones se almacenan en "this.aSug"
            //Y la lista se crea en "this.createList(this.aSug,this);"
		
            //Devuelve JSON de la forma:
            //{"results": [{"id": "1428", "value": "Barcelonnette, Francia - Barcelonnette (BAE)"}]}
		
            var coincidences = new Array();
            objetoActual.options.preloadedOptions.each
            (
                function(entry,index)
                {
                    var formattedVal = this.cleanString(val);
                    if(entry.formatted.indexOf(formattedVal)>=0)
                    {
                        coincidences.push({
                            id:index,
                            value:entry.value
                            });
                    }
                }.bind(objetoActual)
                );
            if(coincidences.length > 0)
            {
                //Usar setSuggestions en lugar de createList directamente
                objetoActual.setSuggestions(coincidences,val);
            }
		
            this.nInpC	= val.length;   		
            clearTimeout(this.keyTimer); 
            this.keyTimer = setTimeout( 
                function () {
                    if ( objetoActual.fld.value.length == objetoActual.nInpC ) {
                        cached = objetoActual.findInCache(val);
                        if (cached)
                            objetoActual.createList(cached,objetoActual);
                        else
                            objetoActual.doAjaxRequest(val,objetoActual);			  		
                    }
                } , this.options.delay);
        }
	
        return false;
    },
  
  
    
    doAjaxRequest: function(input,objetoActual)  {  	 	
        // create ajax request
        // do we need to call a function to recreate the url?
        var cleanInput = this.cleanString(input);
        if (typeof this.options.script == 'function')
            var url = this.options.script(encodeURIComponent(cleanInput));
        else
            var url = this.options.script+this.options.varname+'='+cleanInput;
        //var url = this.options.script+this.options.varname+'='+encodeURIComponent(cleanInput);
  	
        if(!url) return false;
  	  	
        var m = this.options.meth;  // get or post?
        if (this.options.useNotifier){
            objetoActual.fld.removeClassName('ac_field_busy');                    
            objetoActual.fld.addClassName('ac_field_busy');
        } 
  	
        var options = {
            method: m,
            onSuccess: function (req) 
            {
                if (objetoActual.options.useNotifier){
                    objetoActual.fld.removeClassName('ac_field');
                    objetoActual.fld.removeClassName('ac_field_busy');                    
                } 
                // response in json format?
                suggestions = objetoActual.processSuggestions(req.responseText);
                objetoActual.setSuggestions(suggestions,input);
            },
            onFailure: (typeof objetoActual.options.onAjaxError == 'function')? function (status) {
                if (objetoActual.options.useNotifier){
                    objetoActual.fld.removeClassName('ac_field');
                    objetoActual.fld.removeClassName('ac_field_busy');
                    objetoActual.fld.addClassName('ac_field');
                }
                    objetoActual.options.onAjaxError(status)
                } :  function (status) {
                if (objetoActual.options.useNotifier){
                    objetoActual.fld.removeClassName('ac_field');
                    objetoActual.fld.removeClassName('ac_field_busy');
                    objetoActual.fld.addClassName('ac_field');                    
                }
                //alert("AJAX error: "+status);
            }
        }
        // make new ajax request
        new Ajax.Request(url, options);
    },
  
    //Receives JSON code, parses it to an array and returns it
    processSuggestions : function (json)
    {
        var aSugTmp = [];
        var jsondata = eval('(' + json + ')');
	
        for (var i=0; i<jsondata.results.length;i++)
        {
            aSugTmp.push({
                'id':jsondata.results[i].id, 
                'value':jsondata.results[i].value, 
                'info':jsondata.results[i].info
                });
        }
	 
        return aSugTmp;
    },
  
    setSuggestions: function (suggestionsArray,input)
    {
        // compruebo lo necesario para tirar la request a la basura si se nos ha quedado anticuada...  	
        var aSugTmp = suggestionsArray;
	
        this.cacheResults(input, this.aSugTmp);
        if (input != this.fld.value)
            return false;	
		
        this.aSug = aSugTmp;
        aSugTmp = [];
			
        this.acID = 'ac_'+this.fld.id;	
        this.actualRoot = input; 
        if (!this.selected) {
            this.iHigh = 1;
            this.setHighlight(1);			
            this.setHighlightedValue(); 				
        } else
            this.createList(this.aSug,this); 
    },
  
    cacheResults: function (input, values) {
        this.cache.push({
            c:input,
            v:values
        });
    },
  
    findInCache: function (needle) {
        for (i=0,len=this.cache.length; i<len; i++) {
            if (this.cache[i].c.toLowerCase() == needle.toLowerCase())
                return this.cache[i].v;
        }
        return false;
    },
  
    clearCacheResults: function() {
        this.cache.clear();	
    },
  
    createDOMElement: function ( type, attr, cont, html )
    {
        var ne = document.createElement( type );	
        if (!ne)
            return 0;
		
        for (var a in attr)
            ne[a] = attr[a];
	
        var t = typeof(cont);
	
        if (t == "string" && !html)
            ne.appendChild( document.createTextNode(cont) );
        else if (t == "string" && html)
            ne.innerHTML = cont;
        else if (t == "object")
            ne.appendChild( cont );
        return ne;
    },
  
    createList:	function(arr, objetoLocal) {
        // get rid of the old list if any  
        if($(this.acID)) $(this.acID).remove();
  	  	
        // if no results, and shownoresults is false, do nothing
        if (arr.length == 0 && !this.options.shownoresults) return false;
  	
        // create holding div
        var div	= this.createDOMElement('div', {
            id:this.acID, 
            className:this.options.className
            });
  	  	
        // create and populate ul
        var ul	= this.createDOMElement('ul', {
            "id":'ac_ul',
            "className": 'curved_bottom_only'            
        });
        // no results?
        if (arr.length == 0 && this.options.shownoresults) {		
            var li = this.createDOMElement('li', {
                className: 'ac_warning'
            }, this.options.noresults ,true);
            ul.appendChild(li);
            this.noResultsFound = true;		
        } else {
            // loop through arr of suggestions creating an LI element for each of them
            // render must be limited by this.options.limit if it is lower than arr.length
            var limit = this.options.maxentries < arr.length? this.options.maxentries: arr.length;
            var val;
            for (var i=0; i<limit; i++)
            {
                // format output with the input enclosed in a EM elementFromPoint
                // (as HTML not DOM)
                val 	= arr[i].value+'';
                var st 		= 0; // Capado a que muestre solo los resultados por delante
                st=val.toLowerCase().indexOf(this.sInp.toLowerCase());
                if(st!=-1)				
                    var output 	= val.substring(0,st) + '<em>' + val.substring(st,st+this.sInp.length) + '</em>' + val.substring(st+this.sInp.length);
                else
                    var output 	= val;
                var span	= this.createDOMElement('span',{},output,true); // type of, properties, output, isHTML?
			
                if(arr[i].info != '') // do we need to add extra info?
                {				
                    var small = this.createDOMElement('small',{}, arr[i].info);
                    span.appendChild(small);
                }
			
                var a 	= this.createDOMElement('a',{
                    href:'javascript:void(0);'
                });			
                a.appendChild(span); // add the object span into the link
			
                a.name = i+1;
			
                a.onclick 		= function (){
                    objetoLocal.setHighlightedValue();
                };
                a.onmouseover	= function () {
                    objetoLocal.isMouseBlur = true;
                    objetoLocal.setHighlight(this.name);
                }; 
                a.onmouseout	= function () {
                    objetoLocal.isMouseBlur = false;
                }; 			
			
                var li = this.createDOMElement('li',{}, a); // add the link element to a li element
			
                // finally add the newly created li element to the ul element 
                ul.appendChild(li);

            }
        }	
        div.appendChild(ul); // add the newly created list to the div element
	
        // get position of target textfield
        // position holding div below it
        // set width of holding div to width of field 	
        var pos             = Position.cumulativeOffset(this.fld); 
        div.style.left      = pos[0] - this.fld.offsetLeft + this.options.hOffset + "px";
        div.style.top       = pos[1] + this.fld.offsetHeight + this.options.vOffset + "px";
		
        var w               = (this.options.setWidth && this.fld.offsetWidth < this.options.minWidth)? this.options.minWidth : (this.options.setWidth && this.fld.offsetWidth > this.options.maxWidth)? this.options.maxWidth : this.fld.offsetWidth;
	 
        div.style.width     = w + "px";
		
        // add DIV to document
        document.getElementsByTagName("body")[0].appendChild(div);
        $(this.acID).show();	
        this.handleOverlay(0);	
        this.handleOverlay(1);
	
        // highlight first item, if there are items to highlight
        if( arr.length > 0 ) {
            this.iHigh = 1;
            this.setHighlight(1);
            if (!this.selected) {
                this.setHighlightedValue();		
            }
        }
    },
  
    changeHighlight:	function(key)
    {
        var list = $("ac_ul");
        if (!list)
            return false;
	
        var n;

        n = (key == Event.KEY_DOWN || key == Event.KEY_TAB)? this.iHigh + 1 : this.iHigh - 1; // false assumedd to be Event.KEY_UP
	
        n = (n > list.childNodes.length)? list.childNodes.length : ((n < 1)? 1 : n);	
	
        this.setHighlight(n);
    },
  
    setHighlight:		function(n)
    {
        var list = $('ac_ul');
  
        if (!list) 
        {
            return false;
        }
  	
        if (this.iHigh > 0) this.clearHighlight();
  	
        this.iHigh = Number(n);
  	
        list.childNodes[this.iHigh-1].className = 'ac_highlight';	
    },
  
    clearHighlight:	function()
    {
        var list = $('ac_ul');
  	
        if(!list) return false;
  	
        if(this.iHigh > 0)
        {
            list.childNodes[this.iHigh-1].className = '';
            this.iHigh = 0;
        }	
    },
  
    setHighlightedValue:	function()
    {
        if (this.iHigh)
        {
            // HERE WE NEED TO IMPLEMENT THE GMAIL LIKE SPLITTED VALUE  		
            if (!this.aSug[this.iHigh - 1]) return;
  		
            // Old way
            this.sInp	= this.fld.value = this.aSug[ this.iHigh -1 ].value;
  		
            // pass selected object to callback function, if exists
            if (typeof this.options.callback == 'function')
                this.options.callback(this.aSug[this.iHigh-1]); // the object has the properties we want, it will depend of
		
            this.clearSuggestions();
        }
    },

    clearSuggestions:	function ()
    {
        // if ($(this.acID)){this.fadeOut(50,function () {$(this.acID).remove();});}
        // probando sin fade
        if ($(this.acID)){
            $(this.acID).remove(); 
            this.handleOverlay(0);
            this.aSug = [];		
        }
    },

    /*************** IE 5.5 & 6.0 HACK ****************/
    // mira si es necesaria la construcción de un iframe de overlay
    checkOverlaySupport: function() { 
        var browserVersion=parseInt(navigator.appVersion);  	        
        if (Prototype.Browser.IE && (browserVersion > 5 && browserVersion < 7)){
            return true;
        } else 
            return false;
    }, 
    
    // maneja la construcción del overlay
    handleOverlay: function(bVis) {
        if ( this.needOverlay) {
            switch (bVis) {
                case 1 :
                    if (!$(this.iframe)){
                        // create holding iframe
					
                        var iframe = '<iframe id="' + this.iframe + '" frameborder="0" scrolling="no" src="about:blank" style="display:none;position:absolute;border:0px none;z-index:15000; background-color:transparent; filter: alpha(opacity=10); opacity: 0.1"></iframe>';
                        document.getElementsByTagName("body")[0].id = "superbody";
                        new Insertion.Bottom("superbody", iframe);
						
                        var pos		= Position.cumulativeOffset(this.fld); // deprecated in Prototype 1.6, use instead this.fld.cumulativeOffset()		
                        var w 		= (this.options.setWidth && this.fld.offsetWidth < this.options.minWidth)? this.options.minWidth : (this.options.setWidth && this.fld.offsetWidth > this.options.maxWidth)? this.options.maxWidth : this.fld.offsetWidth;
                        var h = $(this.acID).getHeight() + (2*this.options.offsety);
						
                        $(this.iframe).setStyle({			
                            left:pos[0] - this.fld.offsetLeft + "px",
                            top: pos[1] + (-2 * this.options.offsety) +  this.fld.offsetHeight + "px",
                            width: w + "px",
                            height: h + "px"							
                        });								
                        $(this.iframe).show();
                    }
                    break;
                case 0 :
                    if ($(this.iframe)){
                        $(this.iframe).hide();						
                        $(this.iframe).remove();
                    }
                    break;
            }
        }
    }      
}
