/**
 *  The point of this plugin is to take a text input field (Master Input), and turn it into 
 *  an auto-completing widget.  As you type, suggestions pop-down into an list/area (Suggestions 
 *  Div) hinging off the bottom of the Master Input field.
 * 
 *  The URL that is called to fetch the "suggestions" is called ajaxURL.
 *  
 *  The endgame of this functionality is to create a hidden field (Hidden Post Val) 
 *  with a single value that represents the "selection" that you make from the Suggestions Div.  
 *  A choice must be made from Suggestions Div and clicked on in order to create and 
 *  populate this Hidden Post Val field.  If you simply type into the Master Input and ignore 
 *  the Suggestions Div, no Hidden Post Val field will get created and you will not get the the 
 *  data that you are expecting for your form.
 *  
 *  This being the case, pre-populating one of these fields so that its parent form is in a 
 *  submittable state is not simple, becuase the action of creating the Hidden Post Val field 
 *  will not have happened.  Therefore, it is necessary to have a secondary Ajax URL that will 
 *  retrieve a single-value (no Suggestions Div to click in) and magically create the Hidden 
 *  Post Val field with no user input.  This URL is called ajaxSingleLookupURL.
 *  
 *  ajaxSingleLookupURL is ONLY necessary if you will be pre-populating the Master Input 
 *  field and want this plugin to lookup the value for and create the Hidden Post Val field.
 *  If this functionality is not necessary, you can just make ajaxSingleLookupURL the same 
 *  URL as ajaxURL.
 */
