// location fields: html fields (hidden) in which location info is stored; id-s or objects
function GeoSearch(container, mapContainerId, searchInputField, textInputField,
                    locationFormFields, settings, ipLocation)
{
    this.container = Common.isString(container) ? $('#' + container) : container;
    this.mapContainerId = mapContainerId;

    this.searchInputField = searchInputField;
    this.getContentFunction = $.isFunction(textInputField) ? textInputField : null;
    this.textInputField = this.getContentFunction === null ? (Common.isString(textInputField) ? $('#' + textInputField) : textInputField) : null;
    this.isText = this.getContentFunction !== null || this.textInputField !== null;

    this.isExif = false;
    this.exifLongitude = 181;
    this.exifLatitude = 91;

    this.settings = {
        hideMapTypeBtns: false
    };
    if (settings) {
        for (var idx in settings) {
            this.settings[idx] = settings[idx];
        }
    }

    // name search (outer)
    this.nameSearchString = null;
    this.nameFoundLocations = null;
    this.nameSelectedLocationIdx = -1;
    // name search (form)
    this.nameFormSearchString = null;
    this.nameFormFoundLocations = null;
    this.nameFormSelectedLocationIdx = -1;
    this.forceUpdateNameSearch = false;
    // text or exif search
    this.textSearchString = null;
    this.textFoundLocations = null;
    this.textSelectedLocationIdx = -1;
    // location from ip
    this.ipLocation = ipLocation ? GeoSearch.responseToGeoLocation([ipLocation])[0] : null;

    this.selectedLocation = null;
    this.newSearch = false;

    this.mode = GeoSearch.MODE_UNDEFINED;
    this.display = GeoSearch.DISPLAY_NONE;

    this.mapLoaded = 0;
    this.mapLoading = false;
    this.mapsToLoad = [
        //PLACE_NAME_URL + '?type=gmap&key=ABQIAAAA3owUOlrbVbg9wmeCpsy4mhQpnfIP_q5mdE1UMRq8isgKrhvjJBS9c6aczLfjUPERT6tvZiGUfeJGew',
        MEDIA_URL + 'js/geo/map.js'
    ];

    this.map = null;
    this.mapZoom = -1;
    this.mapLongitude = 181;
    this.mapLatitude = 91;
    this.lastHoverPoint = null;
    this.lastSetPoint = null;
    this.ignoreMapPlacesCallback = false;
    this.selectedLocationFromMap = null;
    this.locationFormFields = locationFormFields == undefined ? null : locationFormFields;

    this.cntLocationInfo = this.container.children(':first');
    this.cntTabs = this.cntLocationInfo.next();
    this.cntForm = this.cntTabs.next();
    this.cntInputRow1 = this.cntForm.children(':first').children(':first');
    this.cntInputRow1.children(':first').val('');
    this.cntInputRow2 = this.cntInputRow1.next();
    this.cntInputRow2.children(':first').val('').hide();
    this.cntInstructions = this.cntInputRow2.next();
    this.cntResults = this.cntInstructions.next();

    this.initForm();

    var me = this;
    GeoSearch.getCountries(function() { me.initLocation(); });
}



//================================================================================
GeoSearch.prototype.getSearchText = function() {
    return Common.htmlspecialchars_decode(this.getContentFunction === null ?
        (this.textInputField === null ? '' : this.textInputField.val()) :
        this.getContentFunction()).strip_tags();
};


//================================================================================
// initializes location on page load from form fields
//================================================================================
GeoSearch.prototype.initLocation = function() {
    if (this.selectedLocation = this.locationFromForm()) {
    	this.nameSearchString = this.selectedLocation.city +
    							(this.selectedLocation.country ? (this.selectedLocation.city ? ', ' : '') +
    																this.selectedLocation.country
    															: '');
        this.searchInputField.val(this.nameSearchString);
    	if (this.selectedLocation.validCoordinates()) {
	        this.nameFoundLocations = [this.selectedLocation];
	        this.nameSelectedLocationIdx = 0;
	        this.forceUpdateNameSearch = true;
	        this.copyNameSearchResults(true);
	        this.cntInputRow1.children(':first').val(this.selectedLocation.city);
	        this.displayLocationInfo(GeoSearch.MODE_SEARCH);
    	}
    	else {
    		//console.log('Location is set but has no valid coordinates, gonna search');
    		this.selectedLocation = null;
    		this.nameSearchString = null;
    		this.searchByName();
    	}
    }
    if (!this.selectedLocation) {
        if (this.ipLocation) this.displayLocationInfo(GeoSearch.MODE_IP);
        else this.getLocationFromIP();
    }
};


//================================================================================
GeoSearch.prototype.getLocationFromIP = function() {
    var _this = this;
    Ajax.getLocationFromIP(function(resp) { _this.getLocationFromIPCallback(resp); });
};


//================================================================================
GeoSearch.prototype.getLocationFromIPCallback = function(resp) {
    resp = GeoSearch.responseToGeoLocation(resp);
    if (resp) {
        this.ipLocation = resp[0];
        this.displayLocationInfo(GeoSearch.MODE_IP);
    }
};


//================================================================================
// retrieves location from form fields
//================================================================================
GeoSearch.prototype.locationFromForm = function() {
    var fld, tmpLoc = null;
    //debugout('locationFromForm: ' + this.locationFormFields);
    if (this.locationFormFields) {
        tmpLoc = new GeoLocation();
        if (Common.isString(this.locationFormFields.city) &&
            (fld = document.getElementById(this.locationFormFields.city)))
        {
            tmpLoc.city = fld.value.trim();
        }
        if (Common.isString(this.locationFormFields.countryId) &&
            (fld = document.getElementById(this.locationFormFields.countryId)))
        {
            tmpLoc.setCountryId(fld.value.trim());
        }
        if (Common.isString(this.locationFormFields.stateId) &&
            (fld = document.getElementById(this.locationFormFields.stateId)))
        {
            tmpLoc.setStateId(fld.value.trim());
        }
        if (Common.isString(this.locationFormFields.longitude) &&
            (fld = document.getElementById(this.locationFormFields.longitude)))
        {
            tmpLoc.setLongitude(fld.value.trim());
        }
        if (Common.isString(this.locationFormFields.latitude) &&
            (fld = document.getElementById(this.locationFormFields.latitude)))
        {
            tmpLoc.setLatitude(fld.value.trim());
        }
        if (!tmpLoc.isOneSet()) tmpLoc = null;
    }
    return tmpLoc;
};


//================================================================================
// returns selected location
//================================================================================
GeoSearch.prototype.getLocation = function() {
    return this.selectedLocation;
};


//================================================================================
GeoSearch.prototype.displayForm = function(display, mode, itemLoaded) {
    if (display == undefined)
        display = this.display !== GeoSearch.DISPLAY_FORM;

    if (display &&
        this.mapLoaded < this.mapsToLoad.length)
    {
        if (itemLoaded == undefined) {
            if (!this.mapLoading) {
                this.mapLoading = true;
                var me = this;
                $.getScript(this.mapsToLoad[0], function() { me.displayForm(display, mode, true); });
            }
            return;
        }
        else {
            ++this.mapLoaded;
            if (this.mapLoaded < this.mapsToLoad.length) {
                var me = this;
                $.getScript(this.mapsToLoad[this.mapLoaded], function() { me.displayForm(display, mode, true); });
                return;
            }
            this.mapLoading = false;
            PLACE_NAME_URL = '/geosearch/';
        }
    }

    if (display) {
        if (this.display !== GeoSearch.DISPLAY_FORM) {
            if (mode == undefined) {
                if (this.isExif) mode = GeoSearch.MODE_MANUAL;
                else if (this.isText) mode = GeoSearch.MODE_TEXT;
                else mode = GeoSearch.MODE_SEARCH;
            }
            this.display = GeoSearch.DISPLAY_FORM;
            this.container.trigger('toggledisplay', [true]);
            this.cntLocationInfo.hide();
            this.cntTabs.show();
            this.cntForm.show();
            this.setMode(mode, true);
        }
    }
    else if (this.display === GeoSearch.DISPLAY_FORM) {
        this.mapZoom = -1;
        this.mapLongitude = 181;
        this.mapLatitude = 91;
        this.cntForm.hide();
        this.cntTabs.hide();
        if (Common.isArray(this.nameFoundLocations) ||
            Common.isArray(this.textFoundLocations) ||
            (this.ipLocation && this.ipLocation == this.selectedLocation))
        {
            //this.displayLocationInfo();
            this.cntLocationInfo.show();
        }
        else this.cntLocationInfo.hide();
        this.display = GeoSearch.DISPLAY_NONE;
        this.container.trigger('toggledisplay', [false]);
    }
};


