/**
 * Decorator for the Ajax.Autocompleter class. Allows to work with JSON responses instead of final "<ul><li>..."
 * fragments produced on server-side (cf. Ajax.Autocompleter).
 */
Ajax.Autocompleter.JSONInterceptor = {
    /**
     * @function ? Enhances the given the given autocompleter control with this decorator.
     * @param {Ajax.Autocompleter} control Autocompleter control which will be decorated.
     * @param {function} interceptor Function producing the required "<ul><li>..." fragment. It is called between the
     * reception of the JSON response and the render operation.
     */
    enhance: function(control, interceptor) {
        var updateChoices = control.updateChoices;
        interceptor = interceptor.bind(control);
        control.updateChoices = function(response) {
            response	 = eval(response);
            var iRep = interceptor(response)
            if (iRep != ""){
            	updateChoices.apply(control, [iRep]);
            }
        };
    }
};


/** Widget which helps the user to find airports (IDD 4.1.1). */
AirportCompleter = Class.create(Ajax.Autocompleter, {
    listeners: null,

    initialize: function($super, field, choiceList) {
        // Super-class constructor call.
        $super(field, choiceList, "/adr/AirportSearch.do", {
            minChars: 3,
            parameters: "json=1",
            paramName: "airport",
            afterUpdateElement: this.afterUpdateElement.bind(this),
            onHide: function(field, layer) {
                if(!this._done) {
                    var length = this.getToken().length;
                    if(length >= this.options.minChars) {
                        this.selectEntry();
                    }
                }
                this._done = false;
                // Actually hide the suggestion layer like it would be done if we were not using the "onHide" option.
                layer.hide();
                moveFocus(field.id);
            }.bind(this),
            frequency: 0.05,
            // Overriding the default "onShow" method in order to get rid of the "Appear" effect.
            onShow: function(element, update) {
                if(!update.style.position || (update.style.position != "absolute")) {
                    update.style.position = "absolute";
                    var fieldPosition = element.positionedOffset();
                    update.setStyle({
                		top: (fieldPosition[1] + element.offsetHeight) + "px",
                		left: fieldPosition[0] + "px"
                    });
                }
                update.show();
            }
        });
		this.cachedResponse = null;
        this.listeners = [];
        this.suggestions = [];

        // If the user clears the field, the corresponding code must be cleared
        Event.observe(this.element, "blur", function(event) {
            if ($(this.element.id).value.length < 3)
            	$(this.element.id + "Code").value = '';
        }.bind(this));


        Ajax.Autocompleter.JSONInterceptor.enhance(this, function(response) {
            // Store the suggestions for later usage.
            var tmpSuggestions = (response || []);
            this.suggestions = [];

            // Produce the "<ul><li>..." fragment.
            var fragment = "<ul>";
            var matchFound = false;
            
            tmpSuggestions.each(function(result, index) {
	            var subfragment = "<li><div class=\"code\"><div>" + result.code + " </div></div>";
	            subfragment += "<div class=\"desc\"><div class=\"name\">" + result.name + "</div>, ";
	            subfragment += (result.state!="")?"<div>" + result.state + "</div>, ":"";
	            subfragment += "<div>" + result.country + "</div></div>";
	            subfragment += "<div class=\"clear\">&#160;</div></li>";
	            
	            subfragment = this.highlight(subfragment,this.getToken());
		        
		        if(subfragment.match(/<span/gi)){
		        	fragment += subfragment;
		        	matchFound = true;
		        	this.suggestions.push(result);
		        }

	        }, this);
	        
	        fragment += "</ul>";
	        
        	this.update.setStyle({height: (this.suggestions.length > 10) ? "174px" : "auto"});

            // Display the help box with an error message if there is no suggestion.
            if(!matchFound) {
                this.helpBox.show("<div class='message error'>" + locationNotFoundLabel + "</div><br/>");
                this.update.hide();
            } else {
            	this.helpBox.hide();
            }

            if (matchFound)
            	return fragment;
            else
            	return "";
        });

        this.helpBox = new AirportCompleter.HelpBox(this);
    },

    afterUpdateElement: function(field, item) {
    	// Retrieve the corresponding suggestion.
        var choice = this.suggestions[item.previousSiblings().length];

		if (choice){
	        $(field.id).value = AirportCompleter.templates["choice"].evaluate(Object.extend(choice, {
	            state: choice.state.empty() ? null : (choice.state + ", ")
	        }));
	
	        $(field.id + "Code").value = choice.code;
	        this._done = true;
	
	        this.notify("entrySelected", choice.code);
	    } else {
	        $(field.id + "Code").value = "";	    
	    }
    },

    /** @function ? Highlights the input occurrences in the given string. */
    highlight: function(string, input) {

        var result = string.replace(new RegExp(">(" + input.replace("\.","\\.") + ")", "gi"), "><span class='match'>$1</span>");
        
		var secondPart = input.split(" ")[1];
		
        if(AirportCompleter.keywords[input]){
	        AirportCompleter.keywords[input].each(function(keyword) {
            	if (secondPart)
                	result = result.replace(new RegExp(">(" + keyword + " " + secondPart + ")", "gi"), "><span class='match'>$1</span>");
                else
                	result = result.replace(new RegExp(">(" + keyword + ")", "gi"), "><span class='match'>$1</span>");
	        });
        }
        return result;
    },

    addListener: function(listener) {
        this.listeners.push(listener);
    },

	// Method overwritten caching purpose.
	getUpdatedChoices: function() {
    	this.startIndicator();
    	var entry = encodeURIComponent(this.options.paramName) + '=' +
    	  encodeURIComponent(this.getToken());

    	this.options.parameters = this.options.callback
			? this.options.callback(this.element, entry)
			: entry;

		if(this.options.defaultParams) {
			this.options.parameters += '&' + this.options.defaultParams;
		}

		if(this.cachedResponse) {
			this.updateChoices(this.cachedResponse);
		}
		else {
			new Ajax.Request(this.url, this.options);
		}
	},
	
	getToken: function() {
		var bounds = this.getTokenBounds();
		return this.element.value.substring(bounds[0], bounds[1]);
	},

	// Method overwritten caching purpose.
	onComplete: function(request) {
		this.cachedResponse = request.responseText;
		this.updateChoices(request.responseText);
	},

	// Method overwritten caching purpose.
	onObserverEvent: function() {
		this.changed = false;
		this.tokenBounds = null;
//		if(this.getToken().length >= this.options.minChars) {
		if(this.element.value.length >= this.options.minChars) {
			this.getUpdatedChoices();
		}
		else if((this.element.value.length < this.options.minChars) && (this.getToken().length > 0)) {
			this.cachedResponse = null;
           	if(!this.helpBox.element.visible()) {
           		this.helpBox.show();
           		this.update.hide();
           	}
		}
		else {
			this.active = false;
			this.hide();
		}


		this.oldElementValue = this.element.value;
	},

    /**
     * @function ? Notifies this completer's listeners about the given event.
     * Note: if they are any additional argument after "event", they are all passed to the listeners.
     * @param {object} event Event name.
     */
    notify: function(event) {
        for(var index = 0; index < this.listeners.length; index++) {
            var method = "notify" + event.charAt(0).toUpperCase() + event.substring(1);
            if(method = this.listeners[index][method]) {
                method.apply(null, $A(arguments).without(event));
            }
        }
    },

    toString: function() {
        return "AirportCompleter";
    }
});