(function($) {
$.fn.autocompleteMh = function(opts){

    return this.each(function(){
        var $this = $(this);

        // parameterized variables
        var defaults = {
            ajaxURL:               "", // must come in from outside
            ajaxSingleLookupURL:   "", // must come in from outside
            onSelectJSFunction:    "", // optionally comes from outside to do extra stuff. Will only execute if it is sent in.
            shortenLocationString: false,
            fieldID:               this.id,
            resultsID:             this.id + "_results",
            postID:                this.id + "_hid", 
            postName:              this.name + "_hid", 
            delay:                 700,
            minChars:              4, 
            limit:                 10,
            visibleLimit:          null,
            resultsWidth:          'auto',
            loaderUrl:             "", 
            clickToClear:          "", 
            afterAddCallback:      null, 
            startString:           "", // must come in from outside
            startHidValue:         "", // must come in from outside
            startHelpString:       "", // must come in from outside
            noResultsMessage:      "", // must come in from outside
            helpMessage:           "", // must come in from outside
            moreSpecificMessage:   "",  // must come in from outside
            placeholder:           null,
            selfStartOnStartString: false, 
            displayTheId:          false, // will add a visible text box, displaying the Location ID of the selected Location
            defaultLang:           ""  // must come in from outside
        }; 
        var options = $.extend({}, defaults, $.fn.autocompleteMh.defaults, opts);
        
        $.fn.autocompleteMh.acCounter++;
        
        var displayTheId = options.displayTheId;
        // used with displayTheId, tells when the "hidden" id is displayed.  won't display it again.
        var newLocSet = false;
        
        // check the options.ajaxURL is set
        if ((options.ajaxURL === undefined) || (options.ajaxSingleLookupURL === undefined)) {
            console.error('ERROR! You must set ajaxURL parameter, see documentation');
            return false;
        }
    
        // define interface strings
        var initialString       = options.startString;
        var initialHidValue     = options.startHidValue;
        var initialHelpString   = options.startHelpString;
        var noResultsMessage    = options.noResultsMessage;
        var helpMessage         = options.helpMessage;
        var moreSpecificMessage = options.moreSpecificMessage;
        var selfStartOnStartString = options.selfStartOnStartString;
        var clickToClearId      = options.clickToClear;
        var afterAddCallback    = options.afterAddCallback || options.afterAddCallbackFunc;
    
        // create the results div
        $this.after('<div class="acWrap"><div id="' + options.resultsID + '" class="acResults"></div></div>');
    
        $this.attr('autocomplete','off');
    
        // register mostly used vars
        var acSearchField = $this;
        var acPopDownResultsDiv = $('#'+options.resultsID);
        
        // keep track of last "good" values (display string & hidden value)
        var lastGoodDisplayStr = "";
        var lastGoodHidVal = "";
        
        // currently selected item in the drop-down list of ajax-fetched items
        var acListCurrent;
        var acListTotal;
        
        var disabled = false;
    
        if(options.placeholder) $this.attr('placeholder', options.placeholder); 
        
        if ((initialHelpString !== '') && (initialString == '')) {
            initialString = initialHelpString;
            $this.val(initialHelpString);
        } else if (initialString !== '') {
			// here, we could be setting lastGoodDisplayStr to a city string, but lastGoodHidVal is not set
            lastGoodDisplayStr = initialString;
            lastGoodHidVal = initialHidValue;
            $this.val(initialString);
        } 
        
        if (clickToClearId != '') {
			if (isArray(clickToClearId)) {
				for (var x=0; x<clickToClearId.length; x++) {
					var ss = clickToClearId[x]
		            var clickToClearElement = jQuery('#' + ss);
		            clickToClearElement.click(function(){
		                $this.initWidget();            
		            });
				}
			} else { // probably string
	            var clickToClearElement = jQuery('#' + clickToClearId);
	            clickToClearElement.click(function(){
	                $this.initWidget();            
	            });
			}
        }
        

        /////////////////////////////////////////////////////////////////////
        ///////    FOCUS     ////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////
        
        // register focus event listener
        $this.focus(function(e) {
            var thisRo = acSearchField.attr('readonly');
	        if (thisRo) {
				$this.disabled = true;
		        return;
	        }
            acSearchField.removeClass('errorHighlight');
            $this.removeHidPostEl();
            $this.clearInputField();
            
            if (acSearchField.val().length > 0) {
                $this.runAutoComplete(acSearchField.val());
            } else {
                $this.showHelpMessage();
            }
        }); // end focus


        /////////////////////////////////////////////////////////////////////
        ///////    BLUR     /////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////
        
        // register blur event listener
        $this.blur(function(e) {
            if ($this.disabled) {return;}
            if ($this.holdClose) { e.preventDefault(); return; }
            var selected_aTag = acPopDownResultsDiv.children('a.selected');
            var first_aTag = acPopDownResultsDiv.children('a:first');

            if($this.working) {
                acSearchField.val('');
                $this.working = false;
            }
            
            if (selected_aTag.size() == 1) {
                $this.setAndAddHidPostEl(selected_aTag);
                acSearchField.val(selected_aTag.text());
            } else if (first_aTag.size() == 1) {
                $this.setAndAddHidPostEl(first_aTag);
                acSearchField.val(first_aTag.text());
            } else if ((acSearchField.val() === "") & (lastGoodDisplayStr !== "")) {
                // do nothing
                
                
                acSearchField.val(lastGoodDisplayStr);
                var fakeAtag = $('<a locid="'+lastGoodHidVal+'" class="unselected">'+lastGoodDisplayStr+'</a>');
                acPopDownResultsDiv.append(fakeAtag);
                $this.setAndAddHidPostEl(fakeAtag);
            
                
            } else {
                if ($('#'+options.postID).size() == 1) {
                    // chriso: what is this about?  some error?
                } else {
//                    acSearchField.addClass('errorHighlight');
                    $this.removeHidPostEl();
                }
            }
            // if there's a string in acSearchField, this stuff has no effect
            setTimeout(function () {
                $this.hideAcWidgetExtras();
                $this.resetInputFieldIfEmpty();
            }, 200);
        }); // end BLUR
        
    
    
        /////////////////////////////////////////////////////////////////////
        ///////    KEY DOWN     /////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////
        
        // register keydown event listener
        $this.keydown(function(e) {
        	if ($this.disabled) return;
            // get keyCode (window.event is for IE)
            var keyCode = e.keyCode || window.event.keyCode;
            var lastVal = acSearchField.val();
            
            // check and treat up and down arrows
            if($this.isUpOrDownArrow(keyCode)){
                return false;
            }
            
            // check and ignore certain keys (left, right, control, option, shift)
            if(isNavigationKey(keyCode)) {
                return false;
            }
            
            // check for ENTER
            if(keyCode == 13 || keyCode == 9){
                var selected_aTag = acPopDownResultsDiv.children('a.selected');
                var first_aTag = acPopDownResultsDiv.children('a:first');
                
                if (selected_aTag.size() == 1) {
                    acSearchField.val(selected_aTag.text());
                    $this.setAndAddHidPostEl(selected_aTag);
                } else if (first_aTag.size() == 1) {
                    acSearchField.val(first_aTag.text());
                    $this.setAndAddHidPostEl(first_aTag);
                }
                $this.hideAcWidgetExtras();
                e.stopPropagation();
            }
            
            // check for ESC
            if(keyCode == 27){
                $this.hideAcWidgetExtras();
                return;
            }
            
            // show load indicator
            $this.showLoadIndicator();
            
            // call autoComplete with delay
            clearTimeout($.fn.autocompleteMh.timing);
			newLocSet = false;
            $.fn.autocompleteMh.timing = setTimeout(function () {
                $this.runAutoComplete(lastVal);
            }, options.delay);
        }); // end keydown

    
        $this.checkWordLength = function(str){
           if(str < options.minChars) {
               return false;
           }
           var goodLen = false;
           var words = str.split(" ");
           for (var i=0; i<words.length; i++) {
               var subStr = words[i];
               subStr = subStr.replace(/\W/,"");
//$.log("in this.checkWordLength: subStr is \"" + subStr + "\"");
               if (subStr.length >= options.minChars) {
                  goodLen = true;
                  break;
               }
           }
           return goodLen;
        };
    
    
        /////////////////////////////////////////////////////////////////////
        ///////    RUN AUTOCOMPLETE AJAX CALL     ///////////////////////////
        /////////////////////////////////////////////////////////////////////
            
        // treat the auto-complete action (delayed function)
        $this.runAutoComplete = function(lastValue) {
            // get the field value
            var part = acSearchField.val();

            // Return if we've cancelled the search
            if(!$this.working) return;
        
            // if it's empty clear the resuts box and return
            if(part === ''){
                $this.hideAcWidgetExtras();
                $this.showHelpMessage();
                return;
            }
            
            // require min characters before searching
            if(!$this.checkWordLength(part)) {
                $this.showHelpMessage();
                return;
            }
            
            // make sure that the string is different
            if (lastValue == part) {
                $this.hideLoadIndicator();
                return;
            }
            
            // get remote data as JSON
            var ajaxParams = {q:part, l:options.limit, lng:options.defaultLang, sls:options.shortenLocationString};

            $.getJSON(options.ajaxURL, ajaxParams, function(json){

                $this.hideLoadIndicator();
                
                // get the total of results
                var ansLength = json.length;

                // if there are results populate the results div
                if(ansLength > 0){
                    var newData = '';
                    
                    // resize results div to fit results
                    acPopDownResultsDiv.css('width', options.resultsWidth);
                    
                    // create a div for each result
                    for(var i=0; i < ansLength; i++) {
                        newData += '<a class="unselected" locId="'+json[i][0]+'">' + json[i][1] + '</a>';
                    }
                    
                    // if too many results display be more specific message
                    if (ansLength >= options.limit) {
                        newData += '<div class="acHelp">' + moreSpecificMessage + '</div>';
                    }
        
                    // update the results div
                    acPopDownResultsDiv.html(newData);
                    acPopDownResultsDiv.css("display","hidden"); 
                    if(options.visibleLimit) {
                        var h = acPopDownResultsDiv.find("a.unselected").outerHeight();
                        acPopDownResultsDiv.css("height", '' + (h * options.visibleLimit) + 'px');
                        acPopDownResultsDiv.css("overflow", "auto");
                    }
                    acPopDownResultsDiv.css("display","block");

                    acPopDownResultsDiv.mousedown(function() {
                        $this.holdClose = true
                    })
                    acPopDownResultsDiv.mouseup(function() {
                        $this.holdClose = false
                    })
                    
                    // for all options in results
                    var aTags = acPopDownResultsDiv.children("a");
                
                    // on mouse over clean previous selected and set a new one
                    aTags.mouseover( function() {
                        aTags.attr('class','unselected');
                        this.className = 'selected';
                        acListCurrent  = aTags.index(this);
                    });
                
                    // on click: copy the result text to the search field and hide
                    aTags.click( function() {
                        acSearchField.val(this.childNodes[0].nodeValue);
                        $this.setAndAddHidPostEl(this);
                        $this.hideAcWidgetExtras();
                    });
                } else {
                    acPopDownResultsDiv.css('width', acSearchField.outerWidth());
                    acPopDownResultsDiv.show();
                    acPopDownResultsDiv.html('<div class="acHelp">' + noResultsMessage + '</div>');
                }
            }); // END $.getJSON(options.ajaxURL, ajaxParams, function(json)
        }; // END function runAutoComplete (lastValue)

        // similar to runAutoComplete, but used when you know a valid full-path.  will 
        //  fech one single object id for that string and then call setAndAddHidPostEl
        //  to set the hidden field that you would get by running runAutoComplete and 
        //  clicking on a returned item
        $this.runFindOneId = function(strVal) {
        
            // get the field value
            //var part = acSearchField.val();
            var part = strVal;
        
            // get remote data as JSON
            var ajaxParams = {q:part, l:'1', sls:options.shortenLocationString};
            $.getJSON(options.ajaxSingleLookupURL, ajaxParams, function(locId){
                // get the total of results
                // if there are results populate the results div
                if(locId !== undefined){
                    var myId = locId;
                    var fakeAtag = $('<a locid="'+myId+'" class="unselected">'+part+'</a>');
                    acPopDownResultsDiv.append(fakeAtag);
                    $this.setAndAddHidPostEl(fakeAtag);
                }
            }); // END $.getJSON(options.ajaxURL, ajaxParams, function(json)
        }; // END $this.runFindOneId
    
    
        /////////////////////////////////////////////////////////////////////
        ///////    KEY IDENTIFIER HELPERS     ///////////////////////////////
        /////////////////////////////////////////////////////////////////////

        // let up and down arrows navigate results 
        $this.isUpOrDownArrow = function(keyCode) {
            if(keyCode == 40 || keyCode == 38){
                if(keyCode == 38){ // keyUp
                    if(acListCurrent === 0 || acListCurrent === -1){
                        acListCurrent = acListTotal-1;
                    }else{
                        acListCurrent--;
                    }
                } else { // keyDown
                    if(acListCurrent == acListTotal-1){
                        acListCurrent = 0;
                    }else {
                        acListCurrent++;
                    }
                }
           
                // loop through each result div applying the correct style
                acPopDownResultsDiv.children('a').each(function(i){
                    if(i == acListCurrent){
                        //acSearchField.val(this.childNodes[0].nodeValue);
                        this.className = "selected";
                    } else {
                        this.className = "unselected";
                    }
                });
           
                return true;
            } else {
                // reset
                acListCurrent = -1;
                return false;
            }
        }; // END function isUpOrDownArrow
           
           
        // check for special keys to ignore
        isNavigationKey = function(keyCode) {
            var navKeys = [37, 39, 16, 17, 18];
            for(i=0; i < navKeys.length; i++) {
                if (navKeys[i] == keyCode) {
                    return true;
                }
            }
        }; // END function isNavigationKey
            
    
    
    
        /////////////////////////////////////////////////////////////////////
        ///////    MANIPULATE FORM ELEMENTS     /////////////////////////////
        /////////////////////////////////////////////////////////////////////
        
        // clear autocompelets results
        $this.hideAcWidgetExtras = function() {
            //acPopDownResultsDiv.html('');
            acPopDownResultsDiv.hide();
            $this.hideLoadIndicator();
        }; // END function hideAcWidgetExtras
        
        
        // clear the input field
        $this.clearInputField = function() {
            acSearchField.val("");
            newLocSet = false;
//            if (acSearchField.val() == initialString) {
//            }
        }; // END function clearInputField
        
        
        // reset the input field to initial value
        $this.resetInputFieldIfEmpty = function() {
            if (acSearchField.val() === '') {
                acSearchField.val(initialString);
            }
        }; // END function resetInputFieldIfEmpty
        
        
        //show a helpful message
        $this.showHelpMessage = function() {
            if (acSearchField.val() === '') {
                acPopDownResultsDiv.html('<div class="acHelp">' + helpMessage + '</div>');
                acPopDownResultsDiv.css('width', acSearchField.outerWidth());
                acPopDownResultsDiv.show();
                $this.hideLoadIndicator();
            }
        }; // END function showHelpMessage
        
        
        // set selected location id in hidden form element
        $this.setAndAddHidPostEl = function(aTag) {
			if (newLocSet === true) {
				return;
			}
            var selectedCityId = $(aTag).attr('locId');
            
            if(options.postID || options.postName) {
                if ($('#'+options.postID).val() === undefined) {
                    var hiddenInput = $(document.createElement('input'));
                    hiddenInput.attr('type','hidden');
                    if(options.postID) hiddenInput.attr('id',options.postID);
                    if(options.postName) hiddenInput.attr('name',options.postName);
                    hiddenInput.attr('value',selectedCityId);

                    acSearchField.after(hiddenInput);
                    // store "last good" values here
                    $this.setLastKnownGoodVals(lastGoodDisplayStr, lastGoodHidVal);
                } else {
                    $('#'+options.postID).val(selectedCityId);
                } 
                if (displayTheId) {
                    $this.displaySelectedId(aTag);
                }
           }
           newLocSet = true;
           if (afterAddCallback) {
                afterAddCallback(options.fieldID, selectedCityId, acSearchField.val());
           }
        }; // END function setAndAddHidPostEl
        
        
        // with the displayTheId var set to true, display the "hidden value" in a 
        //  newly created text-field 
        $this.displaySelectedId = function(aTag){
            var selectedCityId = $(aTag).attr('locId');
            var a = $(aTag).parent();
            var b = $(a).siblings().get(0);
            var masterInputId = $(b).attr('id');
            var newInput = $('<input type="text">');
            newInput.attr('id',"cid_"+masterInputId);
            newInput.attr('name',"cid_"+masterInputId);
            newInput.attr('value',selectedCityId);
            acSearchField.css('display','inline');
            acSearchField.css('margin-right','24px');
            // find and remove existing one, if necessary (eg. if this field had been used already)
            $("#cid_"+masterInputId).remove();
            // add the new one
            acSearchField.after(newInput);
        }; // END $this.displaySelectedId = function(aTag)
        
        $this.initWidget = function(){
            $this.removeHidPostEl();
            $this.clearInputField();
            $this.hideAcWidgetExtras();
        };
        
        
        // set last know good values
        $this.setLastKnownGoodVals = function(lastStr, lastId) {
            lastGoodDisplayStr = lastStr;
            lastGoodHidVal = lastId;

            if (typeof options.onSelectJSFunction == 'function') {
                options.onSelectJSFunction();
            } else if ((typeof options.onSelectJSFunction == 'string') &&
                        (options.onSelectJSFunction != '') &&
                        (eval('typeof ' + options.onSelectJSFunction) == 'function')) {
                options.onSelectJSFunction();
            }

        };
        
        
        // remove hidden form element
        $this.removeHidPostEl = function() {
            $('#'+options.postID).remove();
            newLocSet = false;
        }; // END function removeHidPostEl
        
        
        // show the loading indicator
        $this.showLoadIndicator = function() {
            $this.working = true;
            acSearchField.css('background-image','url('+options.loaderUrl+')');
        }; // END function showLoadIndicator
        
        
        
        // hide the loading indicator
        $this.hideLoadIndicator = function() {
            $this.working = false;
            acSearchField.css('background-image','none');
        }; // END function hideLoadIndicator



        $this.clearLastGoodData = function() {
            lastGoodDisplayStr = "";
            lastGoodHidVal = "";
        }; // END function hideLoadIndicator


        /////////////////////////////////////////////////////////////////////
        ///////    **END**  MANIPULATE FORM ELEMENTS     ////////////////////
        /////////////////////////////////////////////////////////////////////

		// helper method
		function isArray(mixed_var) {
		    var key = '';
		    if (!mixed_var) { return false; }
		     if (typeof mixed_var === 'object') {
		        if (mixed_var.hasOwnProperty) {
		            for (key in mixed_var) {
		                if (false === mixed_var.hasOwnProperty(key)) {
		                    return false;
		                }
		            }
		        }
		        return true;
		    }
		    return false;
		}

        if (selfStartOnStartString & (initialString !== '')) {
            $this.runFindOneId(initialString);
        }

    }); // return $(this).each(function(opts)...
};  // end fn.autocomplete = function()...

// plugin defaults - added as a property on our plugin function
$.fn.autocompleteMh.defaults = {
  loaderUrl:    assetDirectory + "images/ui/ajax-loader.gif"
};

// var for use with settimeout, so the ajax call won't happen too frequently
$.fn.autocompleteMh.timing  = null;
$.fn.autocompleteMh.acCounter = 0;

})(jQuery);