//================================================================================
// populates search result list in form
//================================================================================
GeoSearch.prototype.populateSearchResultList = function(mode) {
    //debugout('populateSearchResultList');
    this.cntResults.html('');
    var tmpLoc, n;
    switch (mode) {
        case GeoSearch.MODE_SEARCH:
            //debugout('populateSearchResultList, mode search, ' + (Common.isArray(this.nameFormFoundLocations) ? this.nameFormFoundLocations.length : 0));
            if (Common.isArray(this.nameFormFoundLocations)) {
                for (n = 0; n < this.nameFormFoundLocations.length; ++n) {
                    tmpLoc = this.nameFormFoundLocations[n];
                    this.cntResults.append('<li' + (n == this.nameFormSelectedLocationIdx ? ' class="selected"' : '') + '>' +
                                            tmpLoc.city +
                                            ', ' + tmpLoc.country +
                                            '</li>');
                    this.appendFoundLocOnClick(n, tmpLoc);
                }
            }
            break;
        case GeoSearch.MODE_TEXT:
                if (Common.isArray(this.textFoundLocations)) {
                for (var n = 0; n < this.textFoundLocations.length; ++n) {
                    tmpLoc = this.textFoundLocations[n];
                    this.cntResults.append('<li' + (n == this.textSelectedLocationIdx ? ' class="selected"' : '') + '>' +
                                            tmpLoc.city +
                                            ', ' + tmpLoc.country +
                                            '</li>');
                    this.appendFoundLocOnClick(n, tmpLoc);
                }
            }
            break;
    }
};


//================================================================================
// appends onclick handler on the last element in the form search result list
//================================================================================
GeoSearch.prototype.appendFoundLocOnClick = function(idx, loc) {
    var me = this;
    this.cntResults.children(':last').click(function() { me.selectLocation(idx); }).
        hover(function() { me.showPoint(loc.longitude, loc.latitude, loc.city); },
            function() { me.hidePoint(); });
};


//================================================================================
// selects location in the form search result list (but does not set the location)
//================================================================================
GeoSearch.prototype.selectLocation = function(idx) {
    //debugout('selectLocation ' + idx);
    var tmpLoc;
    switch (this.mode) {
        case GeoSearch.MODE_SEARCH:
            this.nameFormSelectedLocationIdx = idx;
            this.cntResults.children().each(function(i) { this.className = i == idx ? 'selected' : ''});
            if (idx >= 0) {
                tmpLoc = this.nameFormFoundLocations[idx];
                this.setPoint(tmpLoc.longitude, tmpLoc.latitude, tmpLoc.city);
            }
            break;
        case GeoSearch.MODE_TEXT:
            this.textSelectedLocationIdx = idx;
            this.cntResults.children().each(function(i) { this.className = i == idx ? 'selected' : ''});
            if (idx >= 0) {
                tmpLoc = this.textFoundLocations[idx];
                this.setPoint(tmpLoc.longitude, tmpLoc.latitude, tmpLoc.city);
            }
            break;
    }
};


//================================================================================
GeoSearch.prototype.appendFoundTextLocOnClick = function(idx) {
    var me = this;
    var tmpLoc = this.nameFoundLocations[idx];
    this.cntResults.children(':last').click(function() { me.selectTextLocation(idx); }).
        hover(function() { me.showPoint(tmpLoc.longitude, tmpLoc.latitude, tmpLoc.city); },
            function() { me.hidePoint(); });
};


//============================================================================================================
GeoSearch.prototype.selectTextLocation = function(idx) {
    //debugout('selectLocation ' + idx);
    this.nameSelectedLocationIdx = idx;
    this.cntResults.children().each(function(i) { this.className = i == idx ? 'selected' : ''});
    if (idx >= 0) {
        var tmpLoc = this.nameFoundLocations[idx];
        this.setPoint(tmpLoc.longitude, tmpLoc.latitude, tmpLoc.city);
    }
};


//============================================================================================================
GeoSearch.prototype.showPoint = function(longitude, latitude, location) {
    if (this.lastHoverPoint !== null) {
        this.map.removeOverlay(this.lastHoverPoint);
        this.lastHoverPoint = null;
    }
    this.lastHoverPoint = this.map.addOverlay(latitude + " " + longitude, location);
};


//============================================================================================================
GeoSearch.prototype.hidePoint = function(all) {
    if (this.lastHoverPoint !== null) {
        this.map.removeOverlay(this.lastHoverPoint);
        this.lastHoverPoint = null;
    }
    if (all != undefined && all) {
        if (this.lastSetPoint !== null) {
            this.map.removeOverlay(this.lastSetPoint);
            this.lastSetPoint = null;
        }
    }
};


//============================================================================================================
GeoSearch.prototype.setPoint = function(longitude, latitude, location) {
    //debugout('set point ' + longitude + ', ' + latitude + ', ' + location);
    this.map.settings.overlayOpacity = undefined;
    this.hidePoint(true);
    this.lastSetPoint = this.map.addOverlay(latitude + " " + longitude, location, location, "point2.png");
    this.map.mapCenterTo({ lat: latitude, lon: longitude });
};