Object.extend(AirportCompleter, {
    /** Template used to display a choice in the suggestion list. */
    templates: {
        suggestion: new Template("<div class=\"code\">%code%</div>"
            + "<div class=\"name\">%name%</div>"
            + "<div class=\"country\">%country%</div>"
            + "<div class=\"state\">%state%</div>"
            + "<div style=\"clear: both; width: 0; height: 0\"></div>", /(^|.|\r|\n)(%\s*(\w+)\s*%)/),
        choice: new Template("%code% | %name%, %state%%country%", /(^|.|\r|\n)(%\s*(\w+)\s*%)/)
    },

    /**
     * Default keywords (cf. XSL for actual keywords). Keywords are special patterns that trigger the highlighting of
     * some suggestions fragment.
     */
//    keywords: {
//        "(^|[^\w])(st\.|st\-|st\s|ste)": "(^|[^\w])(saint|st|ste|sainte)"
//    }
    keywords: {
        "(st\.|st\-|st\s|ste)": "(saint|st|ste|sainte)"
    }

});




/** @class ? Help box for airport completers (IDD 4.1.1). */
AirportCompleter.HelpBox = Class.create(Layer, {
    completer: null,

    initialize: function($super, completer) {
        this.completer = completer;

/*
        var observer = function(event) {
            var length = this.element.value.length;
            if((length > 0) && ((length < this.options.minChars) || (this.suggestions.length < 1))) {
            	if(!this.helpBox.element.visible()) {
            		this.helpBox.show();
            	}
            }
            else {
           		this.helpBox.hide();
            }
        }.bind(completer);
        Event.observe(completer.element, "keyup", observer);
        Event.observe(completer.element, "focus", observer);
*/
        Event.observe(completer.element, "blur", function(event) {
            this.hide();
        }.bind(this));

        this.element = new Element("div", {"class": "airportCompleterHelpBox"});
        this.element.update(AirportCompleter.HelpBox.template.evaluate({}));
        this.element.hide();
        this.completer.update.insert({after: this.element});
        $super(this.element);
    },

    show: function($super, message) {
        if(message) {
            this.element.update(AirportCompleter.HelpBox.template.evaluate({message: message}));
        }
        else {
            this.element.update(AirportCompleter.HelpBox.template.evaluate({}));
        }
        var fieldPosition = this.completer.element.positionedOffset();
        this.element.setStyle({
    		top: (fieldPosition[1] + this.completer.element.offsetHeight) + "px",
    		left: fieldPosition[0] + "px"
        });

        $super();
    }
});