//================================================================================
GeoSearch.prototype.displayLocationInfo = function(mode) {
    //console.log('displayLocationInfo ' + mode);
    if (this.display !== GeoSearch.DISPLAY_FORM) {
        var message = '';
        var me = this;
        this.cntLocationInfo.html('').show();

        //debugout('loc from text: ' + (Common.isArray(this.textFoundLocations) ? this.textFoundLocations.length : 0));
        if (this.selectedLocation !== null) {
            this.cntLocationInfo.append(gettext('Selected location') + ': ' +
                                        this.selectedLocation.toString() +
                                        '&nbsp;&nbsp;&nbsp;&nbsp;<span>' + gettext('Remove') + '</span>' +
                                        '<br />');
            this.cntLocationInfo.children('span:last').click(function() { me.clearSelectedPlace(); });
        }

        if (mode == GeoSearch.MODE_IP) {
            if (!this.selectedLocation) {
                if (this.ipLocation) {
                    this.cntLocationInfo.append(gettext('IP location: %s <span>Select</span>').replace('%s', this.ipLocation.toString()) + '<br />');
                    this.cntLocationInfo.children('span:last').click(function() { me.setLocationOut(GeoSearch.MODE_IP); });
                }
                else this.cntLocationInfo.hide();
            }
            return;
        }

        var sumResults = (Common.isArray(this.nameFoundLocations) ? this.nameFoundLocations.length : 0) +
                        (Common.isArray(this.textFoundLocations) ? this.textFoundLocations.length : 0);
//                        (Common.isArray(this.textFoundLocations) ? this.textFoundLocations.length : 0) +
//                        (this.ipLocation ? 1 : 0);
        if (sumResults == 0) {
            switch (mode) {
                case GeoSearch.MODE_SEARCH:
                    this.cntLocationInfo.html(gettext('No location search match') +
                                            '.&nbsp;&nbsp;<span>' + gettext('Set location manually') + '</span>').
                    children('span:last').click(function() { me.displayForm(true, GeoSearch.MODE_MANUAL); });
                    break;
                case GeoSearch.MODE_TEXT:
                    this.cntLocationInfo.html(gettext('No location found from text'));
                    break;
                default:
                    this.cntLocationInfo.html(gettext('No location search match'));
                    break;
            }
        }
        else {
            switch (mode) {
                case GeoSearch.MODE_SEARCH:
                    if (Common.isArray(this.nameFoundLocations)) {
                        if (this.newSearch) {
                            this.cntLocationInfo.append(gettext('We found %s <span>Select</span>').replace('%s', this.nameFoundLocations[this.nameSelectedLocationIdx].toString()) + '<br />');
                            this.cntLocationInfo.children('span:last').click(function() { me.setLocationOut(GeoSearch.MODE_SEARCH); });
                            if (this.nameFoundLocations.length > 1) {
                                this.cntLocationInfo.append(ngettext("There is <span id=\"other_places\">%d other place</span> containing '%s'", "There are <span id=\"other_places\">%d other places</span> containing '%s'", this.nameFoundLocations.length - 1).replace('%s', Common.htmlspecialchars(this.nameSearchString)).replace('%d', this.nameFoundLocations.length - 1) + '<br />');
                                this.cntLocationInfo.children('span:last').click(function() { me.copyNameSearchResults(true); me.displayForm(true, GeoSearch.MODE_SEARCH); });
                            }
                        }
                        else if (this.nameFoundLocations.length > 1) {
                            this.cntLocationInfo.append(ngettext("There is <span id=\"other_places\">%d other place</span> containing '%s'", "There are <span id=\"other_places\">%d other places</span> containing '%s'", this.nameFoundLocations.length - 1).replace('%s', Common.htmlspecialchars(this.nameSearchString)).replace('%d', this.nameFoundLocations.length - 1) + '<br />');
                            this.cntLocationInfo.children('span:last').click(function() { me.copyNameSearchResults(true); me.displayForm(true, GeoSearch.MODE_SEARCH); });
                        }
                    }
                    if (Common.isArray(this.textFoundLocations)) {
                        this.cntLocationInfo.append(ngettext("There is <span>%d location</span> found from text", "There are <span>%d locations</span> found from text", this.textFoundLocations.length).replace('%d', this.textFoundLocations.length) + '<br />');
                        //this.cntLocationInfo.append("There are <span>" + this.textFoundLocations.length + " locations</span> found from text<br />");
                        this.cntLocationInfo.children('span:last').click(function() { me.displayForm(true, GeoSearch.MODE_TEXT); });
                    }
                    break;
                case GeoSearch.MODE_TEXT:
                    if (Common.isArray(this.textFoundLocations)) {
                        if (this.textSelectedLocationIdx >= 0) {
                            if (this.newSearch) {
                                //this.cntLocationInfo.append('We found ' + this.textFoundLocations[this.textSelectedLocationIdx].toString() + ' <span>Izberi</span><br />');
                                this.cntLocationInfo.append(gettext('We found %s <span>Select</span>').replace('%s', this.textFoundLocations[this.textSelectedLocationIdx].toString()) + '<br />');
                                this.cntLocationInfo.children('span:last').click(function() { me.setLocationOut(GeoSearch.MODE_TEXT); });
                                if (this.textFoundLocations.length > 1) {
                                    this.cntLocationInfo.append(ngettext("There is <span id=\"other_places\">%d other place</span> found from text", "There are <span id=\"other_places\">%d other places</span> found from text", this.textFoundLocations.length - 1).replace('%d', this.textFoundLocations.length - 1) + '<br />');
                                    this.cntLocationInfo.children('span:last').click(function() { me.displayForm(true, GeoSearch.MODE_TEXT); });
                                }
                            }
                            else if (this.textFoundLocations.length > 1) {
                                this.cntLocationInfo.append(ngettext("There is <span id=\"other_places\">%d other place</span> found from text", "There are <span id=\"other_places\">%d other places</span> found from text", this.textFoundLocations.length - 1).replace('%d', this.textFoundLocations.length - 1) + '<br />');
                                this.cntLocationInfo.children('span:last').click(function() { me.displayForm(true, GeoSearch.MODE_TEXT); });
                            }
                        }
                        else {
                            //this.cntLocationInfo.append("There are <span>" + this.textFoundLocations.length + " locations</span> found from text<br />");
                            this.cntLocationInfo.append(ngettext("There is <span>%d location</span> found from text", "There are <span>%d locations</span> found from text", this.textFoundLocations.length - 1).replace('%d', this.textFoundLocations.length - 1) + '<br />');
                            this.cntLocationInfo.children('span:last').click(function() { me.displayForm(true, GeoSearch.MODE_TEXT); });
                        }
                    }
                    if (Common.isArray(this.nameFoundLocations)) {
                        //this.cntLocationInfo.append('There are ' + this.nameFoundLocations.length + 'locations containing \'' + Common.htmlspecialchars(this.nameSearchString) + '\'<br />');
                        this.cntLocationInfo.append(ngettext("There is <span>%d place</span> containing '%s'", "There are <span>%d places</span> containing '%s'", this.nameFoundLocations.length).replace('%s', Common.htmlspecialchars(this.nameSearchString)).replace('%d', this.nameFoundLocations.length) + '<br />');
                        this.cntLocationInfo.children('span:last').click(function() { me.displayForm(true, GeoSearch.MODE_SEARCH); });
                    }
                    break;
            }
        }
        this.updateFormInfo(mode == GeoSearch.MODE_TEXT);
    }
};


//================================================================================
GeoSearch.prototype.setLocationOut = function(mode) {
    this.newSearch = false;
    switch(mode) {
        case GeoSearch.MODE_IP:
            this.selectedLocation = this.ipLocation;
            this.searchInputField.val(this.selectedLocation.city);
            this.nameSearchString = this.selectedLocation.city;
            this.cntInputRow1.children(':first').val(this.selectedLocation.city);
            this.forceUpdateNameSearch = true;
            this.displayLocationInfo(mode);
            this.locationToForm();
            break;
        case GeoSearch.MODE_SEARCH:
            this.selectedLocation = this.nameFoundLocations[this.nameSelectedLocationIdx];
            this.displayLocationInfo(mode);
            this.locationToForm();
            break;
        case GeoSearch.MODE_TEXT:
            this.selectedLocation = this.textFoundLocations[this.textSelectedLocationIdx];
            this.nameSearchString = this.selectedLocation.city;
            this.searchInputField.val(this.nameSearchString);
            this.nameFoundLocations = null;
            this.nameSelectedLocationIdx = -1;
            this.cntInputRow1.children(':first').val(this.nameSearchString);
            this.cntInputRow2.children(':first').val(this.nameSearchString);
            this.nameFormSearchString = this.nameSearchString;
            this.nameFormFoundLocations = null;
            this.nameFormSelectedLocationIdx = -1;
            this.forceUpdateNameSearch = true;

            this.displayLocationInfo(mode);
            this.locationToForm();
            break;
    }
};


//================================================================================
GeoSearch.prototype.updateFormInfo = function (highlight) {
    var txt = "";
    var venueTxt = "";
    var inputs = this.cntLocationInfo.parents(".collapsable").find("#ex_location input");

    for (var i = 0; i < inputs.length; i++) {
        if ($.trim(inputs[i].value) != "") {
            venueTxt += ((venueTxt.length > 0) ? ", " : "")
                    + $("label[for=" + inputs.eq(i).attr("id") + "]").text()
                    + ' ' + $.trim(inputs[i].value);
        }
    }

    if (this.cntLocationInfo.css("display") == 'none') {
        txt = (venueTxt == "") ? gettext("Location is not set.") : venueTxt;
    } else {
        var children = this.cntLocationInfo[0].childNodes;
        if (children) {
            for (var i = 0; i < children.length; i++) {
                if (children[i].nodeType == 3) {
                    txt += children[i].data + ' ';
                }
                if (typeof children[i].tagName == 'string'
                        && children[i].tagName.toUpperCase() == "BR") {
                    break;
                }
            }
        }
        if (venueTxt != "") {
            txt += venueTxt;
        }
        if ($("#other_places").text() != "") {

            txt += "." + gettext("We found ") + $("#other_places").text() + '.';
        }
    }
    this.cntLocationInfo.parents(".collapsable").find(".legend-info *").remove();
    this.cntLocationInfo.parents(".collapsable").trigger("legendInfo", [txt]);

    if (highlight) {
        var high = this.cntLocationInfo.parents(".collapsable").find(".legend-info");
        setTimeout(function () {
            high.wrapInner("<span></span>");
            high.find("> span").effect("highlight", { color: "#ffff33" }, 1500);
        }, 100);

    }
};


//================================================================================
// initializes search form and attach events on fields
//================================================================================
GeoSearch.prototype.initForm = function() {
    var me = this;
// create tabs
    this.cntTabs.html('').
                append('<li><a href="#">' + gettext("Search") + '</a></li>' +
                        '<li><a href="#">' + gettext("Select on map") + '</a></li>' +
                        '<li><a href="#">' + gettext("Set manually") + '</a></li>').
                children('li:first').children(':first').click(function() { me.setMode(GeoSearch.MODE_SEARCH); return false; }).
                parent().next().children(':first').click(function() { me.setMode(GeoSearch.MODE_SELECT); return false; }).
                parent().next().children(':first').click(function() { me.setMode(GeoSearch.MODE_MANUAL); return false; });

    if (this.isText) {
        this.cntTabs.append('<li><a href="#">' + gettext("Locations from content") + '</a></li>');
        this.cntTabs.children(':last').children(':first').click(function() { me.setMode(GeoSearch.MODE_TEXT); return false; });
    }
    if (!this.locationFromForm()) this.searchInputField.val(gettext('Enter place')).css('font-style', 'italic');
// events
    this.cntInputRow1.children(':button').click(function() { me.searchByName(); return false; }).
        prev().keypress(function(e) { return me.searchInputKeypressCheck(e); });
    this.cntInputRow2.children(':input:first').keypress(function(e) { return me.setInputKeypressCheck(e); });
    this.cntResults.next().click(function() { me.setLocation(); return false; });
    this.searchInputField.keypress(function(e) { return me.inputTriggerSearchByName(e); }).
        blur(function() { me.searchByName(); }).
        focus(function() { me.clearDefInputMsg(); });
};


//================================================================================
// clears selected place
//================================================================================
GeoSearch.prototype.clearSelectedPlace = function() {
    this.nameSearchString = null;
    this.nameFoundLocations = null;
    this.nameSelectedLocationIdx = -1;
    this.nameFormSearchString = null;
    this.nameFormFoundLocations = null;
    this.nameFormSelectedLocationIdx = -1;
    this.forceUpdateNameSearch = false;
    this.textSelectedLocationIdx = -1;
    this.selectedLocation = null;
    this.newSearch = false;
    this.locationToForm(true);
    this.cntLocationInfo.hide();
    GeoSearch.prototype.displayForm(false);
    this.searchInputField.val('').focus();
    this.updateFormInfo();
};


//================================================================================
// checks if extern place input field contains default value "Enter place" and removes it
//================================================================================
GeoSearch.prototype.clearDefInputMsg = function() {
    if (this.searchInputField.val() === gettext('Enter place')) {
        this.searchInputField.val('').css('font-style', 'normal');
    }
};


//================================================================================
// checks press event on extern place input field and triggers search by name
//================================================================================
GeoSearch.prototype.inputTriggerSearchByName = function(e) {
    switch (window.event ? window.event.keyCode : e.which) {
        case 10:
        case 13:
            this.searchByName();
            return false;
    }
    return true;
};


//================================================================================
GeoSearch.prototype.searchInputKeypressCheck = function(e) {
    switch (window.event ? window.event.keyCode : e.which) {
        case 10:
        case 13:
            this.searchByName();
            return false;
    }
    return true;
};


//================================================================================
GeoSearch.prototype.setInputKeypressCheck = function(e) {
    switch (window.event ? window.event.keyCode : e.which) {
        case 10:
        case 13:
            this.setLocation();
            return false;
    }
    return true;
};


//================================================================================
// set location selected in form
// this function should be called from form only
//================================================================================
GeoSearch.prototype.setLocation = function() {
    //debugout('setLocation');
    if (this.display !== GeoSearch.DISPLAY_FORM) return;
    this.forceUpdateNameSearch = true;
    this.newSearch = false;
    switch (this.mode) {
        case GeoSearch.MODE_SEARCH:
            if (this.nameFormSelectedLocationIdx >= 0) {
                this.selectedLocation = this.nameFormFoundLocations[this.nameFormSelectedLocationIdx];
                this.nameFormSearchString = this.selectedLocation.city;
                this.nameFormFoundLocations = [this.selectedLocation];
                this.nameFormSelectedLocationIdx = 0;
                this.copyNameSearchResults(false);
                this.searchInputField.val(this.nameFormSearchString);
                this.cntInputRow1.children(':first').val(this.nameFormSearchString);
                this.locationToForm();
                this.displayForm(false);
                this.displayLocationInfo(GeoSearch.MODE_SEARCH);
            }
            break;
        case GeoSearch.MODE_SELECT:
            if (this.selectedLocationFromMap) {
                this.selectedLocation = this.selectedLocationFromMap;
                this.nameFormSearchString = this.selectedLocation.city;
                this.nameFormFoundLocations = [this.selectedLocation];
                this.nameFormSelectedLocationIdx = 0;
                this.copyNameSearchResults(false);
                this.searchInputField.val(this.nameFormSearchString);
                this.cntInputRow1.children(':first').val(this.nameFormSearchString);
                this.locationToForm();
                this.displayForm(false);
                this.displayLocationInfo(GeoSearch.MODE_SEARCH);
            }
            break;
        case GeoSearch.MODE_MANUAL:
            if (this.selectedLocationFromMap) {
                var manualLocStr = this.cntInputRow2.children(':first').val().trim();
                if (!manualLocStr) {
                    alert(gettext('Please enter location name.'));
                    this.cntInputRow2.children(':first').get(0).focus();
                    return;
                }
                var tmp = manualLocStr.split(/\s*,\s*/);
                var tmpCountry = tmp.length > 1 ? tmp.splice(tmp.length - 1, 1) : '';
                if (manualLocStr.length == 0 || tmp.length == 1 && tmpCountry.length == 0) {
                    if (Common.isString(tmp[0])) this.selectedLocationFromMap.city = tmp[0];
                    this.selectedLocation = this.selectedLocationFromMap;
                    this.nameFormSearchString = this.selectedLocation.city;
                    this.nameFormFoundLocations = [this.selectedLocation];
                    this.nameFormSelectedLocationIdx = 0;
                    this.copyNameSearchResults(false);
                    this.searchInputField.val(this.nameFormSearchString);
                    this.cntInputRow1.children(':first').val(this.nameFormSearchString);
                    this.locationToForm();
                    this.displayForm(false);
                    this.displayLocationInfo(GeoSearch.MODE_SEARCH);
                }
                else {
                    var me = this;
                    Ajax.searchCountry(tmpCountry, GeoSearch.LANG, 1, function(resp) { me.setFoundCountry(resp, manualLocStr); });
                    return;
                }
            }
            break;
        case GeoSearch.MODE_TEXT:
            if (this.textSelectedLocationIdx >= 0) {
                this.selectedLocation = this.textFoundLocations[this.textSelectedLocationIdx];
                this.locationToForm();
                this.displayForm(false);
                this.displayLocationInfo(GeoSearch.MODE_TEXT);
            }
            break;
    }
    this.updateFormInfo(this.mode == GeoSearch.MODE_TEXT);
};


//================================================================================
// sets found country to selectedLocationFromMap and closes form
//================================================================================
GeoSearch.prototype.setFoundCountry = function(resp, manualLocStr) {
    var tmp = manualLocStr.split(/\s*,\s*/);
    if (Common.isArray(resp)) {
        tmp.splice(tmp.length - 1, 1);
        this.selectedLocationFromMap.countryId = resp[0].id;
        this.selectedLocationFromMap.country = resp[0].country;
    }
    var city = tmp.join(', ');
    if (city.length > 0) this.selectedLocationFromMap.city = city;
    this.selectedLocation = this.selectedLocationFromMap;
    this.nameFormSearchString = this.selectedLocation.city;
    this.nameFormFoundLocations = [this.selectedLocation];
    this.nameFormSelectedLocationIdx = 0;
    this.copyNameSearchResults(false);
    this.searchInputField.val(this.nameFormSearchString);
    this.cntInputRow1.children(':first').val(this.nameFormSearchString);
    this.locationToForm();
    this.displayForm(false);
    this.displayLocationInfo(GeoSearch.MODE_SEARCH);
    this.updateFormInfo();
};


//================================================================================
GeoSearch.prototype.searchByName = function(callback) {
    //console.log('searchByName');
    var query, country = '', me = this, splPos;

    if (this.display == GeoSearch.DISPLAY_FORM) {
        query = this.cntInputRow1.children(':first').val().trim();
        if (query.length > 0) {
            // get country from query if exists
            if ((splPos = query.lastIndexOf(',')) > 0) {
                country = query.substr(splPos + 1).trim();
                query = query.substring(0, splPos).trim();
            }
            //console.log('query: ' + query + ', country: ' + country);
            this.hidePoint(true);
            this.nameFormFoundLocations = null;
            this.nameFormSelectedLocationIdx = -1;
            this.nameFormSearchString = query;
            this.cntInstructions.text(gettext("Searching") + '...').show();
            Ajax.searchLocationByName(query, country, GeoSearch.MAX_GEOSEARCH_RESULTS, GeoSearch.LANG, function(resp) { me.searchByNameCallback(resp, callback); });
        }
    }
    else {
            //console.log('searching from outer')
        query = this.searchInputField === null ? '' : this.searchInputField.val().trim();
        if (query.length > 0 && query != this.nameSearchString) {
            // get country from query if exists
            if ((splPos = query.lastIndexOf(',')) > 0) {
                country = query.substr(splPos).trim();
                query = query.substring(0, splPos).trim();
            }
            //console.log('query: ' + query + ', country: ' + country);
            this.nameFoundLocations = null;
            this.nameSelectedLocationIdx = -1;
            this.nameSearchString = query;
            this.cntLocationInfo.text(gettext("Searching") + '...').show();
            Ajax.searchLocationByName(query, country, GeoSearch.MAX_GEOSEARCH_RESULTS, GeoSearch.LANG, function(resp) { me.searchByNameCallback(resp); });
        }
        this.newSearch = true;
    }
};