Object.extend(AirportCompleter.HelpBox, {
    template: new Template("%message%<b>Enter the first 3 characters of the:</b><br/><ul><li>- City name</li><li>- Airport code or name</li><li>- Province or State name</li><li>- Country name</li></ul><br/>"
                           + "<div class='message warning'>localization required</div>", /(^|.|\r|\n)(%\s*(\w+)\s*%)/)
});


/** @class ? City picker juxtaposed to airport search fields. */
CityPicker = Class.create(Dialog, {
    trigger: null,

    initialize: function($super) {
        $super("cityPicker", {title: null});
        this.type = "AJAX";
    },

    show: function($super, trigger) {
        this.trigger = $(trigger);
        $super();
    },

    update: function() {
        this.request("/adr/getCityPicker.do", {
            onSuccess: function(response) {
                this.getContent().update(response.responseText);
                this.center();
                // Give the focus to the search field.
                (function() { this.getContent().down("input").focus(); }.bind(this)).delay(0.5);
            }.bind(this)
        });
       Event.observe($("cityPicker"), 'keypress', this.anyKey.bind(this));
    },

    /** @function ? Applies the user choice to the field where the CityPicker was invoked. */
    applyChoice: function() {
        var list = $("citySelectEl");
        var choice = list.options[list.selectedIndex];
        
        // Replacing the city code at the beginning of the string.
        var edited = choice.text.trim();
        var cityCode = edited.substring(edited.length-3);
        edited = cityCode + " | " + edited;
        edited = edited.substring(0, edited.length - 5).trim();;
        this.trigger.value = edited;
        
        $(this.trigger.id + "Code").value = choice.value;
        this.hide();
    },

    /** @function ? Highlights the first corresponding item in the city list according to the user input. */
    highlight: function(input) {
        var expression = new RegExp("^" + Pattern.escape(input), "i");
        var list = $("citySelectEl");
        var item = $A(list.options).find(function(item) {
            return item.text.match(expression) || item.value.match(expression);
        });
        if(item) {
            item.selected = true;
        }
        else {
            list.selectedIndex = -1;
        }

    },

    selectCity: function() {   
        var list = $("citySelectEl");
        var choice = list.options[list.selectedIndex];
        $('cityPickerInput').value = choice.text;
    },

   anyKey: function(event) {
 		switch (event.keyCode) {
 			case Event.KEY_RETURN:
				this.applyChoice();
				break;
			case Event.KEY_UP:
				this.selectCity();
				break;
			case Event.KEY_DOWN:
				this.selectCity();
				break;
 			}
    }
});

Singleton.decorate(CityPicker);


/** @class ? Layer containing a list of nearby airports according to a given destination (IDD 4.1.1.2.8). */
NearbyAirportsLayer = Class.create(Layer, {
    initialize: function($super) {
        var dummy = new Element("div");
        dummy.update(NearbyAirportsLayer.template.evaluate({
            id: "nearbyAirportsLayer"
        }));
        $super(dummy.down());
        $(document.body).append(this.element);
    },

    update: function(airports) {
        var template = new Template('<div class="airport">'
            + '<input type="radio" id="nearbyAirport%index%" name="nearbyAirport%index%"/>'
            + '<label for="nearbyAirport%index%">%name% (%code%)</label>'
            + '</div>'
            + '<div class="distance">%distance%' + dl.BlockNearbyAirports.LabelFrom + '%origin%</div>',
            /(^|.|\r|\n)(%\s*(\w+)\s*%)/);

        // Remove the previous results.
        this.element.select("div.airport, div.distance").invoke("remove");

        // "onlyCollections" parameter being "true" during the XML/JSON transformation implies that every XML child
        // element is wrapped into a collection, even if it is alone, like «airports["Distance"]» below.
        // Browsing the results with "each" is then easy.
        airports["Distance"].each(function(distance, index) {
            this.element.down("div.footer").insert({before:
                template.evaluate({
                    index: index,
                    name: distance.airport[0]["#TEXT"],
                    code: distance.airport[0].code,
                    distance: distance["#TEXT"]
                })
            });
        }, this);
    }
});

Localization.addStrings({
    "nearbyAirportsLayer.title": "Nearby Airports",
    "nearbyAirportsLayer.subject": "Why not fly into the following nearby cities or airports?",
    "nearbyAirportsLayer.search": "search"
});

Object.extend(NearbyAirportsLayer, {
    template: new Template('<div id="%id%" style="display: none;">'
        + '<div class="title">' + Localization.getString("nearbyAirportsLayer.title") + '</div>'
        +     '<div class="content">'
        +         '<div class="subject">' + Localization.getString("nearbyAirportsLayer.subject") + '</div>'
        // here will be the nearby airport descriptions
        +         '<div class="footer">'
        +             '<button type="button" onclick="">' + Localization.getString("nearbyAirportsLayer.search") + '</button>'
        +         '</div>'
        +     '</div>'
        + '</div>',
        /(^|.|\r|\n)(%\s*(\w+)\s*%)/)
});

Singleton.decorate(NearbyAirportsLayer);