//================================================================================
GeoSearch.prototype.searchByNameCallback = function(resp, callback) {
    //debugout('searchByNameCallback, num found locs = ' + (Common.isArray(this.nameFoundLocations) ? this.nameFoundLocations.length : '0'));
    if (this.display === GeoSearch.DISPLAY_FORM) {
        this.nameFormFoundLocations = GeoSearch.responseToGeoLocation(resp);
        if ($.isFunction(callback)) callback();
        else if (this.map !== null) {
            this.populateSearchResultList(GeoSearch.MODE_SEARCH);
            if (Common.isArray(this.nameFormFoundLocations)) {
                this.cntInstructions.text(gettext("Places found") + ':');
                this.setMapBounds(this.nameFormFoundLocations);
            }
            else this.cntInstructions.html(gettext('No location search match'));
        }
    }
    else {
        this.nameFoundLocations = GeoSearch.responseToGeoLocation(resp);
        if (Common.isArray(this.nameFoundLocations)) {
            this.nameSelectedLocationIdx = 0;
            if (this.selectedLocation === null) {
                this.selectedLocation = this.nameFoundLocations[this.nameSelectedLocationIdx];
                this.locationToForm();
                this.newSearch = false;
            }
        }
        else this.newSearch = false;
        this.copyNameSearchResults(true);
        this.displayLocationInfo(GeoSearch.MODE_SEARCH);
    }
};


//================================================================================
GeoSearch.prototype.searchFromText = function(callbackFunction) {
    var me = this;
    var query = this.getSearchText();
    if (query.length > 0 &&
        query !== this.textSearchString)
    {
        if (this.display === GeoSearch.DISPLAY_FORM) {
            this.hidePoint(true);
            this.textFoundLocations = null;
            this.textSelectedLocationIdx = -1;
            this.textSearchString = query;
            this.cntInstructions.text(gettext("Searching") + '...').show();
            Ajax.searchLocationFromText(query, GeoSearch.MAX_GEOSEARCH_RESULTS, GeoSearch.LANG, function(resp) { me.searchFromTextCallback(resp, callbackFunction); });
        }
        else {
            this.textFoundLocations = null;
            this.textSelectedLocationIdx = -1;
            this.textSearchString = query;
            if (!$.isFunction(callbackFunction)) this.cntLocationInfo.text(gettext("Searching") + '...').show();
            Ajax.searchLocationFromText(query, GeoSearch.MAX_GEOSEARCH_RESULTS, GeoSearch.LANG, function(resp) { me.searchFromTextCallback(resp, callbackFunction); });
            this.newSearch = true;
        }
    }
    else if ($.isFunction(callbackFunction)) callbackFunction();
};


//================================================================================
GeoSearch.prototype.searchFromTextCallback = function(resp, callbackFunction) {
    //debugout('searchFromTextCallback');
    this.textFoundLocations = GeoSearch.responseToGeoLocation(resp);
    if (this.display === GeoSearch.DISPLAY_FORM) {
        if (this.map !== null) {
            this.populateSearchResultList(GeoSearch.MODE_TEXT);
            if (Common.isArray(this.textFoundLocations)) {
                this.cntInstructions.text(gettext("Places found") + ':');
                this.setMapBounds(this.textFoundLocations);
            }
            else this.cntInstructions.text(gettext('No location search match'));
        }
        if ($.isFunction(callbackFunction)) callbackFunction();
    }
    else {
        if ($.isFunction(callbackFunction)) callbackFunction();
        else {
            if (Common.isArray(this.textFoundLocations)) this.textSelectedLocationIdx = 0;
            else newSearch = false;
            this.displayLocationInfo(GeoSearch.MODE_TEXT);
        }
    }
};


//================================================================================
GeoSearch.prototype.copyNameSearchResults = function(toForm) {
// copies name search string, selected index and results between form and outer search result array
    var n;
    if (toForm) {
//            console.log('copying search data to form')
        this.nameFormSearchString = this.nameSearchString;
        this.cntInputRow1.children(':first').val(this.searchInputField.val())
        if (Common.isArray(this.nameFoundLocations)) {
            this.nameFormFoundLocations = new Array(this.nameFoundLocations.length);
            for (n = 0; n < this.nameFoundLocations.length; ++n) {
                this.nameFormFoundLocations[n] = this.nameFoundLocations[n];
            }
            this.nameFormSelectedLocationIdx = this.nameSelectedLocationIdx;
        }
        else {
            this.nameFormFoundLocations = null;
            this.nameFormSelectedLocationIdx = -1;
        }
    }
    else {
        this.nameSearchString = this.nameFormSearchString;
        if (Common.isArray(this.nameFormFoundLocations)) {
            this.nameFoundLocations = new Array(this.nameFormFoundLocations.length);
            for (n = 0; n < this.nameFormFoundLocations.length; ++n) {
                this.nameFoundLocations[n] = this.nameFormFoundLocations[n];
            }
            this.nameSelectedLocationIdx = this.nameFormSelectedLocationIdx;
        }
        else {
            this.nameFoundLocations = null;
            this.nameSelectedLocationIdx = -1;
        }
    }
};


//================================================================================
GeoSearch.prototype.saveMapPosition = function() {
    if (this.map &&
        this.map.gmap &&
        this.map.gmap.gmap)
    {
        this.mapZoom = this.map.gmap.gmap.getZoom();
        var tmpCenter = this.map.gmap.gmap.getCenter();
        this.mapLongitude = tmpCenter.lng();
        this.mapLatitude = tmpCenter.lat();
        if (!GeoLocation.validCoordinates(this.mapLongitude, this.mapLatitude)) {
            this.mapLongitude = 181;
            this.mapLatitude = 91;
        }
    }
};


//================================================================================
GeoSearch.prototype.setMode = function(mode, skipMapPosition) {
    //debugout('set mode ' + mode + ', ' + this.mode);
    var tmpLoc = null, tmpZoom, me = this;

    if (skipMapPosition == undefined || !skipMapPosition) this.saveMapPosition();
    switch (mode) {
        case GeoSearch.MODE_SEARCH:
            if (this.forceUpdateNameSearch) {
//                    console.log('forceUpdateNameSearch')
                this.forceUpdateNameSearch = false;
                this.searchByName(function() { me.setMode(GeoSearch.MODE_SEARCH); });
                return;
            }
            if (this.mode != mode) {
                this.map = null;
                $(this.mapContainerId).html('');
                this.mode = mode;
                this.selectTab(mode);
                this.cntInputRow2.hide();
                this.cntInputRow1.show();
                this.cntResults.removeClass('select');
            }
            if (Common.isString(this.nameFormSearchString))
                this.cntInstructions.text(Common.isArray(this.nameFormFoundLocations) ? gettext("Places found") + ':' : gettext('No location search match')).show();
            else this.cntInstructions.text(gettext("Enter place name")).show();
            this.cntInputRow1.children(':first').val(Common.isString(this.nameFormSearchString) ? this.nameFormSearchString : '').get(0).focus();
            tmpLoc = this.nameFormSelectedLocationIdx >= 0 ?
                        this.nameFormFoundLocations[this.nameFormSelectedLocationIdx] :
                        (this.selectedLocation ? this.selectedLocation : null);
            if (this.map === null) {
                //debugout('creating map');
                this.map = new Map(this.mapContainerId, GeoSearch.MAP_GEORSS,
                                { hideGoogleControls:this.settings.hideMapTypeBtns, renderPlaces: false, showHelp: false, handleGlobalKeys: false, limitDragBounds: false, showGallery: false, overlayOpacity: 0.5, imgRoot:GeoSearch.MAP_IMAGE_ROOT });
                if (this.settings.hideMapTypeBtns) {
                    this.map.gmap.mapControl = new GSmallMapControl();
                    this.map.gmap.gmap.addControl(this.map.gmap.mapControl);
                }
                this.map.stopLive();
                this.map.mapAddListener(MAP_EVENT_INIT, function() { me.populateSearchResultList(GeoSearch.MODE_SEARCH);
                                                                    if (me.mapZoom >= 0) me.map.gmap.gmap.setZoom(me.mapZoom);
                                                                    else me.setMapBounds(me.nameFormFoundLocations);
                                                                    me.setMapBounds(me.nameFormFoundLocations);
                                                                    if (tmpLoc) me.setPoint(tmpLoc.longitude, tmpLoc.latitude, tmpLoc.city);
                                                                    if (GeoLocation.validCoordinates(me.mapLongitude, me.mapLatitude)) {
                                                                        me.map.mapCenterTo({ lat: me.mapLatitude, lon: me.mapLongitude });
                                                                    }
                                                                    });
            }
            else {
                this.populateSearchResultList(GeoSearch.MODE_SEARCH);
                if (this.mapZoom >= 0) this.map.gmap.gmap.setZoom(this.mapZoom);
                else this.setMapBounds(this.nameFormFoundLocations);
                if (tmpLoc) this.setPoint(tmpLoc.longitude, tmpLoc.latitude, tmpLoc.city);
                if (GeoLocation.validCoordinates(this.mapLongitude, this.mapLatitude)) {
                    this.map.mapCenterTo({ lat: this.mapLatitude, lon: this.mapLongitude });
                }
                //this.setMapBounds(me.nameFormFoundLocations);
                //if (tmpLoc) this.setPoint(tmpLoc.longitude, tmpLoc.latitude, tmpLoc.city);
            }
            break;
        case GeoSearch.MODE_SELECT:
            if (this.mode != mode) {
                this.map = null;
                $(this.mapContainerId).html('');
                this.mode = mode;
                this.selectTab(mode);
                this.cntInputRow1.hide();
                this.cntInputRow2.hide();
                this.cntResults.addClass('select');
                this.cntInstructions.text(gettext('Select location from list or map')).show();
                this.cntResults.html('');
            }
            if (this.map === null) {
                //debugout('creating map');
                this.map = new Map(this.mapContainerId, GeoSearch.MAP_GEORSS, { renderPlaces: true, showHelp: false, handleGlobalKeys: false, limitDragBounds: false, showGallery: false, overlayOpacity: 0.5, imgRoot:GeoSearch.MAP_IMAGE_ROOT });
                this.map.stopLive();
                if (this.selectedLocation) {
                    tmpLoc = this.selectedLocation;
                    this.selectedLocationFromMap = tmpLoc;
                }
                else this.selectedLocationFromMap = null;
                this.map.mapAddListener(MAP_EVENT_INIT, function() { if (me.mapZoom >= 0) me.map.gmap.gmap.setZoom(me.mapZoom);
                                                                    if (tmpLoc) me.setPoint(tmpLoc.longitude, tmpLoc.latitude, tmpLoc.city);
                                                                    if (GeoLocation.validCoordinates(me.mapLongitude, me.mapLatitude)) {
                                                                        me.map.mapCenterTo({ lat: me.mapLatitude, lon: me.mapLongitude });
                                                                    }
                                                                    });
                this.map.mapAddListener(MAP_EVENT_CHANGE_PLACES, function(places) { me.displayMapRetrievedPlaces(places); });
                this.map.mapAddListener(MAP_EVENT_CLICK_PLACE, function(id) { me.selectSearchLocationResultFromMap(id); });
            }
            else {
                if (this.mapZoom >= 0) this.map.gmap.gmap.setZoom(this.mapZoom);
                if (this.selectedLocation) {
                    this.selectedLocationFromMap = this.selectedLocation;
                    this.setPoint(this.selectedLocation.longitude, this.selectedLocation.latitude, this.selectedLocation.city);
                }
                if (GeoLocation.validCoordinates(this.mapLongitude, this.mapLatitude)) {
                    this.map.mapCenterTo({ lat: this.mapLatitude, lon: this.mapLongitude });
                }
            }
            if (!this.selectedLocation) this.selectedLocationFromMap = null;
            break;
        case GeoSearch.MODE_MANUAL:
            if (this.mode != mode) {
                this.map = null;
                $(this.mapContainerId).html('');
                this.mode = mode;
                this.selectTab(mode);
                this.cntInputRow1.hide();
                this.cntInputRow2.show();
                this.cntResults.removeClass('select');
                this.cntResults.html('');
            }
            tmpLoc = this.selectedLocation;
            tmpZoom = this.mapZoom;

            this.selectedLocationFromMap = tmpLoc;
            if (tmpLoc) {
                var tmpLocName = tmpLoc.city;
                if (Common.isString(tmpLoc.country)) tmpLocName += ', ' + tmpLoc.country;
                this.cntInputRow2.children(':first').val(tmpLocName).show();
                this.cntInstructions.html('&nbsp;').show();
                this.displaySelectedCoordinates();
            }
            else {
                this.cntInputRow2.children(':first').hide();
                this.cntInstructions.html(gettext("Click on the map to select place")).show();
            }
            if (this.map === null) {
                //debugout('creating map');
                this.map = new Map(this.mapContainerId, GeoSearch.MAP_GEORSS, { showHelp: false, handleGlobalKeys: false, limitDragBounds: false, showGallery: false, overlayOpacity: 0.5, imgRoot:GeoSearch.MAP_IMAGE_ROOT });
                this.map.stopLive();
                this.map.mapAddListener(MAP_EVENT_CLICK, function(lat, lon, resolution) { me.mapClickCallback(lat, lon, resolution); });
                this.map.mapAddListener(MAP_EVENT_INIT, function() {if (me.mapZoom >= 0) me.map.gmap.gmap.setZoom(me.mapZoom);
                                                                    if (tmpLoc) {
                                                                        me.setPoint(tmpLoc.longitude, tmpLoc.latitude, tmpLoc.city);
                                                                        me.searchLocByLonLat(tmpLoc.longitude, tmpLoc.latitude, 0);
                                                                    }
                                                                    if (GeoLocation.validCoordinates(me.mapLongitude, me.mapLatitude)) {
                                                                        me.map.mapCenterTo({ lat: me.mapLatitude, lon: me.mapLongitude });
                                                                    }});
            }
            else {
                if (this.mapZoom >= 0) this.map.gmap.gmap.setZoom(this.mapZoom);
                if (tmpLoc) {
                    this.setPoint(tmpLoc.longitude, tmpLoc.latitude, tmpLoc.city);
                    this.searchLocByLonLat(tmpLoc.longitude, tmpLoc.latitude, 0);
                }
                if (GeoLocation.validCoordinates(this.mapLongitude, this.mapLatitude)) {
                    this.map.mapCenterTo({ lat: this.mapLatitude, lon: this.mapLongitude });
                }
            }
            break;
        case GeoSearch.MODE_TEXT:
            if (!this.isText) break;
            var tmpSearchText = this.getSearchText();
            if (Common.isString(tmpSearchText) &&
                tmpSearchText !== this.textSearchString)
            {
                this.searchFromText(function() { me.setMode(GeoSearch.MODE_TEXT); });
                return;
            }
            if (this.mode != mode) {
                this.map = null;
                $(this.mapContainerId).html('');
                this.mode = mode;
                this.selectTab(mode);
                this.cntInputRow1.hide();
                this.cntInputRow2.hide();
                this.cntResults.html('');
                this.cntResults.addClass('select');
                this.cntInstructions.text(Common.isArray(this.textFoundLocations) ? gettext("Places found from content") + ':' : gettext("No places found from content") + '.').show();
            }
            tmpLoc = this.textSelectedLocationIdx >= 0 ? this.textFoundLocations[this.textSelectedLocationIdx] : null;
            if (this.map === null) {
                this.map = new Map(this.mapContainerId, GeoSearch.MAP_GEORSS, { renderPlaces: false, showHelp: false, handleGlobalKeys: false, limitDragBounds: false, showGallery: false, overlayOpacity: 0.5, imgRoot:GeoSearch.MAP_IMAGE_ROOT });
                this.map.stopLive();
                if (tmpLoc) {
                    this.map.mapAddListener(MAP_EVENT_INIT, function() { me.populateSearchResultList(GeoSearch.MODE_TEXT); me.setMapBounds(me.textFoundLocations); me.setPoint(tmpLoc.longitude, tmpLoc.latitude, tmpLoc.city); });
                }
                else this.map.mapAddListener(MAP_EVENT_INIT, function() { me.populateSearchResultList(GeoSearch.MODE_TEXT); me.setMapBounds(me.textFoundLocations); });
            }
            else {
                this.populateSearchResultList(GeoSearch.MODE_TEXT);
                this.setMapBounds(this.textFoundLocations);
                if (tmpLoc) this.setPoint(tmpLoc.longitude, tmpLoc.latitude, tmpLoc.city);
            }
            break;
    }
};


//================================================================================
// sets lon,lat to selectedLocationFromMap when clicked on map
//================================================================================
GeoSearch.prototype.mapClickCallback = function(lat, lon, resolution) {
    if (this.selectedLocationFromMap === null) this.selectedLocationFromMap = new GeoLocation();
    this.selectedLocationFromMap.longitude = parseFloat(lon);
    this.selectedLocationFromMap.latitude = parseFloat(lat);
    this.displaySelectedCoordinates();
    var locName = this.cntInputRow2.children(':first').val().trim().split(/\s*,\s*/);
    if (locName.length > 1) {
        locName.splice(locName.length - 1, 1);
        locName = locName.join(', ');
    }
    else locName = locName[0];
    this.setPoint(this.selectedLocationFromMap.longitude, this.selectedLocationFromMap.latitude, locName);
    this.searchLocByLonLat(this.selectedLocationFromMap.longitude, this.selectedLocationFromMap.latitude, resolution);
};


//================================================================================
GeoSearch.prototype.displaySelectedCoordinates = function() {
    var dsplCoords = '';
    if (this.selectedLocationFromMap !== null &&
        this.selectedLocationFromMap.validCoordinates())
    {
        dsplCoords = 'lon: ' + this.selectedLocationFromMap.longitude.toFixed(3) +
                    ', lat: ' + this.selectedLocationFromMap.latitude.toFixed(3);
    }
    this.cntInputRow2.children(':first').show().next().text(dsplCoords).show();
};


//================================================================================
// searches for places near lon,lat to display them as suggested place name
//================================================================================
GeoSearch.prototype.searchLocByLonLat = function(longitude, latitude, resolution) {
    this.cntInstructions.text(gettext('Searcing places nearby') + '...');
    this.cntResults.html('');
    var me = this;
    Ajax.searchLocationByPoint(longitude, latitude, resolution,
                            GeoSearch.MAX_GEOSEARCH_RESULTS, GeoSearch.LANG,
                            function(resp) { me.searchLocByLonLatCallback(resp); });
};


//================================================================================
GeoSearch.prototype.searchLocByLonLatCallback = function(resp) {
    //debugout('searchLocByLonLatCallback');
    if (Common.isArray(resp)) {
        this.cntInstructions.text(gettext('Suggested place names') + ':');
        var tmpLocations = new Array(resp.length);
        for (var n = 0; n < resp.length; ++n) {
            var tmpLoc = new GeoLocation(resp[n].city, resp[n].countryId, resp[n].stateId, resp[n].longitude, resp[n].latitude, 0);
            tmpLocations[n] = tmpLoc;
            this.cntResults.append('<li>' +
                                    tmpLoc.city +
                                    (Common.isString(tmpLoc.country) ? ', ' +  tmpLoc.country : '') +
                                    '</li>');
            this.appendManualPlaceOnClick(n, tmpLoc);
        }
        //this.setMapBounds(tmpLocations);
    }
    else this.cntInstructions.text(gettext("No nearby places found"));
};


//================================================================================
// appends onclick handler to search response list
//================================================================================
GeoSearch.prototype.appendManualPlaceOnClick = function(idx, geoLoc) {
    //debugout('appendManualPlaceOnClick');
    var me = this;
    this.cntResults.children(':last').
                hover(function() { me.showPoint(geoLoc.longitude, geoLoc.latitude, geoLoc.city); },
                    function() { me.hidePoint(); }).
                    click(function() { me.selectManualLocation(idx, geoLoc); });
};


//================================================================================
// select suggested name for manually set location
//================================================================================
GeoSearch.prototype.selectManualLocation = function(idx, geoLoc) {
    if (!this.selectedLocationFromMap) this.selectedLocationFromMap = new GeoLocation();
    this.selectedLocationFromMap.city = geoLoc.city;
    this.selectedLocationFromMap.country = geoLoc.country;
    this.selectedLocationFromMap.countryId = geoLoc.countryId;
    this.selectedLocationFromMap.state = geoLoc.state;
    this.selectedLocationFromMap.stateId = geoLoc.stateId;
    this.displayLocationName();
    this.cntResults.children().each(function(i) { this.className = i == idx ? 'selected' : ''});
    this.setPoint(this.selectedLocationFromMap.longitude, this.selectedLocationFromMap.latitude, geoLoc.city);
};


//================================================================================
// sets location name from "preferred location names" list to input field
//================================================================================
GeoSearch.prototype.displayLocationName = function() {
    var locName = '';
    if (this.selectedLocationFromMap) {
        locName = this.selectedLocationFromMap.city +
                                (Common.isString(this.selectedLocationFromMap.country) ?
                                    ', ' + this.selectedLocationFromMap.country :
                                    '');
    }
    this.cntInputRow2.children(':first').val(locName).show();
};


//================================================================================
// select place from map in mode "SELECT"
//================================================================================
GeoSearch.prototype.selectSearchLocationResultFromMap = function(id) {
    var fld = document.getElementById(id);
    if (fld) fld.onclick();
};


//================================================================================
// displays places returned from map in form response list (mode "SELECT")
//================================================================================
GeoSearch.prototype.displayMapRetrievedPlaces = function(places)
{
    if (this.mode == GeoSearch.MODE_SELECT) {
        if (this.ignoreMapPlacesCallback) {
            this.ignoreMapPlacesCallback = false;
            return;
        }
        this.cntResults.html('').get(0).scrollTop = 0;
        if (Common.isArray(places)) {
            this.cntInstructions.text(gettext('Select location from list or map'));
            for (var n = 0; n < places.length; ++n) {
                var place = places[n];
                var tmpLoc = new GeoLocation(place.caption, place.country, place.state, place.lon, place.lat, 0);
                this.cntResults.append('<li id="' + place.id + '">' +
                                        tmpLoc.city +
                                        ', ' + tmpLoc.country +
                                        '</li>');
                this.appendMapPlaceOnClick(n, tmpLoc);
            }
        }
        else this.cntInstructions.text(gettext('No location found'));
    }
};


//================================================================================
// appends onclick event handler to the last element in the search response list (mode "SELECT")
//================================================================================
GeoSearch.prototype.appendMapPlaceOnClick = function(idx, geoLoc) {
    var me = this;
    this.cntResults.children(':last').
        hover(function() { me.showPoint(geoLoc.longitude, geoLoc.latitude, geoLoc.city); },
            function() { me.hidePoint(); }).
        get(0).onclick = function() { me.selectMapLocation(idx, geoLoc); };
        //.bind('click', function() { me.selectMapLocation(idx, geoLoc); });
};


//================================================================================
// select place from result list in mode "SELECT"
//================================================================================
GeoSearch.prototype.selectMapLocation = function(idx, geoLoc) {
    //debugout('selectMapLocation ' + idx + ', ' + geoLoc.toString());
    this.ignoreMapPlacesCallback = true;
    this.selectedLocationFromMap = geoLoc;
    this.cntResults.children().each(function(i) { this.className = i == idx ? 'selected' : ''});
    this.setPoint(geoLoc.longitude, geoLoc.latitude, geoLoc.city);
};


//================================================================================
// TODO:
// - widen map bounds for 10%
// - if there is only one point in bounds add 4 points 1 degree to each direcion
GeoSearch.prototype.setMapBounds = function(locations) {
    if (this.map === null) return;
    var bounds = [];
    if (Common.isArray(locations)) {
        for (var n = 0; n < locations.length; ++n) {
            if (locations[n].validCoordinates()) {
                bounds.push(new Point(locations[n].longitude, locations[n].latitude));
            }
        }
    }
    if (bounds.length == 0) this.map.refocus();
    else this.map.setBounds(bounds);
};


//================================================================================
// Saves selected location to hidden form fields
//================================================================================
GeoSearch.prototype.locationToForm = function(clear) {
    //debugout('locationToForm');
    var fld;
    clear = clear != undefined && clear;
    if ((this.selectedLocation || clear) && this.locationFormFields) {
        if ((Common.isString(this.locationFormFields.city) || clear) &&
            (fld = document.getElementById(this.locationFormFields.city)))
        {
            //debugout('saved city');
            fld.value = clear ? '' : this.selectedLocation.city;
        }
        if ((Common.isString(this.locationFormFields.countryId) || clear) &&
            (fld = document.getElementById(this.locationFormFields.countryId)))
        {
            //debugout('saved country');
            fld.value = clear ? '' : this.selectedLocation.countryId;
        }
        if ((Common.isString(this.locationFormFields.stateId) || clear) &&
            (fld = document.getElementById(this.locationFormFields.stateId)))
        {
            //debugout('saved state');
            fld.value = clear ? '' : this.selectedLocation.stateId;
        }
        if ((Common.isString(this.locationFormFields.longitude) || clear) &&
            (fld = document.getElementById(this.locationFormFields.longitude)))
        {
            //debugout('saved longitude');
            fld.value = clear ? 181 : this.selectedLocation.longitude;
        }
        if ((Common.isString(this.locationFormFields.latitude) || clear) &&
            (fld = document.getElementById(this.locationFormFields.latitude)))
        {
            //debugout('saved latitude');
            fld.value = clear ? 91 : this.selectedLocation.latitude;
        }
    }
};


//================================================================================
GeoSearch.prototype.selectTab = function(mode) {
    --mode;
    this.container.children('ul:first').children('li').each(function(i) { this.className = i == mode ? 'active' : ''; });
};


//================================================================================
GeoSearch.MAX_GEOSEARCH_RESULTS                = 20;
GeoSearch.LANG                                                = LANG;
GeoSearch.US_COUNTRY_ID                                = 'US';

//GeoSearch.MAP_IMAGE_ROOT        = 'jsmap/images/';
GeoSearch.MAP_IMAGE_ROOT        = 'http://triptracker.net/jsmap/images/';
GeoSearch.MAP_GEORSS                = '/noovo/static/georss.xml';

GeoSearch.MODE_UNDEFINED        = 0;
GeoSearch.MODE_SEARCH                = 1;
GeoSearch.MODE_SELECT                = 2;
GeoSearch.MODE_MANUAL                = 3;
GeoSearch.MODE_TEXT                        = 4;
GeoSearch.MODE_IP                        = 5;

GeoSearch.DISPLAY_NONE                = 0;
GeoSearch.DISPLAY_INFO                = 1;
GeoSearch.DISPLAY_FORM                = 2;

GeoSearch.countries = null;


//================================================================================
GeoSearch.getCountries = function(callback) {
    if (GeoSearch.countries === null) {
        Ajax.getCountries(GeoSearch.LANG,
                        function(resp) { GeoSearch.getCountriesCallback(resp, callback); });
    }
    else if ($.isFunction(callback)) callback();
};


//================================================================================
GeoSearch.getCountriesCallback = function(resp, callback)
{
    GeoSearch.countries = {};
    if (Common.isArray(resp)) {
        for (var n = 0; n < resp.length; ++n) {
            GeoSearch.countries[resp[n].id] = resp[n];
        }
    }
    if ($.isFunction(callback)) callback();
};


//================================================================================
GeoSearch.getStateFromId = function(stateId) {
    return Common.isString(stateId) ?
            (typeof GeoSearch.states[stateId] == 'undefined' ?
                stateId :
                GeoSearch.states[stateId]
            ) :
            '';
};


//================================================================================
GeoSearch.getCountryFromId = function(countryId)
{
    return Common.isString(countryId) ?
            (GeoSearch.countries !== null &&
                typeof GeoSearch.countries[countryId] != 'undefined' ?
                    GeoSearch.countries[countryId].country :
                    countryId
            ) :
            '';
};


//================================================================================
GeoSearch.responseToGeoLocation = function(resp) {
    var geoLocs = null;
    if (Common.isArray(resp)) {
        geoLocs = new Array(resp.length);
        for (var n = 0; n < resp.length; ++n) {
            geoLocs[n] = new GeoLocation(resp[n].city,
                                        resp[n].countryId,
                                        resp[n].stateId,
                                        resp[n].longitude,
                                        resp[n].latitude,
                                        resp[n].population);
        }
    }
    return geoLocs;
};


//================================================================================
// US State List
//================================================================================
GeoSearch.states = {
    AL : 'Alabama',
    AK : 'Alaska',
    AS : 'American Samoa',
    AZ : 'Arizona',
    AR : 'Arkansas',
    CA : 'California',
    CO : 'Colorado',
    CT : 'Connecticut',
    DE : 'Delaware',
    DC : 'District of Columbia',
    FM : 'Federated States of Micronesia',
    FL : 'Florida',
    GA : 'Georgia',
    GU : 'Guam',
    HI : 'Hawaii',
    ID : 'Idaho',
    IL : 'Illinois',
    IN : 'Indiana',
    IA : 'Iowa',
    KS : 'Kansas',
    KY : 'Kentucky',
    LA : 'Louisiana',
    ME : 'Maine',
    MH : 'Marshall Islands',
    MD : 'Maryland',
    MA : 'Massachusetts',
    MI : 'Michigan',
    MN : 'Minnesota',
    MS : 'Mississippi',
    MO : 'Missouri',
    MT : 'Montana',
    NE : 'Nebraska',
    NV : 'Nevada',
    NH : 'New Hampshire',
    NJ : 'New Jersey',
    NM : 'New Mexico',
    NY : 'New York',
    NC : 'North Carolina',
    ND : 'North Dakota',
    MP : 'Northern Mariana Islands',
    OH : 'Ohio',
    OK : 'Oklahoma',
    OR : 'Oregon',
    PW : 'Palau',
    PA : 'Pennsylvania',
    PR : 'Puerto Rico',
    RI : 'Rhode Island',
    SC : 'South Carolina',
    SD : 'South Dakota',
    TN : 'Tennessee',
    TX : 'Texas',
    UT : 'Utah',
    VT : 'Vermont',
    VI : 'Virgin Islands',
    VA : 'Virginia',
    WA : 'Washington',
    WV : 'West Virginia',
    WI : 'Wisconsin',
    WY : 'Wyoming'
};



//================================================================================
// GeoLocation class
//================================================================================
//function GeoLocation(city, country, countryId, state, stateId, longitude, latitude, population)
function GeoLocation(city, countryId, stateId, longitude, latitude, population)
{
    this.city = city == undefined ? '' : city;
    this.countryId = countryId == undefined ? '' : countryId;
    this.country = GeoSearch.getCountryFromId(this.countryId);
    this.stateId = this.countryId != GeoSearch.US_COUNTRY_ID || stateId == undefined ? '' : stateId;
    this.state = GeoSearch.getStateFromId(this.stateId);
    this.longitude = longitude == undefined ? 181 : parseFloat(longitude);
    if (isNaN(this.longitude) || Math.abs(this.longitude) > 180) this.longitude = 181;
    this.latitude = latitude == undefined ? 91 : parseFloat(latitude);
    if (isNaN(this.latitude) || Math.abs(this.latitude) > 90) this.latitude = 91;
    this.population = Math.max(0, parseInt(population, 10));
    if (isNaN(this.population)) this.population = 0;
}


//================================================================================
GeoLocation.prototype.setCountryId = function(countryId) {
    this.countryId = countryId;
    this.country = GeoSearch.getCountryFromId(countryId);
};


//================================================================================
GeoLocation.prototype.setStateId = function(stateId) {
    this.stateId = stateId;
    this.state = GeoSearch.getStateFromId(this.stateId);
};


//================================================================================
GeoLocation.prototype.setLongitude = function(longitude) {
    this.longitude = parseFloat(longitude);
    if (isNaN(this.longitude) || Math.abs(this.longitude) > 180) this.longitude = 181;
};


//================================================================================
GeoLocation.prototype.setLatitude = function(latitude) {
    this.latitude = parseFloat(latitude);
    if (isNaN(this.latitude) || Math.abs(this.latitude) > 90) this.latitude = 181;
};


//================================================================================
GeoLocation.prototype.isSet = function()
{
    return typeof this.city == 'string' &&
            this.city !== null &&
            this.city.length > 0 &&
            typeof this.country == 'string' &&
            this.country !== null &&
            this.country.length > 0 &&
            typeof this.countryId == 'string' &&
            this.countryId !== null &&
            this.countryId.length > 0 &&
            this.validCoordinates();
};


//================================================================================
GeoLocation.prototype.isOneSet = function()
{
    return (typeof this.city == 'string' &&
            this.city !== null &&
            this.city.length > 0) ||
            (typeof this.countryId == 'string' &&
            this.countryId !== null &&
            this.countryId.length > 0) ||
            this.validCoordinates();
};


//================================================================================
GeoLocation.prototype.validCoordinates = function()
{
    return GeoLocation.validCoordinates(this.longitude, this.latitude);
};


//================================================================================
GeoLocation.validCoordinates = function(longitude, latitude)
{
    return typeof longitude == 'number' &&
            Math.abs(longitude) <= 180 &&
            typeof latitude == 'number' &&
            Math.abs(latitude) <= 90;
};


//================================================================================
GeoLocation.prototype.unset = function()
{
    this.city = '';
    this.country = '';
    this.countryId = '';
    this.state = '';
    this.stateId = '';
    this.longitude = 181;
    this.latitude = 91;
};


//================================================================================
GeoLocation.prototype.equals = function(otherLoc)
{
    return this.city === otherLoc.city &&
            this.countryId === otherLoc.countryId &&
            this.stateId === otherLoc.stateId &&
            this.longitude === otherLoc.longitude &&
            this.latitude === otherLoc.latitude;
};


//================================================================================
GeoLocation.prototype.toString = function()
{
    return this.city +
            (Common.isString(this.country) ? ', ' + this.country : '') +
            (Common.isString(this.state) ? ', ' + this.state : '') +
            (this.validCoordinates() ?
                ' (lon:' + this.longitude.toFixed(3) +', lat:' + this.latitude.toFixed(3) +
                    (this.population > 0 ? ', ' + gettext('population') + ':' + this.population : '') +
                    ')' :
                (this.population > 0 ? ' (' + gettext('population') + ':' + this.population + ')' : ''));
};


//================================================================================
// class Point
//================================================================================
function Point(lon, lat) {
    this.lon = lon;
    this.lat = lat;
}
Point.prototype.toString = function() {
    return this.lon + ':' + this.lat;
};
