/**
 * @projectDescription
 *  This file holds the functions and object for mapsearch pages in
 *  Fonecta and Eniro
 * 
 */

/**
 * Global / local object references
 *
 */

/*global window, location, jQuery, OpenLayers, Modal, SiteCatalystEvents, mapPreferences, _c, _d */

var mapSearch; // Local reference to Map Object
var EM = EM || {}; // Local reference to EMAPI Object
var Controls = {}; // For handling map Controls
var LoadingIndicator = LoadingIndicator || {};

var Rangifer = Rangifer || {}; // From rangifer's global.js
var Alces = Alces || {}; // From alces's global.js
var Settings = Rangifer.settings || Alces.settings; // General Settings Object from the template
var isRangifer = (Rangifer.Core);

var PopupData = [];

// This is the needed height for single sidebar result element
var singleSearchResultElemHeight = isRangifer ? 53 : 70;
// Specifically calculated height
var affordableHeightOfResults;

var isLoggedIn = isLoggedIn || "no"; // General setting

var cancelMapZoom = false;

var ottoLayer, guestharboursLayer, weatherLayer; // for POI layers (EMAPI team needs these to be public)

// While loading the map
LoadingIndicator.append("map-loading-indicator");

// Animation durations & object sizes
var FADETIMEFAST = 240,
    FADETIME = 400,
    FADETIMESLOWER = 600,
    MANOUVERSBOXHEIGHT = 600,
    SLIDETIME = 480;


var Constants = function() {
    return {
        definedOnMap : "Määritelty kartalla"           
    };
}();

/**
 * Query parameters
 *
 * @constructor QueryParams
 * @param {jQuery Object} $
 *
 * @return {Object} params
 *
 */
var QueryParams = (function () {
    // can get .id property, which is probably MBSID - unique identifier for a company.
    var query = location.search.substr(1).split("&"),
        params = {},
        iL = query.length,
        single = "",
        waypoints,
        arr = [];

    while (iL--) {
        single = query[iL].split("=");
        if (single.length === 2) {
            params[encodeURIComponent(single[0])] = encodeURIComponent(single[1]);
        }
    }
    
    if (typeof params.routeTo !== "undefined") {
        arr = params.routeTo.split("%7C");
        if (arr.length > 1) {
            waypoints = arr.slice(0, arr.length - 1);
            params.routeTo = arr[arr.length - 1];
            params.waypoints = waypoints;
        } else {
            params.waypoints = [];
        }
    }

    return params;
}());

var Alerts = (function () {
    var self = {};

    self.showNoResults = function() {
        var UrlObject = Common.getCurrentUIState();

        if (UrlObject.what) {
            var modalNoResults = new Modal({
                modalClass: 'noResults',
                noDimmer: true,
                //closeButton: jQuery('<a class="close">X</a>'),
                closeButton: "",
                content: "<h1>Ei yhtään hakutulosta</h1><p>Hakutermillä <i>\"" + decodeURIComponent(UrlObject.what) + "\"</i> ei löytynyt yhtään hakutulosta.</p>"
            });
            if (!UrlObject.printLayout) {
                modalNoResults.show();
            }
        }
    };

    self.showPlaceNotFound = function() {
        var UrlObject = Common.getCurrentUIState();

        if (UrlObject.where) {
            var modalNotFound = new Modal({
                modalClass: 'noResults',
                noDimmer: true,
                //closeButton: jQuery('<a class="close"></a>'),
                closeButton: "",
                content: "<h1>Paikkaa ei löydy</h1><p>Paikkahaulla <i>\"" + decodeURIComponent(UrlObject.where) + "\"</i> ei löytynyt yhtään hakutulosta.</p>"
            });
            if (!UrlObject.printLayout) {
                modalNotFound.show();
            }
        }
    };

    return self;
}());
    
/**
 * @constructor Pagination
 *  For Search Results pagination, amount per page
 *  depends on the height of viewport
 *
 * @param {jQuery Object} $
 * @return {Object} Pagination
 *
 */
var Pagination = function ($) {
    var $instance = $("#mapsearch_pagination");

    // Function variables
    var _checkScope,
    _navigateToPrevResults,
    _navigateToNextResults,
    _resetPanes,
    _sendIdsToSiteCatalyst,
    _updateStatus;

    var _currentPage = 0;
    var _totalPages = 0;
    var _totalResults = 0;
    var _resultsPerView = 0;
    var _areResultsAnimating = false;

    _checkScope = function () {
        var _scope = mapSearch.Sidebar.getCurrentScope();
        if ((_currentPage*_resultsPerView) < _scope[0] || (_currentPage *_resultsPerView) > _scope[1]) {
            //mapSearch.getLayers().markerLayer.removeAllFeatures();
            //Common.addPointsToMap();
        }
    };
    
    _navigateToPrevResults = function () {
        _areResultsAnimating = true;

        var width = parseInt($(".result_page").css("width"), 10),
        curr = parseInt($("#mapsearch_results").css("left"), 10),
        snap = curr + width;

        _currentPage--;
        _checkScope();

        $("#mapsearch_results").animate({
            "left": snap +"px"
        }, 400, function () {
            _updateStatus();
            _areResultsAnimating = false;
            _sendIdsToSiteCatalyst();
        });
    };
    _navigateToNextResults = function () {
        _areResultsAnimating = true;
        var width = parseInt($(".result_page").css("width"), 10),
        curr = parseInt($("#mapsearch_results").css("left"), 10),
        snap = curr - width;
        
        _currentPage++;
        _checkScope();

        $("#mapsearch_results").animate({
            "left": snap +"px"
        }, 400, function () {
            _updateStatus();
            _areResultsAnimating = false;
            _sendIdsToSiteCatalyst();
        });
    };
    _resetPanes = function () {
        _currentPage = 0;
        _areResultsAnimating = false;

        $("#mapsearch_results").css("left", "0px");
    };
    /**
     * @function _sendIdsToSiteCatalyst
     *
     * Adds company id, based on .vcard id-attribute ("id_123")
     * to pass them for Site Catalyst.
     */
    _sendIdsToSiteCatalyst = function () {
        _c.info("_sendIdsToSiteCatalyst");
        
        var companiesShown = $(".result_page").eq((_currentPage-1)).find(".vcard");
        var iI=0,iL=companiesShown.length;
        var idsForSiteCatalyst = [];

        for (;iI<iL;iI++) {
            idsForSiteCatalyst.push(companiesShown.eq(iI).attr("id").substr(3));
        }

        var startIndex = parseInt(_currentPage-1, 10) * parseInt(_resultsPerView, 10) + 1;
        
        SiteCatalystEvents.pushEvents(idsForSiteCatalyst);
        SiteCatalystEvents.setProp("pageName", "ms_business");
        SiteCatalystEvents.setProp("prop2", "ms");
        SiteCatalystEvents.setProp("prop10", MapLayers.getVisibleLayer());
        SiteCatalystEvents.setProp("prop13", jQuery("#header-search-where").val());
        SiteCatalystEvents.setProp("prop25", "ms_business_"+ _currentPage);
        SiteCatalystEvents.dispatch(startIndex);
    };
    
    _updateStatus = function () {
        $("#pagination_prev").toggleClass("disabled", (_currentPage === 1));
        $("#pagination_next").toggleClass("disabled", (_currentPage === _totalPages));

        var _index = (_currentPage-1) * _resultsPerView;
        var iI = 0,
            _layerFeatures = mapSearch.getLayers().markerLayer.features;

        for (;iI < _totalResults; iI++) {

            var _feature = _layerFeatures[iI];

            if (_feature === undefined) {
                break;
            }

            var inScope =  (iI >= _index && iI < _index+_resultsPerView);
            var style = _feature.style;

            if (!!style) {
                style.graphicWidth = inScope ? Marker.prefs.normal.w : Marker.prefs.small.w;
                style.graphicHeight = inScope ? Marker.prefs.normal.h : Marker.prefs.small.h;
                style.graphicXOffset = inScope ?  Marker.prefs.normal.x : Marker.prefs.small.x;
                style.graphicYOffset = inScope ?  Marker.prefs.normal.y : Marker.prefs.small.y;
                style.label = inScope ? (iI+1) + "" : "";
            }
        }

        mapSearch.getLayers().markerLayer.redraw();

        $("#pagination_status").fadeTo(200, 0.25, function () {
            $("#status_start").text(1 + ((_currentPage-1) * _resultsPerView));
            $("#status_end").text(Math.min(_currentPage * _resultsPerView, _totalResults));
            $("#status_total").text(_totalResults);

            $("#pagination_status").fadeTo(200, 1.0);
        });
    };
    /**
     * Public functions
     */
    this.getCurrentPage = function () {
        return _currentPage;
    };
    
    this.getResultsPerView = function () {
        //_c.log("resultsPerView:"+ _resultsPerView);
        return _resultsPerView;
    };
    this.setPages = function (param) {
        _currentPage = param.currentPage;
        _resultsPerView = param.resultsPerView;
        _totalPages = param.totalPages;
        _totalResults = param.totalResults;

        _updateStatus();
    };
    this.show = function () {
        $instance.addClass("shown");
    };
    this.hide = function () {
        $instance.removeClass("shown");
        _resetPanes();
    };
    ( function init() {
        $instance.find(".nav").bind("click", function (evt) {
            evt.preventDefault();

            var $this = $(this);

            if ($this.attr("id") === "pagination_prev" && _currentPage > 1 && !_areResultsAnimating) {
                _navigateToPrevResults();
            }

            if ($this.attr("id") === "pagination_next" && _currentPage < _totalPages && !_areResultsAnimating) {
                _navigateToNextResults();
            }

        });
    }());
    return this;
};

/**
 * @constructor LinkToList
 * @return {Object} LinkToList
 *
 */
var LinkToList  = function ($) {
    var $instance = $("#mapsearch_linktolist");
    var self = this;

    var _updateHref = function (param) {
        var _where = Common.getCurrentUIState().where || "-";
        var _what = Common.getCurrentUIState().what || "-";
        var _groupId = Common.getCurrentUIState().groupId;
        var _lat = param.lat,
            _lon = param.lon,
            _radius = param.radius;

        var _href = mapPreferences.listViewURL + _where +"/"+ _what;

        if (self.isMapPanned) {
            _href +=  "?latitude=" + _lat + "&longitude=" + _lon + "&type=radius&radius=" + _radius;
        } else {
            if (QueryParams.filter) {
                _href += "?filter="+ decodeURIComponent(QueryParams.filter);

                if (QueryParams.radius) {
                    _href += "&type=radius&radius="+ QueryParams.radius;
                }
            }
            else if (QueryParams.radius) {
                _href += "?type=radius&radius="+ QueryParams.radius;
            }
        }
        
        if (_href.indexOf("?") > 0) {
            _href += !!_groupId ? "&groupId=" + _groupId : "";
        } else {
            _href += !!_groupId ? "?groupId=" + _groupId : "";
        }
        
        $instance.show().attr("href", _href);
    };
    this.isMapPanned = false;

    this.show = function (param) {
        _c.info("LinkToList.show()");
        _updateHref(param);
        $instance.addClass("shown");
    };
    this.hide = function () {
        _c.info("LinkToList.hide()");
        $instance.removeClass("shown");
    };
    return this;
};


/**
 * Rangifer and Alces Tabs Handling
 * @param {Object} jQuery
 * @return {Object} Tabs
 *  
 */
var Tabs = (function($) {
    var self = {};
    
    // Private variables
    var $this,
        $parent,
        _locTabId = (isRangifer) ? "link-places-on-map" : "tab-location",
        _panesInLocTab = "#singleVenue, #mapsearch_sidebar, #didYouMean",
        _panesInRouteTab = "#route-details",
        _sidebar,
        _routing,
        _venueMarkers,
        $activePanesInLocTab,
        $activePanesInRouteTab = $(_panesInRouteTab);
        
    
    // Private function declarations
    var _addEventListeners,
        _expandCollapse, 
        _fromLocToRoute,
        _fromRouteToLoc,
        _isRouteTabShown = false,
        _isLocTabShown = true,
        _toggleMapLayers,
        _toggleTabs;

        
    _addEventListeners = function() {
        
    };
    
    _expandCollapse = function($tab) {
        $tab.toggleClass("collapsed");
        
        if ($tab.attr("id") === _locTabId) {
            $activePanesInLocTab.toggleClass("shown");
        }
        else {
            $activePanesInRouteTab.toggleClass("shown", (!$tab.hasClass("collapsed") && $("#route-details").hasClass("notEmpty")));
        }
    };
    
    _fromLocToRoute = function() {
        if (isRangifer) {
            _sidebar.$results.hide();
            _routing.$instance.fadeTo(450, 1.0, function () {
                _routing.$routeFrom.focus();
            });
        }
        else {
            if ($activePanesInLocTab === undefined) {
                $activePanesInLocTab = $(_panesInLocTab).parent().find(".shown");
            }
            
            $activePanesInLocTab.removeClass("shown");
            $activePanesInRouteTab.toggleClass("shown", $("#route-details").hasClass("notEmpty"));
            _routing.$routeFrom.focus();
        }    
    };
    
    _fromRouteToLoc = function() {
        if (isRangifer) {
            _routing.$instance.hide();
            _sidebar.$results.fadeTo(450, 1.0);
        }
        else {
            $("#route-details").removeClass("shown");
            
            $activePanesInLocTab.addClass("shown");
            var $what = $("#header-search-what");
            var what = $what.val() == $what.attr('placeholder') ? "" : $what.val();
            $("#mapsearch_sidebar").toggleClass("shown", what.length > 0);
        }
    };
    
    _toggleMapLayers = function() {
        _venueMarkers.toggleLayer();
        _routing.toggleLayer();
    };
    
    _toggleTabs = function(evt) {
        $this = $(evt.target);
        $parent = $this.closest(".tab");
        
        if ($parent.attr("id") == "tab-location") {
            _c.log('location tab');
            _isRouteTabShown = false;
            _isLocTabShown = true;
        } else if ($parent.attr("id") == "tab-route") {
            _c.log('route tab');
            _isRouteTabShown = true;
            _isLocTabShown = false;
        }
        
        // Assign global objects now - can't be done earlier
        // since there's no proof they're actually created
        if (_sidebar === undefined) {
            _sidebar = mapSearch.Sidebar;
            _routing = mapSearch.Routing;
            _venueMarkers = mapSearch.VenueMarkers;
        }

        if ($parent.is(".selected")) {
            _expandCollapse($parent);
            return false;
        }

        if ($parent.is(".disabled")) {
            return false;
        }

        $parent.parent().find(".selected").removeClass("selected");
        $parent.addClass("selected");
        
        return true;
    };
    
    // Public Functions
    self.activateLocTab = function (tabId) {
        _c.log('activateLocTab');
        $activePanesInLocTab = $(tabId);
        $activePanesInLocTab.addClass("shown");
        _isRouteTabShown = false;
        _isLocTabShown = true;
    };
    
    self.activateRouteTab = function () {
       //$activePanesInRouteTab = $(tabId);
       _c.log('activateRouteTab');
       $activePanesInRouteTab.toggleClass("shown",$("#route-details").hasClass("notEmpty"));
        _isRouteTabShown = true;
        _isLocTabShown = false;       
    };
    
    self.toggle = function (evt) {
        if (!_toggleTabs(evt)) {
            return false;
        }

        $parent.removeClass("collapsed");
        // ?what is this? 
        if ($this.attr("id") === _locTabId || ($parent.attr("id") === _locTabId)) {
            _fromRouteToLoc();
        } else {
            _fromLocToRoute();
        }

        _toggleMapLayers();
    };
    
    self.isRouteTabShown = function () {
        return _isRouteTabShown;   
    };

    self.isLocTabShown = function () {
        return _isLocTabShown;   
    };
    
    (function init() {
        _addEventListeners();
    }());
    
    return self;
    
}(jQuery));

/**
 * @constructor Sidebar
 *
 * @param {jQuery Object} $
 * @return Sidebar
 *
 */
var Sidebar = function ($) {
    var init;
    var self = this;
    var _currentScope = [0,0];
    var _mapPoints = [];

    // Private function declarations
    var _paginateSidebar;
    
    /**
     * Public references
     */
    this.Pagination = new Pagination($);
    this.LinkToList = new LinkToList($);
    this.$instance = $("#mapsearch_sidebar");
    this.$tab = $("#link-places-on-map");
    this.$results = $("#places-on-map");


    /**
     * @private _paginateSidebar
     * Paginates the sidebar after population
     *
     */
    _paginateSidebar = function (results) {
        _c.info("Sidebar :: _paginateSidebar");
        
        var resultsPerView = Math.floor(affordableHeightOfResults / singleSearchResultElemHeight);

        var $instance = $("#mapsearch_results");
        var resultPages = [];
        var iI=0,iL=results.length;

        for (;iI<iL;iI++) {
            if (iI % resultsPerView === 0) {
                resultPages.push($("<div class='result_page'></div>"));
            }
            
            if (resultPages && resultPages.length) {
                resultPages[resultPages.length-1].append(results[iI]);
            }
        }

        // Updates the number of total results for Site Catalyst data
        SiteCatalystEvents.setProp("pageName", "ms_business");
        SiteCatalystEvents.setProp("events", "event1,event4,event9");
        SiteCatalystEvents.setProp("prop2", "ms");
        SiteCatalystEvents.setProp("prop10", MapLayers.getVisibleLayer());
        SiteCatalystEvents.setProp("prop13", jQuery("#header-search-where").val());
        SiteCatalystEvents.setTotalAmount(results.length);

        self.Pagination.setPages({
            "currentPage": 1,
            "resultsPerView": resultsPerView,
            "totalPages": resultPages.length,
            "totalResults": results.length
        });

        if (resultPages.length > 1) {
            self.Pagination.show();
        }

        iI=0;
        iL=resultPages.length;

        for (;iI<iL;iI++) {
            $instance.append(resultPages[iI]);
        }

        $(".result_page").css("height", affordableHeightOfResults);
    };


    /**
     * @public handleResultsHeight
     */
    this.handleResultsHeight = function () {
        var $sidebarResults = $("#mapsearch_results");
        var sidebarHeight = $('#mapsearch_sidebar').height() || (window.innerHeight - 75);
        var tabsHeight  = $("#mapsearch_tabs").outerHeight() || 0;
        var paginationHeight = $("#mapsearch_pagination").outerHeight() || 16;
        var linktolistHeight = $("#mapsearch_linktolist").outerHeight() || 16;

        affordableHeightOfResults = sidebarHeight - tabsHeight - paginationHeight - linktolistHeight - 10;

        $sidebarResults.css("height", affordableHeightOfResults);
    };

    /**
     * @public populate
     *
     * Populates the sidebar content according
     * to the passed data.
     *
     * @param {Object} data
     *
     */
    this.populate = function (data) {
        _c.info("Sidebar.populate()");
        
        var resultIndex = 0;
        var idsForSiteCatalyst = [];
        var $singleResult;
        var results = [];
        var iI = 0, iL = data.length;
        var _data, $link, id, mainCategory, title, streetAddress, dealCount,
        postalCode, postalArea, lat, lon, websiteUrl, companyUrl, logoUrl, relatedTacticalAds, phoneNumber,
        ratings;

        var _isDominantCategorySet = false;
        
        _mapPoints = [];

        /**
         * Fills the results template
         */

        var dataForPopup = {};
        
        for (;iI<iL;iI++) {
            _data = data[iI];
            
            if (!_isDominantCategorySet && _data.dominantCategory) {
                SiteCatalystEvents.setProp("prop16", _data.dominantCategory.name);
                _isDominantCategorySet = true;
            }

            id = _data.id + "";
            dealCount = _data.dealCount ? _data.dealCount : 0;
            mainCategory = (_data.dominantCategory) ? _data.dominantCategory.name : "";
            title = _data.title + "" || "";
            streetAddress = _data.streetAddress || "-";
            postalCode = _data.postalCode || "";
            postalArea = _data.postalArea || "-";
            lat = (parseFloat(_data.latitude) > 0) ? parseFloat(_data.latitude) : false;
            lon = (parseFloat(_data.longitude) > 0) ? parseFloat(_data.longitude) : false;
            websiteUrl = _data.websiteUrl || "";
            companyUrl = _data.companyUrl;
            logoUrl = _data.logoUrl || false;
            relatedTacticalAds = _data.relatedTacticalAds;
            phoneNumber = _data.phoneNumber || false;
            ratings = _data.ratings || [];

            companyUrl += "?what="+ Common.getCurrentUIState().what +"&where="+ $("#header-search-where").val();

            dataForPopup = {
                "id" : id,
                "mainCategory": mainCategory,
                "title" : title,
                "streetAddress" : streetAddress,
                "postalCode" : postalCode,
                "postalArea" : postalArea,
                "companyUrl" : companyUrl,
                "websiteUrl" : websiteUrl,
                "logoUrl" : logoUrl,
                "relatedTacticalAds" : relatedTacticalAds,
                "dealCount" : dealCount,
                "phoneNumber" : phoneNumber,
                "ratings" : ratings
            };

            PopupData[iI] = dataForPopup;

            $singleResult = $(mapPreferences.sidebarResultTemplate);
            $singleResult.attr("id", "id-"+ id);
            $singleResult.attr("index", iI);
            $singleResult.find("span.result_index").text(resultIndex+1);

            $link = $singleResult.find("a.fn");
            $link.text(title).attr("title", title);

            $link.attr("href", companyUrl);

            $singleResult.data("index", resultIndex);
            $singleResult.find("span.street-address").text(streetAddress);
            $singleResult.find("span.locality").text(postalCode +" "+ postalArea);
            $singleResult.find("span.latitude span.value-title").attr({"title":lat, "content": lat});
            $singleResult.find("span.longitude span.value-title").attr({"title":lon, "content": lon});

            if (Rangifer.Core) {
                // Rangifer + tactical ads
                if (relatedTacticalAds && relatedTacticalAds.length) {
                    $singleResult.addClass("tacticalAd");
                }
            } else if (dealCount) {
                // Alces + deals
                $singleResult.addClass("tacticalAd");
            }

            results.push($singleResult);
            idsForSiteCatalyst.push(id);

            _mapPoints.push({
                "id": id,
                "lat": lat,
                "lon": lon,
                "title": title,
                "address": streetAddress +" "+ postalArea,
                "websiteUrl": websiteUrl,
                "companyUrl" : companyUrl,
                "dealCount" : dealCount,
                "relatedTacticalAds" : relatedTacticalAds
            });
            resultIndex++;
        }

        if (!!resultIndex) {
            _paginateSidebar(results);
        }

        return (!!resultIndex);
    };
    
    this.update = function (bShow) {
        if (bShow) {
            this.Pagination.show();
            this.LinkToList.show();
        } else {
            this.Pagination.hide();
            this.LinkToList.hide();
        }
    };

    this.getCurrentScope = function () {
        return _currentScope;
    };

    this.getMapPoints = function () {
        var _perView = this.Pagination.getResultsPerView(),
            _currPageIndex = this.Pagination.getCurrentPage() -1,
            _scopeMin = _currPageIndex *_perView,
            _scopeMax = (_currPageIndex + 4) * _perView;
        
        _currentScope = [_scopeMin, _scopeMax];
        
        //var _sliced = _mapPoints.slice(_scopeMin, _scopeMax);
        //return _sliced;
        
        return _mapPoints;
    };
    
    
    init = (function () {
        $("#mapsearch_results").live("mouseleave", function () {
            MapPopup.close();
        });
        
        self.$instance.delegate("div.vcard", "mouseenter mouseleave", function (evt) {
            var _index = parseInt($(this).find(".result_index").text(), 10) -1;
            var _markerLayer = mapSearch.getLayers().markerLayer;
            var _feature = _markerLayer.features[_index];

            var style = _feature.style;
            var inScope =  (evt.type === "mouseenter");

            $(this).toggleClass("hovered", inScope);

            _feature.toggleTooltip(inScope);

            if (!!style) {
                var external = style.externalGraphic;
                external = (inScope) ? external.replace("default", "hover") : external.replace("hover", "default");

                style.externalGraphic = external;
            }

            _markerLayer.redraw();
        });
    }());
    
    return this;
};


var Marker = ( function () {
    var self = {},
        browserSpecific = (jQuery.browser.mozilla) ? 3 : 3;
            
    browserSpecific =  (jQuery.browser.webkit) ? 3 : browserSpecific; 
    browserSpecific =  (jQuery.browser.msie) ? 0 : browserSpecific; 

    /**
     * @public {Object} preferences
     */
    self.prefs = {
        "cursor" : "pointer",
        "externalGraphic" : {
            "default": (mapPreferences) ? mapPreferences.defaultMarkerIcon : "",
            "location": (mapPreferences) ? mapPreferences.locationMarkerIcon : "",
            "tactical": (mapPreferences) ? mapPreferences.tacticalMarkerIcon : ""
        },
        "large" : {
            "w" : 60,
            "h" : 72,
            "x" : -27,
            "y" : -60
        },
        "medium" : {
            "w" : (Rangifer.Core) ? 40 : 25,
            "h" : (Rangifer.Core) ? 48 : 25,
            "x" : (Rangifer.Core) ? -19 : -14,
            "y" : (Rangifer.Core) ? -41 : -12
        },
        "normal" : {
            "w" : (Rangifer.Core) ? 40 : 22,
            "h" : (Rangifer.Core) ? 48 : 22,
            "x" : (Rangifer.Core) ? -19 : -11,
            "y" : (Rangifer.Core) ? -41 : -19
        },
        "small" : {
            "w": (Rangifer.Core) ? 20 : 16,
            "h": (Rangifer.Core) ? 24 : 16,
            "x" : (Rangifer.Core) ? -9 : -8,
            "y": (Rangifer.Core) ? -20 : -16
        },

        "font" : {
            "x" : (Rangifer.Core) ? 1 : 0,
            "y": (Rangifer.Core) ? 18 : 5 + browserSpecific,
            "color": "#FFFFFF",
            "size" : "12px",
            "weight" : "bold"
        }
    };

    /**
     * @public create()
     * Constructor for single marker
     *
     */
    self.create = function (param) {
        var type = param.type;
        var _size = this.prefs[param.size];
        var _font = this.prefs.font;
        var index = param.index || 0;
        var label = (param.label || "") + "";


        var obj = {
            cursor: this.prefs.cursor,
            externalGraphic: this.prefs.externalGraphic[type],
            graphicWidth: _size.w,
            graphicHeight: _size.h,
            graphicZIndex: 900,
            zIndex: 900,
            backgroundGraphicZIndex : 900,
            graphicOpacity: 1,
            graphicXOffset: _size.x,
            graphicYOffset: _size.y,
            labelXOffset : _font.x,
            labelYOffset: _font.y,
            fontColor : _font.color,
            fontSize : _font.size,
            fontWeight : _font.weight,
            index : index,
            label : label
        };

        return obj;
    };
    return self;
}());


var VenueMarkers = function ($) {
    this.toggleSelect = function ($parent) {
        if ($parent.hasClass("selected")) {
            return false;
        }

        $(".tab.selected").removeClass("selected");
        $parent.addClass("selected");
    };
    
    this.toggleLayer = function () {
        var _markerLayer = mapSearch.getLayers().markerLayer;
        var visibility = !_markerLayer.getVisibility();

        _markerLayer.setVisibility(visibility);

        /**
         * when we switch from routing layer to marker layer,
         * it's likely we need to perform a new search
         */
        if (visibility && mapSearch.performSearch) {
            _c.log('VenueMarkers.toggleLayer performSearch');
            mapSearch.performSearch();
        }
    };
};


/**
 * @constructor Routing
 * @param {jQuery Object} $
 *
 * @return {Object} Routing
 */

// Routepoint = point on route. 
// Waypoint = point between From / To.  

var Routing = function ($) {
    var init,
        self = this;
            
    var ROUTEPOINTNAMES = ["A","B","C","D","E","F","G","H","I"],
        EMAPIGTE171 = !!OpenLayers.Control.prototype.deactivateMapMeasurement,
        ROUTECOMPRESSION = (QueryParams.routecompression == "false" && EMAPIGTE171 ? false : true),
        COORDINATEROUTEPOINTTEXT = Constants.definedOnMap,
        MAXROUTEPOINTS = 8;

    // Private function declarations
    var _addEventListeners,
        _appendDistanceAndEstimation,
        _displayAdditionalInfo,
        _displayRouteAdvertisement,
        _fetchRoute,
        _generateManeuversList,
        _generateMultiplePlaces,
        _generateMultiplePlacesRow,
        _generateMultiplePlacesRows,        
        _getGeocodeForAddress,
        _getGeocodesForRoutePoints,
        _generateDistance,
        _geoSearchResult = [],
        _getLocationData,
        _getRouteMarkers,
        _handleGeoResult,
        _multiplePlaces = [],
        _multipleFrom = false,
        _multipleTo = false,
        _noPlaces,
        _populateManeuvers,
        _populateMultiplePlaces,
        _showMultipleContent,
        _showMapCenterEmpty;

    // templates    
    var _closeButtonTemplate = "<a class='close'>X</a>",
        _distanceKmSuffixTemplate = "<span class='suffix'>km</span>",
        _distanceMSuffixTemplate = "<span class='suffix'>m</span>",
        _multipleLinkTemplate = "<li><a href='#'>%address%</a></li>",
        _routeDirectionsTitleTemplate = "<h1>Ajo-ohjeet perille</h1>",
        _routeWaypointTemplate = "";
        
    // Private variables
    var _routeFromInputPlaceholderText = "Mistä? - Katuosoite tai paikka",
        _routeToInputPlaceholderText = "Mihin? - Katuosoite tai paikka",   
        _routeWaypointInputPlaceholderText = "Katuosoite tai paikka",   
        $routeAdditional = $("#route-additional"),
        $routeAddWaypoint = $("#route-add-waypoint"),
        $routeSubmit = $("#route-submit"),
        $routeSwitch = $("#route-switch"),
        $routeManeuvers = $("#route-details-maneuvers"),
        $routeMultiplePlaces = $("#route-multiple-places"),
        $routeDetails = $("#route-details"),
        $routeDirections = $("#route-directions"),
        $routeTabContent = $("#tab-route-content"),
        $tfDistance = $("#tfDistance"),
        $tfEstimation = $("#tfEstimation"),
        $tfPrintDistance = $("#print-route #route-distance"),
        $tfPrintEstimation = $("#print-route #route-estimation");
    
    var _allowSubmit = true,
        _distance,
        _estimation,
        _maneuvers = [], // For verbal instructions of the route
        _routeFrom = {},
        _routeTo = {};
        
    // Public variables
    this.$tab = $("#link-routes-on-map");
    this.$instance = $("#routes-on-map");
    this.$routeFrom = $("#route-from");
    this.$routeTo = $("#route-to");
    /**
     * Private functions
     *
     */

    // pointHandler-object handles display, adding, removing and modifying route-points. It also caches available data and offers that cache outside.
    this.pointHandler = (function () {
        // Currently template is in FTL. Adding/sorting fields first modifies DOM and then stores data.  
        // TODO: Move template from FTL to JS. Store data in pointHandler-object and render UI changes from JS based on data. 
        var ph = this;
                  
        var _showRoutePoint,
            _generateWaypointField,
            _addressGeocodeCache = {},
            _UIRoutePoints = [{"address":self.$routeFrom.val(), "lon":undefined, "lat":undefined},{"address":self.$routeTo.val(), "lon":undefined, "lat":undefined} ];        // [{address: "", lon: 1, lat: 2}, {address:"espoo", ...}] 
                                             
        _showRoutePoint = function($routePoint) {
            var $tf = $routePoint.find("input"),
                val = $tf.val();
                
            $routePoint.slideDown(SLIDETIME, function () {
                self.adjustRouteDetailsBoxLayout();    
                ph.manageRoutePointRemoveDisplay();
                if ((val.length > 0)  && (val !== COORDINATEROUTEPOINTTEXT) && (val !== $tf.attr("placeholder"))) {
                    $tf.focus();
                } 
                //self.setManeuversHeight();
            });
        };
        
        
        // Return HTML for a new waypoint field.  
        _generateWaypointField = function() {
            var routepointNumber = (ph.getAllRouteInputFields().length - 1),
                routepointLabel = ROUTEPOINTNAMES[routepointNumber],
                routepointId = "route-waypoint-" + routepointLabel,
                $newWaypointHtml = $("#route-waypoint-template").clone();
             
            $newWaypointHtml.attr("id", "").find("input").attr("id", routepointId);
            $newWaypointHtml.find("label").text(routepointLabel).attr("for", routepointId);
            
            return $newWaypointHtml;
        };
        
                
        this.addRoutePoint = function (address, lon, lat, municipality) {
            _c.log("pointHandler.addRoutePoint");
            var $newWaypointHtml = _generateWaypointField(),
                $input = $newWaypointHtml.find("input"),
                pointNumber = (ph.getAllRouteInputFields().length - 1),
                pointName  = ROUTEPOINTNAMES[pointNumber],
                routePointId = "route-waypoint-" + pointName, 
                $containerLast = ph.getAllRouteInputFields().last();
            
            
            if (!!$containerLast && $containerLast.parents("#route-waypoint-container").length > 0) {
                $containerLast.parents("p").before($newWaypointHtml);                
            } else {
                $("#route-waypoint-container").append($newWaypointHtml);                
            }
        
            // Generate autosuggestion functionality new for input-field
            var routePointAutosuggest = new Alces.autoSuggest({
                    id: 'autosuggest-' + routePointId,
                    container: $("#tab-route"),
                    input: $newWaypointHtml.find('.route-waypoint'),
                    type: 'where',
                    autoposition: true
                });
        
            ph.addUIRoutePoint(address, lon, lat, pointName);
            _showRoutePoint($newWaypointHtml);
            ph.updateRouteInputLabels();
        
            // disable adding more locations, if 8 on the screen.
            if (ph.getAllRouteInputFields().length >= MAXROUTEPOINTS) {
                $routeAddWaypoint.addClass("disabled");
            }
            
            if (!!address) {
                //address = (typeof address.error === undefined) ? "" : address;
                if (typeof address === "string") {
                    $newWaypointHtml.find(".route-waypoint").val(address);
                    if (!!lon && !!lat) {
                        ph.addToCache(address, lon, lat, municipality);
                    }
                } else {
                    $newWaypointHtml.find(".route-waypoint").val(COORDINATEROUTEPOINTTEXT).attr("placeholder", "");            
                }
            }
            
            if (!!lon && !!lat) {
                $routeSubmit.trigger("click");
            } else {
                $newWaypointHtml.find("input").focus();
            } 
            
            _allowSubmit = true;
        };
        
        // adds address to geopoint cache. 
        this.addToCache = function (address, lon, lat, municipality) {
            if (!!address && address.length > 0) {                
                _addressGeocodeCache[ address.toLowerCase() ] = { "lon" : lon, "lat" : lat, "municipality" : municipality };
                return _addressGeocodeCache[ address.toLowerCase() ];
            }
            return false;
        };
        
        
        // uses #route-to & #route-from to ensure Rangifer compatibility . Otherwise "#tab-route-countent .route-point" would be enough.
        this.getAllRouteInputFields = function() {
            //return $("#route-from, #route-waypoint-container .route-waypoint, #route-to");
            if (Alces.Core) {
                return $("#route-from, #route-waypoint-container .route-waypoint, #route-to").extend($("#tab-route-content .route-point")).not("#route-waypoint-");
            } else {
                return $("#route-from, #route-to");
            }
        };
        

        // gets all route input fields, removes empty rows from array.
        this.getAllRoutePoints = function() {
            // TODO: change to use _UIRoutePoints instead of DOM fields.
            var _routepoints = $(ph.getAllRouteInputFields()).map(function(){return $(this).val();}).get();
            _routepoints = $.grep(_routepoints, function(n) {       // remove empty items
                return (n);
            });
            
            return _routepoints;
        };
        
        
        this.getAllRouteInputFieldsWithLabels = function() {
            //Return for ex. [{label: "A", address:"Helsinki" }, {label: "B", address:"Espoo"}]
            var $containers,
                result = [],
                label, address, placeholder;
                
            if (Rangifer.Core) {
                result.push({"label":"Mistä", "address":$("#route-from").val()}, {"label":"Mihin", "address":$("#route-to").val()});
            } else {
                $containers = $("#tab-route-content form p").not("#route-waypoint-template, #route-point-tools");               
                 
                $.each($containers, function(index, block) {
                     label = $.trim($(block).find("label").text());
                     address = $.trim($(block).find("input").val());
                     placeholder = $.trim($(block).find("input").attr('placeholder'));
                     
                     if ((label.length > 0 || address.length > 0) && ((address !== placeholder) || (address == Constants.definedOnMap))) {
                         result.push({"label":label, "address":address});
                     }
                });
            } 
           
            return result; 
        };

        this.updateUIRoutePoint = function( address, lon, lat, point, municipality ) {
            var pointNumber = isNaN(point) ? $.inArray(point, ROUTEPOINTNAMES) : point;
            
            address = typeof address == "string" ? address : "";                      
            _c.log( "updateUIRoutePoint " + point + pointNumber + " " + address + " " + lon + "," + lat );
            _UIRoutePoints[pointNumber] = { "address" : address, "lon" : lon, "lat" : lat, "municipality" : municipality };
            
            return _UIRoutePoints[pointNumber];
        };
        
        this.addUIRoutePoint = function( address, lon, lat, point, municipality ) {
            var pointNumber; 
            
            if (!!point) {
                pointNumber = isNaN(point) ? $.inArray(point, ROUTEPOINTNAMES) : point;
                _UIRoutePoints.splice( pointNumber, 0, { "address" : address, "lon" : lon, "lat" : lat, "municipality" : municipality } );
            } else {
                _UIRoutePoints.push( {"address" : address, "lon" : lon, "lat" : lat, "municipality" : municipality} );
            }
            _c.log("addUIRoutePoint " + point + pointNumber + " " + address + " " + lon + "," + lat + " " + municipality);
            
            return _UIRoutePoints;
        };
        
        this.getUIRoutePoint = function(point) {
            var pointNumber = isNaN(point) ? $.inArray(point, ROUTEPOINTNAMES) : point;
            
            return _UIRoutePoints[pointNumber];
        };
        
        this.getUIRoutePoints = function() {
            return _UIRoutePoints;
        };
        
        this.removeUIRoutePoint = function(point) {
            var pointNumber = isNaN(point) ? $.inArray(point, ROUTEPOINTNAMES) : point;
            _c.log("removeUIRoutePoint :: " + point + " " + _UIRoutePoints[pointNumber].address + ' ' + _UIRoutePoints[pointNumber].lon + ', ' + _UIRoutePoints[pointNumber].lat);
            
            return _UIRoutePoints.splice(pointNumber, 1);
        };
        
        // if address defined, return cache for that address. Otherwise return cache for all points of the route. 
        this.getCache = function (address) {
            var cache = _addressGeocodeCache, 
                cacheRow = false;
            
            if (!!address & !!cache) {

                for(var objId in cache) {
                    if (cache.hasOwnProperty(objId) && objId.toLowerCase() == address.toLowerCase()) {
                        cacheRow = cache[objId];
                    }
                }            
                return cacheRow;
            }
                        
            return _addressGeocodeCache;               
        };


        // _getRouteFieldCount :: get amount of input fields shown in routesearch UI
        this.getRouteFieldCount = function () {
            return (ph.getAllRouteInputFields().length);
            // TODO: Return from _UIRoutePoints               
        };
        
               
        // _manageRoutePointRemoveDisplay() : manages display of "remove"-icon in route-search, when more than 2 addresses used.
        this.manageRoutePointRemoveDisplay = function() {
           var routeWaypointCount = ph.getRouteFieldCount();
           
           if (routeWaypointCount > 2) {
               $("#tab-route-content .route-point-remove").show();
           } else {                   
               $("#tab-route-content .route-point-remove").hide();
           }       
        };
        

        // takes as parameter the parent <p> jQuery object
        this.removeRoutePoint = function ($parent) {
            var lblName = ($parent.attr("id")).replace("route-", "");

            _c.log("pointHandler.removeRoutePoint " + lblName);

            $parent.slideUp(SLIDETIME, function () {
                $(this).remove();
                
                ph.updateRouteInputLabels();
                ph.removeUIRoutePoint(lblName);
                self.adjustRouteDetailsBoxLayout();
                ph.manageRoutePointRemoveDisplay();
                
                if (ph.getRouteFieldCount() < (MAXROUTEPOINTS)) {
                    $routeAddWaypoint.removeClass("disabled");
                }
                
                $routeSubmit.trigger("click");
            });
        };


        // reverse order of route-point-fields in UI
        this.switchRoutePointOrder = function () {
            var points = $(ph.getAllRouteInputFields()).clone(), 
                l = points.length - 1,
                placeholder;
                

            $(ph.getAllRouteInputFields()).each(function (index, curPoint) {
                $(curPoint).val($(points[l - index]).val());
                placeholder = $(points[l - index]).attr("placeholder");
                $(curPoint).attr("placeholder", placeholder);
            });
            _UIRoutePoints = _UIRoutePoints.reverse();
            //$routeSubmit.trigger("click");
        };

        // Update input field text / lon/lat.
        this.updateRoutePoint = function(pointName, address, lon, lat, municipality) {
            var $point = $("#route-" + pointName + " .route-point");
            
            //address = (typeof address.error === undefined) ? "" : address;
            if ((typeof address === "string") && address.length > 0 && (address !== COORDINATEROUTEPOINTTEXT)) {
                $point.val(address);
            } else {
                $point.val(COORDINATEROUTEPOINTTEXT).attr("placeholder", COORDINATEROUTEPOINTTEXT);            
            }
            $routeSubmit.focus();
    
            if (!!lon && !!lat && !!address && address.length > 0) {
                ph.addToCache(address, lon, lat, municipality);
            }
            ph.updateUIRoutePoint(address, lon, lat, pointName);
        };
        
    
        // updates route input labels & ID for the <p> containing them.
        this.updateRouteInputLabels = function() {
            var labels = $("#tab-route-content .route-from-label, #route-waypoint-container label, #tab-route-content .route-to-label"),
                $parent, lblName, oldId, cachePosition;
                
            $.each(labels, function(labelIndex, labelElement) {
                lblName = ROUTEPOINTNAMES[labelIndex];
                oldId = labelElement.id; 
                
                $(labelElement).text(lblName).attr({"for": "route-waypoint-" + lblName, "id":"route-waypoint-"+lblName});
                $parent = $(labelElement).parent("p"); 
                $parent.attr("id", "route-" + lblName).find("input.route-point").attr("id", "route-waypoint-"+lblName);
            });
        };
        
        return this;
    }()); // /pointHandler() 


    _addEventListeners = function () {

        var placeholder_gotfocus = function(elem) {
            var $input = $(elem), 
                val = $input.val();

            if ((val == COORDINATEROUTEPOINTTEXT) || (val == $input.attr("placeholder"))) {
                $input.attr("placeholder", val).val("");
            }            
        };
        
        var placeholder_blur = function(elem) {
            var $input = $(elem);

            if ($input.val().length === 0) {
                $input.val($input.attr("placeholder"));
            }            
        };
        
        // When ENTER is pressed, submit form
        $("#route-data, #route-from, #route-to").bind("keyup", function (evt) {
            if (evt.keyCode === 13) {          // 13 = Enter
                $($routeSubmit).trigger("click");
            }
        });

        $("#tab-route-content .route-waypoint").live("keyup", function (evt) {
            if (evt.keyCode === 13) {          // 13 = Enter
                $($routeSubmit).trigger("click");
            }            
        });

        $("#route-from, #route-to").bind("focusin", function(evt) {
           placeholder_gotfocus(this); 
        });
        
        $("#route-from, #route-to").bind("blur", function(evt) {
           placeholder_blur(this); 
        });

        $("#tab-route-content .route-waypoint").live("focusin", function (evt) {
            placeholder_gotfocus(this);
        });
            
        $("#tab-route-content .route-waypoint").live("blur", function (evt) {
            placeholder_blur(this);
        });

        $("#route-extra input[name=routestyle]").bind("click", function(evt) {
            $($routeSubmit).trigger("click");            
        });

        $("#tab-route-content .route-point-remove").live('click', function (evt) {
            var $parent = $(evt.target).parent("p");

            evt.preventDefault();
            self.pointHandler.removeRoutePoint($parent);

            return false; 
        });

        $routeSubmit.live("click", function (evt) {

            evt.preventDefault();

            if (_allowSubmit) {
                //: TODO: return routepoints with information about input field label (A, B, ...)
                var _points = self.pointHandler.getAllRoutePoints();
                var _labeledPoints = self.pointHandler.getAllRouteInputFieldsWithLabels();

                $routeSubmit.data("inProgress", true);
                $('#mapsearch_sidebar').addClass("loading");

                $routeAdditional.fadeTo(FADETIMEFAST, 0.0, function () {
                    jQuery("#route-distance, #route-directions").show();
                    jQuery("#place-not-found").hide();
                });
                _c.log('routeSubmit::');
                _c.log(_labeledPoints);
                _getLocationData({"points":_labeledPoints});
            }
            return false;
        });


        $routeAddWaypoint.live("click", function (evt) {
            evt.preventDefault();
            
            if ($(this).hasClass("disabled")) {
                return false;
            }

            self.pointHandler.addRoutePoint();
            
            evt.stopPropagation();
            return false;
        });
        
        $routeSwitch.live("click", function (evt) {
            evt.preventDefault();

            if ($(this).hasClass("disabled")) {
                return false;
            }

            self.pointHandler.switchRoutePointOrder();

           $routeSubmit.trigger("click");
           
           return false;
        });

        $routeMultiplePlaces.find("a").live("click", function(evt) {
            // click for "search returned multiple results"-item
            evt.preventDefault();
            var $this = $(this),
                targetPoint = $(this).closest(".multiple-places-container").find("h2 .pointLabel").text(),     // find which input link belongs to
                $valTarget = $("#route-" + targetPoint).find("input");                                  // target input field
                
            _c.log('multipleClick - target ' + targetPoint);
            _c.log($valTarget);

            $valTarget.val($this.text());

            $this.closest("ol").find(".selected").removeClass("selected");
            $this.parent("li").addClass("selected");

            $routeSubmit.trigger("click");
            $routeMultiplePlaces.slideUp(function() {
                $(this).empty(); 
            });
            
            return false;
        });

        $routeDirections.live("click", function (evt) {
            _c.log("routeDirections click");
            var content = _generateManeuversList(_maneuvers);

            var modalDirections = new Modal({
                modalClass: 'directions',
                dimmerClass: 'absolute',
                closeButton: jQuery(_closeButtonTemplate),
                content: _routeDirectionsTitleTemplate + content
            });

            modalDirections.show();
            return false;
        });

        // prevent map movement by keyboard when focus on input fields.
        $("#tab-route-content").on("keydown", ".route-point",  function(event) {
            if (event.which && event.which >= 37 && event.which <= 40) {
                // arrow-key pressed
                event.stopPropagation();
            }
        });
        
        // interval for handling disabled buttons
        setInterval( function () {
            var isDisabled = false;

            if (!_allowSubmit) {
                isDisabled = true;
            }

            $routeSwitch.toggleClass("disabled", isDisabled);
            $routeSubmit.toggleClass("disabled", (isDisabled || $routeSubmit.data("inProgress") === true));
        }, 500);
    };
    
    // rounds distance and estimation updates them to DOM
    _appendDistanceAndEstimation = function () {
        if (_distance > 0) {
            $tfDistance.text(_distance);
            $tfPrintDistance.text(_distance);
        }
        if (_estimation > 0) {
            _estimation = Math.round(_estimation / 30) / 2;     // rounding to 1/2 minute accuracy

            if (_estimation >= 60) {
                _estimation = Math.floor(_estimation / 60) +"h "+  Math.floor(_estimation % 60);
            }

            $tfEstimation.text(_estimation);
            $tfPrintEstimation.text(_estimation);
        }
    };
    
    // Additional info for route = distance and timee estimation, manouvers ("turn right, then left...") 
    _displayAdditionalInfo = function (param) {
        $routeAdditional.fadeTo(FADETIME, 1.0);
        _appendDistanceAndEstimation();
        
        
        if (Alces.Core && param.maneuvers) {
            _populateManeuvers(param.maneuvers);
            self.adjustRouteDetailsBoxLayout();
            $("#route-details-info, #route-details-toolbar").slideDown(function() {$(this).show();});                    
        }
        
    };
    
    // Display Flybe-ad for route
    _displayRouteAdvertisement = function(points) {
        var departureCity, destinationCity, distance, 
            duration, h_pos, hours, minutes,
            iframeSrc, $iframeHtml,
            screenWidth = $(window).width(), 
            screenMinWidth = 999;
            
       // TODO: get URL & routeAdsConfig-data from JSON
       // TODO: get JSON url from config
       // TODO: don't display if screen width < 10     
       _c.log('************ displayRouteAdvertisement');
       _c.log(points);
        
        if (routeAdsConfig && routeAdsConfig.showAds && (screenWidth > screenMinWidth)) {
        
            if (points && points.length > 1) {
                departureCity = points[0].municipality;
                destinationCity = points[ points.length - 1].municipality;
                distance = $("#route-details #tfDistance").text();
                distance = parseInt(distance, 10);
                
                duration = $("#route-details #tfEstimation").text();
                h_pos = duration.indexOf('h');
                if (h_pos > 0) {
                    hours = duration.substr(0, h_pos);
                    minutes = duration.substr(h_pos+1, duration.length);
                } else {
                    hours = 0;
                    minutes = duration;
                }
                duration = parseInt(hours, 10) * 60 + Math.round(minutes / 10) * 10;
                
                iframeSrc = 'http://localhost:8888/flybe.php?DESTINATION_CITY=' + destinationCity + '&DEPARTURE_CITY=' + departureCity + '&DURATION=' + duration + '&DISTANCE=' + distance;
                $iframeHtml = $('<iframe scrolling="no" allowtransparency="true" id="map-route-ad" src="' + iframeSrc + '"></iframe>')
            }
            
            if (($("#eniro-map-header #mapRouteAd").length === 0) && ($iframeHtml !== undefined )) {
                $("#eniro-map-header").append($iframeHtml)
            } else {
                $("#eniro-map-header #mapRouteAd").attr("src", iframeSrc);
            }
        }            
    };
    
    _generateDistance = function (distance) {
        var generateDistance = "";
        
        if (distance >= 1000) {
            generateDistance = (distance/1000).toFixed(1) + _distanceKmSuffixTemplate;
        }
        else if (distance > 0) {
            generateDistance = distance + _distanceMSuffixTemplate;
        }
        
        return generateDistance; 
    };
    
    // in routesearch, generates HTML for "how to get there"-instructions
    _generateManeuversList = function (maneuvers) {
        // TODO: change so that uses templates that are defined in Routing-object top.
        _c.info("Routing._generateManeuversList()");
        
        var l = maneuvers.length,
            content = "";

        while (l--) {
            content = "<li>"+
            "<span class='index'>"+ (l+1) +".</span>"+ 
            "<span class='path'>"+ maneuvers[l].path.replace(/->/g, " -> ") +"</span>"+
            "<span class='distance'>"+ _generateDistance(maneuvers[l].distance) +"</span>"+
            "</li>"+ content;
        }

        content = "<div class='listwrapper'><ol>" + content + "</ol></div>";
        
        return content;
    };
    
    // generates HTML for "multiple places found for address"-box
    _generateMultiplePlaces = function (place) {
        var _template = _multipleLinkTemplate, 
            rg;
            
        place.address = place.address.replace(/\,? \d{5}/, '');     // Remove postal number from address field
        for (var x in place) {
            if (place.hasOwnProperty(x)) {
                rg = new RegExp("%"+ x +"%", "gi");
                _template = _template.replace(rg, place[x]);
            }
        }
        return _template;
    };
    
    
    _generateMultiplePlacesRow = function (place) {
        var address = place.address.replace(/\,? \d{5}/, ''),     // Remove postal number from address field
            rowHtml = "<li><a href='#'>" + address + "</a></li>";
            
        return rowHtml;   
    };

    // TODO; make this create the whole list-object, rename to _generateMultiplePlacesList(places)
    _generateMultiplePlacesRows = function (places) {
        var i = 0,
            l = places.length, 
            rowsHtml = "";
            
        if (places.length > 0) {
            for (; i < l; i++) {
                rowsHtml += _generateMultiplePlacesRow(places[i]);
            }  
            return rowsHtml;
        } else {
            return false;
        }            
    };


    // _noPlaces() : no search results, display center of Finland & "no results"-message
    _noPlaces = function () {
        _c.log("_noPlaces - ");
        $("#route-additional").css("opacity", "1.0");
        $('#mapsearch_sidebar').removeClass("loading");
        $("#map-loading").fadeOut(FADETIME);
        $("#route-distance, #route-directions").hide();
        $("#route-details-info, #route-details-toolbar").hide();
        $("#route-details").addClass("shown");
        $("#place-not-found").fadeIn(FADETIME);

        self.adjustRouteDetailsBoxLayout(); 
        $routeSubmit.removeData("inProgress");
        LoadingIndicator.remove(/* from sendGeoCodeRequest */);
        mapSearch.getLayers().routingLayer.removeAllFeatures();
        
        if (mapSearch.getMapObj().getZoom() === 0) {
            mapSearch.getMapObj().setCenter(new EM.LonLat(Common.Geo.getLon(), Common.Geo.getLat()), 5);
        } 
    };
    
    // _showMapCenterEmpty() : show center of finland, remove markers from map.
    _showMapCenterEmpty = function () {
        $("#route-additional").css("opacity", "1.0");
        $('#mapsearch_sidebar').removeClass("loading");
        $("#map-loading").fadeOut(FADETIME);
        $("#route-distance, #route-directions, #route-details-info, #route-details-toolbar").hide();
        $("#route-details").addClass("shown");
        
        $routeSubmit.removeData("inProgress");
        LoadingIndicator.remove();   //from sendGeoCodeRequest
        mapSearch.getLayers().routingLayer.removeAllFeatures();
        
        if (mapSearch.getMapObj().getZoom() === 0) {
            mapSearch.getMapObj().setCenter(new EM.LonLat(Common.Geo.getLon(), Common.Geo.getLat()), 5);
        } 
    }; 
    
    // _populateManouvers(manouvers) : populate manouver data to UI
    _populateManeuvers = function (maneuvers) {
        _c.info("Routing._populateManeuvers()");
        var content = _generateManeuversList(maneuvers);
        $routeManeuvers.append(content);
    };
    
    // if multiple search results for a location, populates data to a list on screen.
    _populateMultiplePlaces = function() {

        // Clears existing multiple places from previous search
        $routeMultiplePlaces.empty().hide();
        
        if (_multiplePlaces) {
            $.each(_multiplePlaces, function(index, searchedPlace) {
                 _showMultipleContent(searchedPlace);               
            });
        }

        _multiplePlaces = [];
        _multipleFrom = false;
        _multipleTo = false;
    };
    

    // _showMultipleContent : Use: takse list of multiple places & selector of target element
    // Action: parses the data to a template into given target element in DOM, shows that target container, marks the first element as selected..
    _showMultipleContent = function(multiplePlace, multipleContainerSelector) {
        var i= 0, 
            multiplePlaceContent = "",
            $newMultiple;

        multiplePlaceContent += _generateMultiplePlacesRows(multiplePlace.places);
        
        $newMultiple = $("#route-multiple-places-template").clone();
        $newMultiple.attr("id", "").find("h2").html("<span class='pointLabel'>" + multiplePlace.label + "</span> - <span class='pointAddress'>" +multiplePlace.address+"</span>" + " - Löytyi monta sijaintia ");
        
        $routeMultiplePlaces.append($newMultiple);
        //$(multipleContainerSelector).show()
        $newMultiple.show()
        .find("ol").attr("id", "route-"+multiplePlace.label).append(multiplePlaceContent);
        //.find("li:first").addClass("selected");
        
        $routeMultiplePlaces.slideDown(SLIDETIME);
        //self.setManeuversHeight();        
    };

       
    _fetchRoute = function (param) {
        var compression = (ROUTECOMPRESSION === true ? "&compressed=y" : "");
                
        LoadingIndicator.append("map-loading-indicator");
        param.from = param.points[0];
        param.to   = param.points[param.points.length-1];
        _c.log("Routing._fetchRoute compression" + (compression.length === 0 ? " false" : "=y") + " ROUTECOMPRESSION:"+ROUTECOMPRESSION);
        
        // if waypoints, parse lon/lats to a  comma-separated string (in 6 decimal accuracy). 
        if (param.points.length > 2) {
            param.waypoints = param.points.slice(1, param.points.length-1);
            param.waypointString = "";
            $.each(param.waypoints, function(index, waypoint) {
                if (!!waypoint.lat && !!waypoint.lon) {
                    param.waypointString += waypoint.lat.toFixed(6) + "," + waypoint.lon.toFixed(6) + ((index === (param.waypoints.length - 1)) ? "" : ",");
                }
            });
        } else {
            param.waypointString = '';
        }
        /*
        if (typeof param.from.lon === "undefined" || param.error) {
            _c.log("Routing.fetchRoute() - No route.");
            
            // TODO: Log to siteCatalyst 
            
            _noPlaces();
            return false;
        }
        */
        if (typeof param.to.lon === "undefined") {
            // 0.001 is a hack atm, because EMAPI can't handle points too close to each other
            param.to.lon = param.from.lon+0.001;
            param.to.lat = param.from.lat+0.001;

            _noPlaces();
        }
        
        self.pointHandler.manageRoutePointRemoveDisplay();
        
        var url = Settings.emapi_findroute_uri +"?provider="+ ($("#oym").is(":checked") ? "onYourMapRoutingService" : "logicaRoutingService"); 
            url += "&fromXlng=" + param.from.lon +"&fromYlat="+ param.from.lat +"&toXlng="+ param.to.lon +"&toYlat="+ param.to.lat +"&locale=fi&pid=fonecta&routeStyle="; 
            url += Common.getRouteStyle() +"&callback=mapSearch.Routing.drawRoute" + (param.waypointString.length > 0 ? "&wp=" + param.waypointString : "") + compression;
            
        _c.log("Routing.fetchRoute() :: URL: "+ url);

        SiteCatalystEvents.sendRoutingFetchRoute(self.$routeFrom.val(), self.$routeTo.val(), MapLayers.getVisibleLayer(), isLoggedIn);

        mapSearch.isMapPanned = true;
        
       // OpenLayers.loadURL('proxy.jsp?url=' + encodeURIComponent(url), {}, mapearch.getMapObj(), mapSearch.Routing.drawRoute);

        jQuery.ajax(url,
        {
            "dataType": 'jsonp'
        }
        ).success( function (response) {
            _c.log('Routing.fetchRoute() AJAX success.');
            if (!param.error) {
                $routeSubmit.removeData("inProgress");
                self.drawRoute(response);
                self.routeAddSearchMeta(response, param.points);
            }
        }).error( function (response) {
            _c.warn("Routing.fetchRoute() :: error");
            _c.dir(response);
        }); 
    }; 

    
    _getGeocodesForRoutePoints = function(routePoints) {
        var allRequests = [],
            reqCount = routePoints.points.length, 
            address = "",
            place,
            municipality,
            cacheRow = false,
            geoResult, lon, lat;

        // TODO: make 1 function that parses geocoding result to pretty object, and another one that parses "määritelty kartalla"-point to an object.

        _geoSearchResult = [];
        _c.log("_getGeocodesForRoutePoints:: ");
        _c.log(routePoints);
        
        $.each(routePoints.points, function(index, routePoint) {
            address = routePoint.address;
            // Address is not empty, is not a coordinate (start with "(") and is not equal to a placeholder text like "Määritelty kartalla"
            //_c.log('_getGeoCodesForRoutePoints AJAX/cache ' + index);
            //_c.log(routePoint);

            if ((address.length > 0) && (address.substring(0, 1) !== "(") && (address !== COORDINATEROUTEPOINTTEXT)) {
                cacheRow = self.pointHandler.getCache(address.toLowerCase());
                
                if (!!cacheRow) {
                    _c.log(address + " found in cache " + reqCount + ": " + cacheRow.lon.toFixed(6) + "," + cacheRow.lat.toFixed(6));
                    _geoSearchResult.push({"address" : address, "lon" : cacheRow.lon, "lat" : cacheRow.lat, "label" : routePoint.label, "municipality" : cacheRow.municipality});
                    
                    reqCount -= 1;
                    if (reqCount === 0) {
                        _handleGeoResult(_geoSearchResult);
                    }                    
                } else if (reqCount > 0) {
                    // Not found in cache, make a geocoding request for address.
                    allRequests.push(
                        jQuery.ajax(
                        Settings.emapi_geocode_uri,
                        {
                            data: {
                                country: 'FIN',
                                address: routePoint.address
                            },
                            dataType: 'jsonp'
                        }
                        ).success(function(response) {
                            if (response.places && response.places[0] && response.places[0].coordinate) {
                                place = response.places[0];
                                lon = place.coordinate.wgs84xLng;
                                lat = place.coordinate.wgs84yLat;
                                address = place.address;
                                municipality = place.municipality;
                                
                                if ( response.places.length > 1 ) {
                                    _multiplePlaces.push({"address" : routePoint.address, "places" : response.places, "label" : routePoint.label});
                                }
                                
                                geoResult = { "label" : routePoint.label, "lon" : lon, "lat" : lat, "address" : address, municipality : municipality };
                                _geoSearchResult.push( geoResult );
    
                                self.pointHandler.addToCache( address, lon, lat, municipality );
                                self.pointHandler.updateUIRoutePoint( routePoint.label, address, lon, lat, municipality );
                            } else {
                                geoResult = {"error" : true};
                                _geoSearchResult.push(geoResult);
                                _c.log('getGeoCodesForRoutePoints - no valid result data from XHR for .' + reqCount + ' ' + routePoint.address);
                                _c.log(response);
                            }
                            
                            reqCount -= 1; 
                            if (reqCount === 0) {
                                //_c.log("All geocoding responses gotten:");
                                //_c.log(_geoSearchResult);
                                _handleGeoResult(_geoSearchResult);
                            }                    
                    }));
                }                  
            } else if (address.substring(0, 1) == "(") {    
                // Address in url is in (lon,lat)-format, so parse coordinates from address-string. 
                var commaLocation = address.indexOf(",");
                
                lon = address.substring(1, commaLocation) - 0;
                lat = address.substring(commaLocation+1, address.length-1) - 0;
                    
                self.pointHandler.updateRoutePoint(routePoint.label, "", lon, lat);
                geoResult = {"lon":lon, "lat":lat, "label":routePoint.label, "address":Constants.definedOnMap};
                _c.log('getGeocodesForUIPoints - Määritelty kartalla:');
                _c.log(geoResult);

                _geoSearchResult.push(geoResult);
                reqCount -= 1;
            } else {        // address not defined, will look for data UI 
                var UIPoint = self.pointHandler.getUIRoutePoint(routePoint.label);
                
                //_c.log('_getGeocodesForRoutePoints:: get point ' + routePoints.label + ' from UIRoutePoints');
                geoResult = {
                    "label": routePoint.label,
                    "address":Constants.definedOnMap,
                    "lat" : UIPoint.lat,
                    "lon" : UIPoint.lon
                };
                //_c.log('getGeocodesForUIPoints - UIpoint:')
                //_c.log(geoResult);
                _geoSearchResult.push(geoResult);
                reqCount -= 1;
                
                if (reqCount === 0) {
                    _handleGeoResult(_geoSearchResult);
                }                    
            }
        });
        
        return allRequests;
    };
    
    // checks geodata, normalizes object
    // if multiple places for search, show user options
    // if one possibility for each address, handle geodata to a format that can be given to _fetchRoute
    // otherwise notify problem. 
    _handleGeoResult = function(geopoints) {
        var error = false, 
            lon, lat, address, UIPoint,
            pointName = "";
 
        _allowSubmit = true;
        _c.log('_handleGeoResult params:');
        _c.log(geopoints);

        // All data gotten

        if (!geopoints[0].lat || !geopoints[1].lat) {
            _c.log("_getLocation ERROR");
            error = true;
        } else if (_multiplePlaces.length > 0) {       // if multiple options for a location, don't draw route, just show map of finland.
            // multiple places in data for given address
            error = false;
            _showMapCenterEmpty();
            self.adjustRouteDetailsBoxLayout(); 
            _populateMultiplePlaces();
        } else {
            // sort coordinates and fetch route
            geopoints.sort(function(a, b) {
               return (a.label > b.label ? 1 : -1);
            });
            
            _fetchRoute({"points": geopoints});
        } 
        
        if (error) {
            _c.log('Routing._getLocationData() - route location not found.');
            SiteCatalystEvents.sendRoutingFetchRoute(self.$routeFrom.val(), self.$routeTo.val(), MapLayers.getVisibleLayer(), isLoggedIn, true);
            _noPlaces();
        } 
        
    };
    
    _getLocationData = function(addresses) {
        // TODO: rename function, if works this way. Consider if should be combined with handleGeoResult        
        var i = 0;
        
        $routeManeuvers.html("").addClass("loading");       // Clears driving instructions listing

        _getGeocodesForRoutePoints(addresses);       
    };
    

    // returns OL markers for route points.    
    _getRouteMarkers = function (points) {
        _c.log("Routing._getRouteMarkers ::"); 
        _c.log(points);
        
        var myMap = mapSearch.getMapObj(),
            features = [],
            routePoints = [],
            routePointsOL = [],
            markerWidth = Rangifer.Core ? 55 : 30,
            markerHeight = Rangifer.Core ? 36 : 25,
            labelYOffset = Rangifer.Core ? 22 : 15,
            labelXOffset = Rangifer.Core ? 0 : 15,
            graphicsYOffset = Rangifer.Core ? -33 : -25,
            graphicsXOffset = Rangifer.Core ? -28 : 0,
            projectionMap =  myMap.getProjectionObject(),
            projectionEPSG4326 = new OpenLayers.Projection('EPSG:4326'),
            routePointCount = points.length,
            routeMarkerImage = Rangifer.Core ? "/pics/bubble-to.png" : "/pics/bubble-routepoint.png",
            browserVersion;
                      
        if ($.browser.msie) {
            browserVersion = parseInt($.browser.version, 10); 
            if (browserVersion <= 9) {
                labelYOffset = Rangifer.Core ? 18 : 11; 
            } 
        }
        
        for (var i=0; i < routePointCount; i++) {
            routePoints[i] = new OpenLayers.Geometry.Point(points[i].lon, points[i].lat);
            routePoints[i].transform(projectionEPSG4326, projectionMap);
            routePointsOL.push({
                "point" : routePoints[routePoints.length - 1],
                "style" : {
                    externalGraphic: Settings.static_path + routeMarkerImage,
                    graphicZIndex: 900,
                    backgroundGraphicZIndex : 900,
                    zIndex: 900,
                    graphicWidth: markerWidth,
                    graphicHeight: markerHeight,
                    graphicOpacity: 1,
                    graphicXOffset: graphicsXOffset,
                    graphicYOffset: graphicsYOffset,
                    labelXOffset : labelXOffset,
                    labelYOffset : labelYOffset,      
                    fontColor : "#FFFFFF",
                    fontSize : 12,
                    fontWeight: "bold",
                    label : points[i].label
                }
            });  // labelYOffset: 15 for EMAPI > 1.74, for older 11.
            features.push(new OpenLayers.Feature.Vector(routePointsOL[i].point, null, routePointsOL[i].style));
        }    
        
        return features;
    };


    /**
     * Public functions
     *
     */
    // adjustRouteDetailsBoxLayout(top) : calculate position in UI for the route details-box that contains route manouvers among other things.
    this.adjustRouteDetailsBoxLayout = function(top) {
        var viewportHeight = $(window).height(),
            mainHeaderHeight = $("#main-header").height() || 0,
            routingHeight = $("#tab-route-content").height(),
            footerHeight = $("#eniro-map-toolbar").height() || 0,
            multiplePlacesHeight = $routeMultiplePlaces.height() || 0,
            height = viewportHeight - (routingHeight + mainHeaderHeight + footerHeight + multiplePlacesHeight) - 140;
            
        if (typeof top === "undefined") {
            top = $routeTabContent.height();
        }
        $routeDetails.animate({top: 50 + top}, SLIDETIME/2);
        $routeManeuvers.animate({height: height}, SLIDETIME/2);
        _c.log("maneuversHeight: " + height);
    };    
    
    this.drawRoute = function (data) {
        $routeSubmit.removeData("inProgress");
        $("#place-not-found").hide();
        $('#mapsearch_sidebar').removeClass("loading");
        // OBSOLETE? $("#tab-route .tab-header").trigger("click");

        var points = [],
            compressed = (typeof data.route.encodedCoordinates !== "undefined" && data.route.encodedCoordinates !==""),
            pos =  (compressed) ? google.maps.geometry.encoding.decodePath(data.route.encodedCoordinates) : data.route.coordinates,
            myMap = mapSearch.getMapObj(),
            routingLayer = mapSearch.getLayers().routingLayer,
            posi, 
            i=0,
            len=pos.length;

        function getX(compressed, pos) {
            if(compressed){
                return pos.Oa;
            } 
            return pos[0];
        }
        
        function getY(compressed, pos) {
              if (compressed) {
                  return pos.Na;
              } 
              return pos[1];
        }
        
        _maneuvers = data.route.maneuvers;
        
        _distance = ( function () {
            var iL = _maneuvers.length;
            var distance = 0;

            while (iL--) {
                distance += _maneuvers[iL].distance;
            }

            return distance;
        }());
        
        _distance = (_distance / 1000).toFixed(2);      // presumed distance is in meters.

        _estimation = data.route.travelTime;

        routingLayer.removeAllFeatures();

        for (; i < len; i++) {
            posi = pos[i];

            var point = new OpenLayers.Geometry.Point(getX(compressed, posi), getY(compressed, posi));
            point.transform(new OpenLayers.Projection('EPSG:4326'), myMap.getProjectionObject());
            points.push(point);
        }
        
        var geometry = new OpenLayers.Geometry.LineString(points);
        
        var feature = new OpenLayers.Feature.Vector(geometry, null, {
            strokeColor: '#48409E', // '#48409E', '#2ca5ff' 
            strokeOpacity: 0.7,
            strokeWidth: 5
        });
        
        routingLayer.addFeatures(feature);        
    };
    
    this.routeFromValue = function() {
        var placeHolder = self.$routeFrom.attr('placeholder'), 
            fromValue   = self.$routeFrom.val();
        
        return (placeHolder == fromValue ? undefined : fromValue); 
    };

    this.routeToValue = function() {
        var placeHolder = self.$routeTo.attr('placeholder'), 
            toValue   = self.$routeTo.val();
        
        return (placeHolder == toValue ? undefined : toValue); 
    };
    
    
    this.routeAddSearchMeta = function(data, points) {
        var routingLayer = mapSearch.getLayers().routingLayer,
            myMap   = mapSearch.getMapObj(),
            urlObj  = Common.getCurrentUIState(), 
            lon     = urlObj.longitude,
            lat     = urlObj.latitude,
            printLayout = urlObj.printLayout,
            zoomLevel = urlObj.zoomLevel;
        
        routingLayer.addFeatures(
            _getRouteMarkers(points)
        );

        if (!!lon && !!lat && !!zoomLevel && printLayout) {
            myMap.setCenter(new EM.LonLat(lon, lat), zoomLevel, false);
        } else {
            myMap.zoomToExtent(routingLayer.getDataExtent());
        }

        _displayAdditionalInfo({
            "maneuvers" : _maneuvers
        });

        //_displayRouteAdvertisement(points);

        LoadingIndicator.remove(/* from fetchRoute */);
        $("#route-details").addClass("notEmpty");
        Tabs.activateRouteTab();
    };

    this.setManeuversHeight = function(height) {
        if (typeof height == "undefined") {
            height = $("body").height() - ($("#tab-route-content").height() + $("#eniro-map-header").height() + $("#eniro-map-toolbar").height() + 20);
        }
        _c.log("setManouversHeight: "+ height);
        $routeManeuvers.css("height", height);
    };

    this.toggleLayer = function () {
        var _routingLayer = mapSearch.getLayers().routingLayer;
        var visibility = !_routingLayer.getVisibility();

        _routingLayer.setVisibility(visibility);
    };
    
    this.submitSearch = function () {
        $routeSubmit.trigger("click");
    };
    
    init = ( function () {
        _addEventListeners();
    }());
    return this;
};


/**
 * @constructor MapPopup
 * 
 * return {Object} MapPopup
 */
 
var MapPopup = (function () {
    var self = {},
    $ = jQuery;

    var _popupExists = false,
    _popupHovered = false,
    _feature,
    _popup,
    _popupLeft = false,
    _toGetExtra;
    
    // Private function declarations
    var _addCustomPopupListeners,
        _getRatings,
        _populateRatings,
        _onPopupClose;


    _addCustomPopupListeners = function () {
        _c.info("MapPopup :: _addCustomPopupListeners()");
        var _toClosePopup,
            _markerLayer = mapSearch.getLayers().markerLayer;

        $("body").live("keyup", function (evt) {
            if (evt.keyCode === 27) {
                MapPopup.close(true);
            }
        });
        
        $("#featurePopup").live("mouseenter mouseleave", function (evt) {
            //_c.log('featurepopup mouseenter mouseleave');
            _popupHovered = (evt.type === "mouseenter");
            _popupLeft = (evt.type === "mouseleave");

            _c.log(_feature.style.index);
            if (_popupLeft) {
                setTimeout( function () {
                    $("div.vcard").removeClass("hovered");
                },100);
            }
            MapPopup.handle(evt);
        });
        
        _markerLayer.events.register("featurehighlighted", _markerLayer, function (evt) {
            _c.info("event register highlighted");
            clearTimeout(_toClosePopup);

            var $vcard = $("div.vcard");

            if ($vcard.length) {
                $vcard.eq(evt.feature.style.index).addClass("hovered");
            }

            MapPopup.close(true);
            _feature = evt.feature;
            var _evt = evt;
            _toClosePopup = setTimeout( function () {
                MapPopup.handle(_evt);
            }, 160);
        });
        
        _markerLayer.events.register("featureunhighlighted", _markerLayer, function (evt) {
            /*var _feature = evt.feature,
            style = _feature.style,
            newStyle = style.externalGraphic.replace("hover", "default");

            style.externalGraphic = newStyle;*/
            // _markerLayer.redraw();

            if (_popupLeft) {
                setTimeout( function () {
                    $("div.vcard").removeClass("hovered");
                },100);
            }
        });
       
    };
    
    _getRatings = function (id) {
        var api_path = Settings.api_path;
        $.getJSON(api_path + "/yellowpagemap?id="+ id, function (data) {
            var company = (data.yellowPageInfo.length) ? data.yellowPageInfo[0] : false;
    
            if (company) {
                _populateRatings(company.ratings);
            }
        });
    };
    
    _populateRatings = function (ratings) {
        var _listOfRatings = ratings || false,
            _ratings = "<div class='popupExtraRatings'><h3>Asiakkaiden mielestä</h3><div class='ratingsEmblem'></div>",
            _amountOfRatings = 0,
            i = 0, 
            l = _listOfRatings.length;
            
        if (!_listOfRatings) {
            $("#popupFooter .popupRatings").removeClass("loading").find(".amount").text("0");
            return;
        }
        
        while (l--) {
            _amountOfRatings += parseInt(_listOfRatings[l].positiveRatings, 10);
        }

        // TOP3 (ratings are already sorted DESC)
        _listOfRatings = _listOfRatings.slice(0,3);

        l= _listOfRatings.length;
        for (;i<l;i++) {
            _ratings += "<div class='popupRating'>";
            _ratings += "<span class='positiveRatings'>"+ _listOfRatings[i].positiveRatings +"</span>";
            _ratings += "<span class='categoryName'>"+ _listOfRatings[i].categoryName +"</span>";
            _ratings += "</div>";
        }
        
        _ratings += "</div>"; // Closing for .popupExtraRatings

        /**
         * This nullifies the ratings results when first positiveRating is "0"
         * which means that there are no ratings at all
         */
        if (!ratings.length || (ratings.length && ratings[0].positiveRatings === 0)) {
            _ratings = "";
        }
        
        $("#popupData .popupExtra").append(_ratings);
        $("#popupFooter .popupRatings").removeClass("loading").find(".amount").text(_amountOfRatings);
    };
    
    _onPopupClose = function (evt) {
        _c.log("onPopupClose()");
        self.close(true);
    };

    self.handle = function (evt, instant) {
        _c.log("MapPopup.handle()");
        _c.log(evt);

        if (_popupLeft) {
            this.close();
        } else if (!_popupExists) {
            this.show();
        }
    };

    self.close = function (instant) {
        if (!_feature) {
            return false;
        }

        clearTimeout(_toGetExtra);
        
        var style = _feature.style,
            newStyle = style.externalGraphic.replace("hover", "default");

        style.label += "";
        style.externalGraphic = newStyle;
        mapSearch.getLayers().markerLayer.redraw();

        if (!_popup) {
            return false;
        }

        _c.log("MapPopup.close()");

        var closeInstantly = instant || false;
        var _close = function () {
            mapSearch.getMapObj().removePopup(_popup);
            _popupExists = false;
            _popupHovered = false;
            _popupLeft = false;
        };
        if (closeInstantly) {
            _close();
        } else {
            $("#featurePopup").fadeTo(200, 0.0, _close);

        }
    };

    self.show = function () {
        var i, l,
        _index = _feature.style.index,
        _current = PopupData[_index];

        if (_current === undefined) {
            return false;
        }
        _c.log("mapPopup.show()");
        //_c.log(_current);

        var _logoUrl = (_current.logoUrl) ? "<img class='logoUrl' src='"+ _current.logoUrl +"' />" : "";
        var _phoneNumber = (_current.phoneNumber) ? "<div class='phoneNumber'>"+ _current.phoneNumber +"</div>" : "";
        var _amountOfRelatedTacticalAds = (_current.relatedTacticalAds) ? _current.relatedTacticalAds.length : 0;
        var _dealCount = _current.dealCount ? _current.dealCount : 0; 
        var _offerings = "";
        var _adCount;

        // Offerings. In Rangifer use tactical ads, in Alces use deals.
        _adCount = Rangifer.Core ? _amountOfRelatedTacticalAds : _dealCount; 
        if (_adCount) {
            i = 0;
            l = Math.min(3, _adCount);
            
            for (;i<l;i++) {
                _offerings += "<div class='popupOffering'><div class='offeringEmblem'></div>";
                if (Rangifer.Core) {
                    _offerings += "<p class='description'>"+ _current.relatedTacticalAds[i].description +"&nbsp;<a class='popupMore' href='"+ _current.relatedTacticalAds[i].url +"'>Lue lisää &raquo;</a></p>";
                }
                _offerings += "</div>";
            }

            if (l < _adCount) {
                _offerings += "<h4 id='amountOfOfferings'>(näytetään "+ l +" etua)</h4>";
            } 
        }

        _popupExists = true;
        
        _popup = new OpenLayers.Popup.FramedCloud("featurePopup",
            _feature.geometry.getBounds().getCenterLonLat(),
            new OpenLayers.Size(305, 120),
            "<div id='popupData'>"+
                _logoUrl +
                "<h2 class='popupHeader' data-id='"+ _current.id +"' data-main-category='"+ _current.mainCategory+"'><a href='"+ _current.companyUrl +"'>"+ _current.title +"</a></h2>" +
                _phoneNumber +
                "<p class='popupTextContent'><span class='streetAddress'>"+ _current.streetAddress + "</span>, " + "<span class='postalCode'>" + _current.postalCode + "</span>" + "&nbsp;<span class='postalArea'>" + _current.postalArea +"</span></p>"+
                "<a class='popupLink' rel='external' target='_blank' href='"+ _current.websiteUrl +"'>"+ _current.websiteUrl +"</a>"+
                "<div class='popupExtra'>"+ _offerings +"</div>"+
            "</div>"+
            "<div id='popupFooter'>"+
                "<div class='popupOfferings'><span class='amount'>"+ _adCount +"</span><span class='suffix'>kpl</span></div>"+
                "<div class='popupRatings'><span class='amount'>&nbsp;&nbsp;&nbsp;</span><span class='suffix'>kpl</span></div>"+
                "<a class='popupRoute' href='#'>Reittiohjeet&nbsp;&raquo;</a>"+
            "</div>",
            null, true, _onPopupClose);

        _popup.autoSize = false;
        _popup.panMapIfOutOfView = false;
        _popup.fixedRelativePosition = true;
        _popup.keepInMap = true;

        _popup.expand = function (evt) {
            var target = (evt.target ? evt.target : (evt.srcElement ? evt.srcElement : false));
            var $popupExtra = jQuery(".popupExtra");
            
            if (!!target && target.nodeName.toLowerCase() !== "a" && $popupExtra.length && $popupExtra[0].childNodes.length) {
                jQuery(".popupExtra").show();
                this.updateSize();
            }

            if (jQuery(target).hasClass("popupLink")) {
                SiteCatalystEvents.sendExitLink.call(target);
            } else if (jQuery(target).hasClass("popupMore")) {
                SiteCatalystEvents.sendOfferLink.call(target);
            } else if (jQuery(target).hasClass("popupRoute")) {
                
                if (evt.preventDefault) {  
                    evt.preventDefault();  
                } else {  
                    evt.returnValue = false;  
                    evt.cancelBubble = true;  
                }
                
                var $addr = jQuery("#popupData").find(".popupTextContent");
                var address = $addr.find(".streetAddress").text() + ", " + $addr.find(".postalArea").text();
                
                jQuery("#link-routes-on-map").trigger("click");
                jQuery("#tab-route .tab-header").trigger("click");
                jQuery("#route-to").val(address);
                jQuery("#route-from").focus();
            } else {
                SiteCatalystEvents.dispatch(0, false, true);
            }
            
            return false;
        };
        
        _popup.events.register("click", undefined, function (evt) {
            _popup.expand(evt);
        });

        _feature.popupBubble = _popup;
        _popup.feature = _feature;

        mapSearch.getMapObj().addPopup(_popup);
        
        _toGetExtra = setTimeout(function () {
            $("#popupFooter .popupRatings").addClass("loading");
            _getRatings(_current.id);
        },1000);
    };

    self.init = function () {
        _c.log('apPopup.init();');
        _addCustomPopupListeners();
    };
    
    return self;
    
}());


var MapLayers = (function ($) {
    var self = {};

    // Private Variables
    var _layersInUse = ["AERIAL", "HYBRID", "MAP", "NAUTICAL", "TOPO"],
        _currentlyVisible = false; 
    
    // Private Function Declarations
    var _addEventListeners,
        _addOnVisibilityChange;
        
    _addEventListeners = function () {
        _addOnVisibilityChange();
    };
    
    _addOnVisibilityChange = function () {
        var i = 0,
            l = _layersInUse.length;
            
        for (;i<l;i++) {
            var layer = EM.Layer[_layersInUse[i]];
            if (layer !== undefined) { 
                layer.events.register("visibilitychanged", null, function (evt) {
                    // This ensures that map is displayed when switching back from BLOM
                    jQuery("#mapDiv").css("display", "block");
                    
                    var layer = evt.object;

                    if (layer.getVisibility()) {
                        _currentlyVisible = _layersInUse[i];
                         
                        if (layer === EM.Layer.NAUTICAL) {
                            $("#nautical-disclaimer").fadeIn(320);
                            $("#tab-route").addClass("disabled");
                            $("#tab-location .tab-header").trigger("click");
                            layer.layerOverlay.setVisibility(true);
                        }
                    }
                    else {
                        if (layer === EM.Layer.NAUTICAL) {
                            $("#nautical-disclaimer").fadeOut(320);
                            $("#tab-route").removeClass("disabled");
                            layer.layerOverlay.setVisibility(false);
                        }
                    }
                });
            }
        }
    };
    
    self.getVisibleLayer = function () {
        // First time we're not sure which layer is displayed
        if (!_currentlyVisible) {
            var i = 0,
                l = _layersInUse.length;
                
            for (;i<l;i++) {
                if (EM.Layer[_layersInUse[i]].visibility) {
                    _currentlyVisible = _layersInUse[i];
                    break;
                }
            }
        }
        
        return _currentlyVisible;
    };
    
    self.setVisibleLayer = function (target) {
        var currentLayer = EM.Layer[self.getVisibleLayer()];
        
        if (currentLayer.setVisibility) {
            currentLayer.setVisibility(false);
        }
        if (currentLayer.layerOverlay) {
            currentLayer.layerOverlay.setVisibility(false);
        }
        
        var newLayer = EM.Layer[target.toUpperCase()];
        
        if (newLayer !== undefined) {
            mapSearch.getMapObj().setBaseLayer(newLayer);
            if (newLayer.layerOverlay) {
                newLayer.layerOverlay.setVisibility(true);
            }
        }
    };
    
    self.init = function () {
        _c.info("MapLayers.init()");
        _addEventListeners();
        
        var layer = QueryParams.layer || "";
        
        if (layer.length) {
            self.setVisibleLayer(layer);
        }
    };
    
    return self;
}(jQuery));


/** 
 * Toolbar in Footer
 */
var Toolbar = (function ($) {
    var self = {},
        _map,
        $toolbar = $("#eniro-map-toolbar"),
        $dataPopup = $("#toolbar-data-popup"),
        _activeMeasureControl,
        _activeTabId,
        _allowPopupMovement = true,
        _gpsControl,
        _lastUrlShortened = "",
        _lastPopupX,
        _lastPopupY,
        _measureControls,
        _sketchSymbolizers;

    _sketchSymbolizers = {
        "Point": {
            "externalGraphic" : mapPreferences.locationMarkerIcon,
            "graphicWidth": 25,
            "graphicHeight": 25,
            "graphicOpacity": 1
        },
        "Line": {
            strokeWidth: 3,
            strokeOpacity: 1,
            strokeColor: "#5080B0"
        },
        "Polygon": {
            strokeWidth: 2,
            strokeOpacity: 1,
            strokeColor: "#666666",
            fillColor: "white",
            fillOpacity: 0.3
        }
    };


    
    // Private function declarations
    var _addEventListeners,
        _addControls,
        _addCopyToClipboard,
        _createShortURL,
        _formatMetricDistance,
        _getDistanceBetweenPoints,
        _handleBodyClickEvents,
        _handleDistanceClick,
        _handleDistanceDblClick,
        _handlePopupMove,
        _handleGPSMouseMove,
        _handleGPSClick,
        _handleMeasurementsTotal,
        _handleShortURL,
        _handleToolbar,
        _resetProperties,
        _setMeasureDistance,
        _setMeasureDistancePartial,
        _setMeasureDistanceTotal;
    
    
    _addEventListeners = function () {
        $toolbar.delegate("li.topLevel", "click", _handleToolbar);
/*        
        $("#shortened-url").live("mouseup", function (evt) {            
            _handleGPSClick(evt);
            $(this).focus().select();
        }); 
*/

        _map.events.register("mousemove", _map, function (evt) {
            if (_allowPopupMovement) {
                if (_activeTabId === "point") {
                    //_c.log('handleGPSMouseMove');
                    _handleGPSMouseMove(evt);
                }
                // FIXME: this is not needed if setImmediate() starts to work 
                else if (_activeTabId === "distance") {
                    _handlePopupMove(evt); 
                }
            }
        });
        
    };
    
    // return distance between point1 and point2, takes into account the map projection etc.
    _getDistanceBetweenPoints = function(point1, point2) {
        var mapObj      = mapSearch.getMapObj(),
            projection  = mapObj.projection,
            line        = new OpenLayers.Geometry.LineString([point1, point2]), 
            distance    = line.getGeodesicLength(projection);
                     
        return distance;
    };
            
    
    _addControls = function () {
        var _style = new OpenLayers.Style(),
            _styleMap, xI;
            
        _style.addRules([
            new OpenLayers.Rule({symbolizer: _sketchSymbolizers})
        ]);
    
        _styleMap = new OpenLayers.StyleMap({"default": _style});    

     /*_gpsControl = new OpenLayers.Control();
        OpenLayers.Util.extend(_gpsControl, {
                draw: function () {
                    // this Handler.Box will intercept the shift-mousedown
                    // before Control.MouseDefault gets to see it
                    this.box = new OpenLayers.Handler.Box( _gpsControl, 
                        {"done": this.notice},
                        {keyMask: OpenLayers.Handler.MOD_SHIFT});
                    this.box.activate();
                },

                notice: function (bounds) {
                    alert(bounds);
                }
        }); */
               
        _measureControls = {
            /*"point" : new OpenLayers.Control(
                OpenLayers.Handler.Point, {
                    persist: true,
                    immediate: true,
                    geodesic: true,
                    callbacks: {
                        "point" : function(evt) {
                            _handleGPSClick(evt);  
                        },
                        "done": function(evt) {
                            _handleGPSClick(evt);
                        }
                    },
                    handlerOptions: {
                        layerOptions: {styleMap: _styleMap}
                    }
                }
            ), */
            "distance": new OpenLayers.Control.Measure(
                OpenLayers.Handler.Path, {
                    persist: true,
                    immediate: true,
                    geodesic: true, 
                    callbacks: {
                        "create": function(evt) {
                            _allowPopupMovement = true;
                            _setMeasureDistance(0, '', 0, '');                           
                        },
                        "point": function(evt) {
                            _handleDistanceClick(evt);
                        },
                        "done": function() {
                            _allowPopupMovement = false;
                            $dataPopup.find("#btn-continue-measuring").show();
                             _c.log("distance done");
                        }
                    },
                    handlerOptions: {
                        layerOptions: {styleMap: _styleMap}
                    }
                }
            )/*,
            "polygon": new OpenLayers.Control.Measure(
                OpenLayers.Handler.Polygon, {
                    persist: true,
                    handlerOptions: {
                        layerOptions: {styleMap: _styleMap}
                    }
                }
            )*/
        };
        for (xI in _measureControls) {
            _map.addControl(_measureControls[xI]);
        }        
        // Add measure controls to map
    };
    
    _addCopyToClipboard = function() {
        var _clip = new ZeroClipboard.Client();
        
        _clip.addEventListener('mouseup', function(client) {
            _clip.setText(jQuery("#shortened-url").val());
        });

        _clip.glue("copy-to-clipboard", "copy-to-clipboard-container");
    };
    
    _createShortURL = function () {
        var _urlToShorten = encodeURIComponent(Common.getCurrentUIState().fullUrl),
            _jsonpURL = Settings.rangifer_api_path + "/shorturl/shorten.jsonp?originalUrl="+ _urlToShorten;
            
        if (_lastUrlShortened === _urlToShorten) {
            return false;
        }

        _c.log("shorten: "+ _urlToShorten);

        _lastUrlShortened = _urlToShorten;

        $.ajax({
            url: _jsonpURL,
            cache : true,
            dataType: 'jsonp',
            data: [],
            success: _handleShortURL
        });
    };
    
    _formatMetricDistance = function(distance) {
        var unit = "m"; 
        if (distance > 1000) {
            distance = (distance / 1000).toFixed(2);
            unit = "km";
        }
        return ({"distance": distance, "unit":unit});
    };
    
    _handleBodyClickEvents = function () {
        _c.log('handleBodyClickEvents');
        var _mapObj = mapSearch.getMapObj();
        
        $("#mapDiv").bind("mouseup.handleBodyClickEvents", function (evt) {            
            if (_activeTabId === "distance" || !!$(evt.target).closest("#eniro-map-toolbar").length) {
                return false;
            } 
            
            if (_activeTabId === "point") {
                //_c.log(evt);
                _handleGPSClick(evt);
                cancelMapZoom = true;
            } else {
                if (_activeMeasureControl !== undefined) {
                    _activeMeasureControl.deactivateMapControl();
                }
                _resetProperties();
                $("#mapDiv").unbind("mouseup.handleBodyClickEvents");
                _c.log(evt);
            }

            evt.preventDefault();
            evt.stopPropagation();
            return false;
        });
    };
    
    _handleDistanceClick = function(evt) {
        var point1, point2, partialDistance, i, totalFormatted, partialFormatted, 
            measureLinePoints = evt.parent.components,
            totalDistance = 0,                                   
            partialDistanceUnit = "m",
            totalDistanceUnit = "m",
            lineLength = measureLinePoints.length;
        
        if (measureLinePoints && measureLinePoints.length > 2) {
            point1 = measureLinePoints[lineLength-2];
            point2 = measureLinePoints[lineLength-3];
            
            for (i=0; i < (lineLength-1); i++) {
                point1 = measureLinePoints[i];
                point2 = measureLinePoints[i-1];
                totalDistance += _getDistanceBetweenPoints(point1, point2);
            }
            
            totalFormatted = _formatMetricDistance(totalDistance);
            _setMeasureDistanceTotal(totalFormatted.distance, totalFormatted.unit);
            
            partialDistance = _getDistanceBetweenPoints(point1, point2);
            partialFormatted = _formatMetricDistance(partialDistance);                                
            _setMeasureDistancePartial(partialFormatted.distance, partialFormatted.unit);
        }        
    };
    
    // stops measuring    
    _handleDistanceDblClick = function(evt) {
        _allowPopupMovement = false;
        _activeMeasureControl.deactivateMapControl();
        
        return true;
    };
    
    // re-positions data-popup
    _handlePopupMove = function (evt) {
        var x = evt.clientX || evt.x,
            y = evt.clientY || evt.y,
            screenHeight = $(document).height() - 40,   // 40 is the height of footer 
            screenWidth = $(document).width(),
            popupHeight = 103, 
            popupWidth = 155;
        
        if ( ((y + popupHeight) > screenHeight) ) {
            y -= (popupHeight + 30);
        } 
            
        if ( (x + popupWidth) > screenWidth ) {
            x -= (popupWidth + 15);
        }
        
        _lastPopupX = x + 15;
        _lastPopupY = y + 15;
           
        $dataPopup.css({
            "left": x+15,
            "top": y+15
        });       
    };
    
    
    _handleGPSClick = function (evt) {
        _c.log('Common._handleGPSClick()');
        var _lonlat, _point, _px, feature, 
            features = [],
            _mapObj = mapSearch.getMapObj(),
            marker = Marker.create({
                "type": "default",
                "size": "normal",
                "index": 2,
                "label": ""
            });
        _c.log(evt);
        // if event is openlayers event (has x and y) or jQuery-event (has pageX and pageY)            
        if ( !!evt.x && !!evt.y ) {
            _lonlat = OpenLayers.Layer.SphericalMercator.inverseMercator( evt.x, evt.y );
            _px = _mapObj.getPixelFromLonLat( new OpenLayers.LonLat( evt.x, evt.y ) );
            _point = new EM.LonLat( evt.x, evt.y );
        } else if ( !!evt.pageX && !!evt.pageY ) {
            _px = { x: evt.pageX, y: evt.pageY };
            _lonlat = Common.Geo.getLonLatFromPixel( _px.x, _px.y );
            _point = new EM.LonLat(_lonlat.lon, _lonlat.lat);
        }
        
/*        feature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(_point.lon, _point.lat),
            {
                tooltip: {},
                onclickURL :  null
            }, marker
        );
        features.push(feature);
        mapSearch.getLayers().markerLayer.addFeatures(features);              */  
        
        if (_allowPopupMovement) {
            $dataPopup.find("#lonlat").replaceWith("<input id='lonlat' value='" + _lonlat.lon.toFixed(5) + "," + _lonlat.lat.toFixed(5) + "' />");
            _allowPopupMovement = false;
        } else {
            _handlePopupMove({x:_px.x, y:_px.y});

            if ($dataPopup.find("#lonlat input").length <= 0) {
                $dataPopup.find("#lonlat").replaceWith("<input id='lonlat' value='' />");
            }
        }

        $dataPopup.find("#lonlat").attr("value", _lonlat.lat.toFixed(5) + ", " + _lonlat.lon.toFixed(5));
        $dataPopup.find("#lonlatSexa").html('N ' + Common.Geo.decimalToSexagesimal(_lonlat.lat) + '<br />E ' + Common.Geo.decimalToSexagesimal(_lonlat.lon));
    };
    
    
    _handleGPSMouseMove = function (evt) {
        var _lonlat = _map.getLonLatFromViewPortPx(evt.xy);
        _lonlat.transform(_map.getProjectionObject(), new OpenLayers.Projection("EPSG:4326"));
        
        _handlePopupMove(evt);        
        $dataPopup.find("#lonlat").html(_lonlat.lat.toFixed(5) +" N, "+ _lonlat.lon.toFixed(5) +" E");
        $dataPopup.find("#lonlatSexa").html('N ' + Common.Geo.decimalToSexagesimal(_lonlat.lat) + '<br />E ' + Common.Geo.decimalToSexagesimal(_lonlat.lon));
    };
    
    
    _handleShortURL = function (data) {
        $("#shortened-url").val(data.shortUrl).focus().select();
    };
    

    _handleToolbar = function (evt) {
        var $this = $(this),
            alreadyActive = $this.hasClass("active");

        // contents of menu won't trigger this event
        if (!!$(evt.target).closest(".toolbar-container").length) {
            return false;
        }
        
        _resetProperties();
        _activeTabId = $this.attr("id");

        // deactivate point (GPS) control        
        if (_activeTabId == "point" && alreadyActive) {
            //$("#mapDiv").unbind("mouseup.handleBodyClickEvents");            
            //_cancelMapZoom = false;
        }
        
        // deactivate distance measurement control
        if (_activeMeasureControl) {
            if (_activeTabId == "point") {
                _activeMeasureControl.deactivate();                
            } else {
                _activeMeasureControl.deactivateMapMeasurement();
            }
        }
        
        if (!alreadyActive) {
            _handleBodyClickEvents();

            $this.addClass("active").find(".toolbar-container").addClass("active");
            _activeMeasureControl = _measureControls[_activeTabId];
            
            if (_activeTabId === "link-on-map") {
                _createShortURL();
                if ($("#copy-to-clipboard-container div").length === 0) {
                    _addCopyToClipboard();
                }
            } else if (_activeTabId === "point") {
                _c.log('activate point');
                _allowPopupMovement = true;
                $dataPopup.addClass("active").html("<p>Koordinaatit</p><div id='lonlatSexa'></div><p><b>WGS84 - desimaaliin:</b></p><div id='lonlat'></div>");
                
                //_activeMeasureControl.activate();
            } else if (_activeTabId === "distance") {
                _c.log('activate distance');

                $dataPopup.addClass("active").html("<p><label>Osamatka</label><b id='partial-distance'></b></p><p><label>Kokonaismatka</label><b id='complete-distance'></b></p><span class='button-box' id='btn-continue-measuring' style='display:none;'><input onclick='Toolbar.continueMeasurement();' class='button' id='emContinueButton' type='button' value='Jatka mittaamista' /></span>");
                _activeMeasureControl.activateMapMeasurement();   
            } 
        }
         
    };
    
    _resetProperties = function () {
        _activeTabId = "";
        $dataPopup.css({"left":"0px", "top":"-100px"}).removeClass("active").html("");
        $toolbar.find(".active").removeClass("active");
    };
    
    _setMeasureDistance = function(partialDistance, partialUnit, totalDistance, totalUnit) {
        _setMeasureDistancePartial(partialDistance, partialUnit);
        _setMeasureDistanceTotal(totalDistance, totalUnit);
    }
    
    // update partial distance to data popup for distance measurement tool
    _setMeasureDistancePartial = function (partialDistance, partialUnit) {
        $dataPopup.find("#partial-distance").text(parseFloat(partialDistance).toFixed(2) +" "+ partialUnit);
    };
    
    // update partial distance to data popup for distance measurement tool
    _setMeasureDistanceTotal = function (totalDistance, totalUnit) {
        $dataPopup.find("#complete-distance").text(parseFloat(totalDistance).toFixed(2) +" "+ totalUnit);
    };


    self.continueMeasurement = function() {
        $dataPopup.find('#btn-continue-measuring').hide();        
        if (_activeMeasureControl && _activeTabId === "distance") {
            _allowPopupMovement = true;
            _activeMeasureControl.partialContinue();
        }
    };
    
    self.deactivateControls = function () {
        _resetProperties();
        
        if (_activeMeasureControl) {
            _activeMeasureControl.deactivate();
        }
    };
    
    self.init = function () {
      _map = mapSearch.getMapObj();
      _addEventListeners();
      _addControls();
      
      $toolbar.fadeIn(320);
    };
    
    return self;
    
}(jQuery));


var Extend = ( function () {
    var _extendFeatures = function () {
        OpenLayers.Feature.Vector.prototype.toggleTooltip = function (isHighlighted) {
            this.isHighlighted = isHighlighted || !this.isHighlighted;
            var type = this.isHighlighted ? "featurehighlighted" : "featureunhighlighted";

            this.layer.events.triggerEvent(type, {feature:this});
        };
    };
    
    this.init   = function () {
        _extendFeatures();
    };
    return this;
}());


/**
 *
 * Creates a new mapSearch instance for Rangifer
 *
 * @constructor Rangifer.mapSearch
 * @return {Object} Returns a mapSearch object
 *
 */
Rangifer.mapSearch = (window.Alces && Alces.Core) ? false : function ($) {
    Rangifer.Core.Component.call(this);

    // Private Function variables
    var _handleOverOut,
        _initMapView,
        _showResults;

    var self = this;

    var _mapObj;    // EMAPI instance
    var $mapObj;    // jQuery instance
    var _markerLayer = new EM.Layer.Vector.Listing("My Layer", {
        'visibility': true,
        'clustered':false,
        'tooltip':false,//true,
        'isBaseLayer': false,
        'displayInLayerSwitcher': false
    });
    
    var _routingLayer = new OpenLayers.Layer.Vector('Routes');
    _routingLayer.setVisibility(false);

    var _what, _where;

    var _prefs = {
        "searchURL": Settings.api_path +"/yellowpagemap",
        "mapElem" : "",
        "sidebarResultTemplate" : ""
    };

    /**
     * Public Variables
     *
     */
    if (typeof Sidebar !== "undefined") {
        self.Sidebar = new Sidebar($);
    }
    if (typeof Routing !== "undefined") {
        self.Routing = new Routing($);
    }

    if (typeof VenueMarkers !== "undefined") {
        self.VenueMarkers = new VenueMarkers($);
    }

    self.isMapPanned = false;

    /**
     * @private _handleOverOut
     * Controls marker mouseover/out events
     *
     * @param {Object} event
     */
    _handleOverOut = function (evt) {
        var elem = evt.target || evt.srcElement,
        isOver = (evt.type === "mouseover");

        if (elem && elem.nodeName === "image") {
            var feature = _markerLayer.features[elem._style.index];

            if (feature === undefined) {
                return false;
            }

            var style = feature.style;

            if (!!style && isOver) {
                var newStyle = isOver ? style.externalGraphic.replace("default", "hover") : style.externalGraphic.replace("hover", "default");
                $("div.vcard").eq(style.index).toggleClass("hovered", isOver);
                feature.style.externalGraphic = newStyle;
                _markerLayer.redraw();
            }
        }
    };


    /**
     * @private _initMapView
     *
     * Initializes the default view of map
     *
     */
    _initMapView = function () {
        _c.info("mapSearch.initMapView()");

        // If we enter through route then default value of where shouldn't be populated
        if (QueryParams.routeFrom && QueryParams.routeTo) {
            $("#header-search-where").val("");
        }

        var _where = encodeURIComponent($("#header-search-where").val()),
            _what = Common.getCurrentUIState().what,
            _searchURL = _prefs.searchURL +"?where="+ _where +"&what="+ _what;

        Common.fixHeight();
        MapPopup.init();

        if (!!QueryParams.id) {
            _searchURL += "&id="+ QueryParams.id;
        } else if (!!QueryParams.latitude && !!QueryParams.longitude) {
            var lat = QueryParams.latitude || _prefs.geoLat;
            var lon = QueryParams.longitude || _prefs.geoLon;

            _searchURL += "&latitude="+ lat +"&longitude="+ lon;
            if (!!QueryParams.radius) {
                _searchURL += "&radius="+ QueryParams.radius;
            }
        }

        if (!!QueryParams.filter) {
            _searchURL += "&filter="+ decodeURIComponent(QueryParams.filter);
        }

        $('#mapsearch_sidebar').addClass("loading");

        self.Sidebar.handleResultsHeight();
        $("#mapsearch_results").css("left", "0px");

        _mapObj.addLayers([_markerLayer,_routingLayer]);

        if (QueryParams.routeFrom && QueryParams.routeTo) {
            if (!QueryParams.printLayout) {
                mapSearch.addEventListeners();                
            }
        } else {
            Common.executeJSONSearch(_searchURL);
        }

    };

    _showResults = function (param) {
        _c.log("showResults()");
        var $results = $("#mapsearch_results");
        var startIndex = 1;

        if (param) {
            startIndex = 0;
            
            if (param.noResults) {
                $results.html("<span id='no_results'>Ei yhtään hakutulosta.</span>");
                return false;
            } else if (param.error) {
                if (param.error === 500) {
                    $results.html("<span id='no_results'>Palvelussa on tilapäinen häiriö. Yritä myöhemmin uudelleen.</span>");
                }
            } else {
                _c.warn("Parameters were provided to showResults(), but they were handled incorrectly.");
            }
        }

        $('#mapsearch_sidebar').removeClass("loading");
        $results.stop().fadeTo(FADETIMESLOWER, 1.0);

        // Passes the company ids of first page results to Site Catalyst
        var companiesShown = $(".result_page").eq(0).find(".vcard");
        var iI=0,iL=companiesShown.length;
        var idsForSiteCatalyst = [];

        for (;iI<iL;iI++) {
            idsForSiteCatalyst.push(companiesShown.eq(iI).attr("id").substr(3));
        }

        SiteCatalystEvents.sendMapShowResults(idsForSiteCatalyst, MapLayers.getVisibleLayer(), jQuery("#header-search-where").val());

    };
    

    /**
     * @public mapSearch.addEventListeners
     * Binds all the necessary event listeners
     *
     * @param {boolean} Exclude map controls
     */

    self.addEventListeners = function (excludeSearch) {
        Common.addEventListeners(excludeSearch);

        _mapObj.events.register("mouseover", _markerLayer, function (evt) {
            _handleOverOut(evt);
        });
        _mapObj.events.register("mouseout", _markerLayer, function (evt) {
            _handleOverOut(evt);
        });

        self.Sidebar.$tab.live("click", function (evt) {
            evt.preventDefault();
            Tabs.toggle(evt);
        });

        self.Routing.$tab.live("click", function (evt) {
            evt.preventDefault();
            Tabs.toggle(evt);
        });

        $("div.vcard").live("click", function (evt) {
            if (MapPopup.instance) {
                MapPopup.instance.expand(evt);
            }
        });
        /**
         * Conditional listeners / triggers
         */
        if (QueryParams.routeFrom || QueryParams.routeTo) {
            $("#link-routes-on-map").trigger("click");
            if (QueryParams.routeFrom && QueryParams.routeTo) {
                $("#route-submit").trigger("click");
            }
        }
    };
    /**
     * @public appendData
     * Appends fetched data to sidebar and map
     *
     * @param {Object} data
     */
    self.appendData = function (data) {
        _c.info("Rangifer :: mapSearch.appendData()");

        var _queryExpansionsUsed = (data.queryExpansions && data.queryExpansions["any.word.partial.match.search.message"]) ? "yes" : "no";
        SiteCatalystEvents.setProp("prop1", $("#header-search-what").val());
        SiteCatalystEvents.setProp("prop17", _queryExpansionsUsed);

        var results = data.yellowPageInfo || [];

        if (results.length) {
            if (self.Sidebar.populate(results)) {
                self.Sidebar.LinkToList.show({
                    "lat": data.latitude,
                    "lon": data.longitude,
                    "radius": data.radius
                });

                _showResults();

                return (Common.addPointsToMap());
            }
        } else {
            if (data.error && data.error.errorMsg) {
                _c.warn("Error in data: "+ data.error.errorMsg);
            } else {
                _c.warn("No Yellow Page results in data.");
            }
            _showResults({"noResults": true});
            return false;
        }
    };
    
    
    /**
     * @public performSearch
     *
     * @param {Object} param
     */
    self.performSearch = function (param) {
        if (!self.isMapPanned || !_markerLayer.getVisibility()) {
            _c.log("Rangifer:: performSearch() interrupted :: either map is not panned or markerLayer is hidden.");
            return false;
        }

        _c.info("performSearch()");
        
        LoadingIndicator.append("map-loading-indicator");
        
        var UrlObject = Common.getCurrentUIState();

        self.Sidebar.update(false);

        $('#mapsearch_sidebar').addClass("loading");

        var searchURL = _prefs.searchURL +"?"+ Common.getCurrentUIState().queryString;
        _c.log(searchURL);

        Common.clearPreviousResults();
        
        $.getJSON(searchURL, function (data) {
            self.isMapPanned = false;
            mapSearch.appendData(data);
            LoadingIndicator.remove();
        });

    };
    
    /**
     * @public Prefs
     * Sets preferences for the
     * mapSearch object
     *
     * @param {Object} oPrefs
     */
    this.setPrefs = function (oPrefs) {
        var xI;

        for (xI in oPrefs) {
            //if (oPrefs.hasOwnProperty(xI)) {
            _prefs[xI] = oPrefs[xI];
            //}
        }
    };
    
    this.getPrefs = function () {
        return _prefs;
    };
    
    /**
     * @public getMapObj
     * Returns the EMAPI map instance
     *
     */
    this.getMapObj = function () {
        return _mapObj;
    };
    
    this.getLayers = function () {
        return {
            "markerLayer" : _markerLayer,
            "routingLayer" : _routingLayer
        };
    };
    
    /******************
     * Initialization *
     *****************/

    self.init = function (param) {
        this.setPrefs(param.prefs);

        $mapObj = $("#"+ _prefs.mapElem);

        if (!$mapObj.length) {
            _c.warn("Element #"+ _prefs.mapElem +" not found.");
            return false;
        }

        _mapObj = new EM.Map(_prefs.mapElem);
        _mapObj.baseLayer.attribution = _prefs.copyrightText;
        _mapObj.baseLayer.maxScale = 17;

        $("#header-search-what").focus();

        _initMapView();
    };
    
    return self;
};

/**
 *
 * Creates a new mapSearch instance for Alces
 *
 * @constructor Alces.mapSearch
 * @return {Object} mapSearch
 *
 */
Alces.mapSearch = (window.Rangifer && Rangifer.Core) ? false : function ($) {
    Alces.Core.Component.call(this);

    // Private Variables
    var self = this,
        $mapObj,
        _mapObj,
        _markerLayer = new EM.Layer.Vector.Listing("Markers", {
            'visibility': true,
            'clustered':false,
            'tooltip':false,//true,
            'isBaseLayer': false,
            'displayInLayerSwitcher': false
        }),
        _routingLayer = new OpenLayers.Layer.Vector('Routes'),
        _prefs = {
            "searchURL": Settings.api_path +"/yellowpagemap",
            "mapElem" : "",
            "sidebarResultTemplate" : ""
        };

    // Private Functions
    var _initMapView,
        _showResults;

    _routingLayer.setVisibility(false);

    if (typeof VenueMarkers !== "undefined") {
        self.VenueMarkers = new VenueMarkers($);
    }

    if (typeof Sidebar !== "undefined") {
        self.Sidebar = new Sidebar($);
    }

    if (typeof Routing !== "undefined") {
        self.Routing = new Routing($);
    }
    
    /**
     * 
     * @private _showResults
     * @param {Object} param
     * 
     */
    _showResults = function (param) {
        _c.log("Alces :: _showResults()");
        var $results = $("#mapsearch_results");
        var startIndex = 1;
        
        //Tabs.activateLocTab("#mapsearch_sidebar");
        $results.fadeTo(1.0, FADETIME);

        if (param) {
            startIndex = 0;
            
            if (param.noResults) {
                $('#mapsearch_sidebar').hide().removeClass("loading");

                return false;
            } else if (param.error) {
                if (param.error === 500) {
                    $results.html("<span id='no_results'>Palvelussa on tilapäinen häiriö. Yritä myöhemmin uudelleen.</span>");
                }
            } else {
                _c.warn("Parameters were provided to showResults(), but they were handled incorrectly.");
            }
        } else {
            $('#mapsearch_sidebar').addClass("shown");
        }
        

        $results.removeClass("loading");
        $results.stop().fadeTo(FADETIMESLOWER, 1.0);

        // Passes the company ids of first page results to Site Catalyst
        var companiesShown = $(".result_page").eq(0).find(".vcard");
        var iI=0,iL=companiesShown.length;
        var idsForSiteCatalyst = [];

        for (;iI<iL;iI++) {
            idsForSiteCatalyst.push(companiesShown.eq(iI).attr("id").substr(3));
        }
        SiteCatalystEvents.sendMapShowResults(idsForSiteCatalyst, jQuery("#header-search-what").val(), MapLayers.getVisibleLayer(), jQuery("#header-search-where").val(), isLoggedIn, startIndex);

    };

    /**
     * @private _initMapView
     *
     * Initializes the default view of Alces map
     *
     */
    _initMapView = function () {
        _c.info("Alces.mapSearch.initMapView()");
        
        // If we enter through route then default value of where shouldn't be populated
        if (QueryParams.routeFrom && QueryParams.routeTo) {
            $("#header-search-where").val("");
        }
        
        var _groupId = Common.getCurrentUIState().groupId,
            _where = ($("#header-search-where").val()) ? encodeURIComponent($("#header-search-where").val()) : "",
            _whatField = $("#header-search-what").val() ? encodeURIComponent($("#header-search-what").val()) : "",
            // verification that what-value is not "hakusanat" - a hack to fix IE7/IE8 problems with placeholder text.
            _what = _whatField !== $("#header-search-what").attr('placeholder') ? _whatField : "",
            _isCategorySearch = QueryParams.isCategorySearch, 
            _searchURL = _prefs.searchURL +"?where="+ _where +"&what="+ _what;
             
        if (!!_groupId) {
            _searchURL += "&groupId=" + _groupId;
        }

        if (!QueryParams.printLayout) {
            Common.fixHeight();
            MapPopup.init();        
            Toolbar.init();
        } else {
            $("#btn-print-map").on("click", function(event) {
                _c.log('click');
                window.print();                
            });
        }

        _mapObj.addLayers([_markerLayer,_routingLayer]);

        if (!!QueryParams.id) {
            _searchURL += "&id="+ QueryParams.id;
        } else if (!!QueryParams.latitude && !!QueryParams.longitude) {
            var lat = QueryParams.latitude || _prefs.geoLat;
            var lon = QueryParams.longitude || _prefs.geoLon;

            _searchURL += "&latitude="+ lat +"&longitude="+ lon;
            if (!!QueryParams.radius) {
                _searchURL += "&radius="+ QueryParams.radius;
            }
        }

        if (!!QueryParams.filter) {
            _searchURL += "&filter="+ decodeURIComponent(QueryParams.filter);
        }

        if (_isCategorySearch === "true") {
            _searchURL += "&isCategorySearch=true";
        }

        if (!QueryParams.printLayout) {
            $('#mapsearch_sidebar').addClass("loading");
            self.Sidebar.handleResultsHeight();
            $("#mapsearch_results").css("left", "0px");
            Common.addPOILayers();
        }

        if (QueryParams.routeFrom && QueryParams.routeTo) {
            // routeSearch defined in URL
            mapSearch.addEventListeners();
        } else {
            Common.executeJSONSearch(_searchURL);
        }
        

        /**
         * Init BLOM Layer
         */
        if (window.initBLOM && !QueryParams.printLayout) {
            initBLOM(_mapObj, "mapDiv", null, null);

            if (birdsEye) {
                var myUrbexMap = birdsEye.getUrbexMap();
                if (myUrbexMap) {
                    myUrbexMap.addLayer(_markerLayer);
                }
            }
        }
    };

    /**
     * @public addEventListeners
     *
     */
    self.addEventListeners = function (excludeSearch) {
        _c.log("Alces mapSearch.addEventListeners()");
        // Add listeners to Alces map, tabs, nautical-disclaimer and route search submit button.
        
        Common.addEventListeners(excludeSearch);

        $("#tabs .tab-header").live("click", Tabs.toggle);
        
        $("#nautical-disclaimer-close").live("click", function (evt) {
            if(evt.preventDefault) {
                evt.preventDefault();
            } else {
                evt.returnValue = false;
            }
            window.name += "nauticalDisclaimerClosed";
            $("#nautical-disclaimer").fadeOut(320);
        });
        
        if (QueryParams.routeFrom || QueryParams.routeTo) {
            $("#tab-route .tab-header").trigger("click");
            if (QueryParams.routeFrom && QueryParams.routeTo) {
                // create and populate waypoint fields
                $("#route-submit").trigger("click");
            }
        }
        
        // Prevent map movement by keyboard when focus on input fields. 
        $("#form-map-search").on("keydown", ".input-box input",  function(event) {
            if (event.which && event.which >= 37 && event.which <= 40) {
                // arrow-key pressed
                event.stopPropagation();
            }
        });        
        
    };
    
    self.addSimilarLocations = function () {
        var $container = $("#didYouMean"),
        realBasePath = Settings.base_path.replace("/eniro", ""),
        address,
        street,
        code,
        office,
        clickURL,
        mapPointData = [],
        $list = $("<ol></ol>"),
        i = 0,
        l = similarLocations.length;
        
        for (;i<l;i++) {
            address = similarLocations[i].address.split(", ");
            street = address[0] || "";
            code = (address[2]) ? ", "+ address[2] : "";
            office = (address[1]) ? ", "+ address[1] : "";
            clickURL = realBasePath +"/kartta/"+ encodeURIComponent(street+office);
            $list.append("<li><a href='"+ clickURL +"'>"+ street + office  +"</a></li>");
            mapPointData.push({
                "lat": similarLocations[i].lat,
                "lon": similarLocations[i].lon,
                "title": street,
                "address": code +" "+ office,
                "onclickURL": clickURL
            });
        }
        
        SiteCatalystEvents.sendMapAddSimilarLocations(similarLocations.length, MapLayers.getVisibleLayer(), $("#header-search-where").val(), isLoggedIn);
        
        $container.append($list);
        Tabs.activateLocTab("#didYouMean");
        
        Common.addPointsToMap(mapPointData);
        _mapObj.zoomToExtent(_markerLayer.getDataExtent());
    };
    
    self.addVenuesNearby = function (data) {
        _c.log("mapSearch.addVenuesNearby()");
        _c.log(data);
        
        var _data = data.yellowPageInfo || [],
        idsForSiteCatalyst = [],
        i = 0;
        l = Math.min(_data.length, 5);
        venues = "";
        
        if (!_data.length) {
            Alerts.showNoResults();
        }
        
        for (;i<l;i++) {
            if (_data[i].title.length) {
                idsForSiteCatalyst.push(_data[i].id);
                venues += "<li><a href='"+ _data[i].companyUrl +"'>"+ _data[i].title +"</a></li>";
            }
        }
        
        jQuery("#singleVenue-listing").append(venues);
        _c.dir(idsForSiteCatalyst);
        
        SiteCatalystEvents.pushEvents(idsForSiteCatalyst);
        SiteCatalystEvents.setProp({
            "channel" : "ms",
            "pageName" : "ms_location",
            "prop2" : "lo",
            "prop3" : "1",
            "prop13" : $("#header-search-where").val(),
            "prop25" : "ms_location",
            "prop30" : isLoggedIn,
            "events" : "event1,event4,event30"
        });
        SiteCatalystEvents.dispatch(1);

    };

    self.appendData = function (data) {
        // - Tell siteCatalyst "what" (user searched for) & query expansion 
        // - If yellowpage-data, populate it on the map (sidebar + point markers on map). 
        // If no YP data Log warning and return false and call _showResults with error-data.
        _c.info("Alces :: mapSearch.appendData()");

        var _queryExpansionsUsed = (data.queryExpansions && data.queryExpansions["any.word.partial.match.search.message"]) ? "yes" : "no";
        SiteCatalystEvents.setProp("prop1", $("#header-search-what").val());
        SiteCatalystEvents.setProp("prop17", _queryExpansionsUsed);

        var results = data.yellowPageInfo || [];
        g_results = results;

        if (results.length) {
            if (self.Sidebar.populate(results)) {
                self.Sidebar.LinkToList.show({
                    "lat": data.latitude,
                    "lon": data.longitude,
                    "radius": data.radius
                });
                _showResults();

                return (Common.addPointsToMap());
            }
        } else {
            if (data.error && data.error.errorMsg) {
                _c.warn("Error in data: "+ data.error.errorMsg);
            } else {
                _c.warn("No Yellow Page results in data.");
            }
            _showResults({
                "noResults": true,
                "what": data.what,
                "where": data.where
            });
            return false;
        }
    };


    /**
     * @public performSearch
     *
     * @param {Object} param
     */
    self.performSearch = function (param) {
        if (!self.isMapPanned || !_markerLayer.getVisibility() || !Common.getCurrentUIState().what) {
            _c.log("Alces:: performSearch() interrupted :: map is not panned, markerLayer is hidden or what value is missing.");

            return false;
        }

        _c.info("performSearch()");
        
        LoadingIndicator.append("map-loading-indicator");

        var UrlObject = Common.getCurrentUIState();

        self.Sidebar.update(false);

        $('#mapsearch_sidebar').addClass("loading");

        //var searchURL = _prefs.searchURL +"?latitude="+ UrlObject.latitude +"&longitude="+ UrlObject.longitude +"&radius="+ UrlObject.radius +"&what="+ UrlObject.what;
        var searchURL = _prefs.searchURL +"?"+ Common.getCurrentUIState().queryString;
        _c.log(searchURL);

        Common.clearPreviousResults();
        
        $.getJSON(searchURL, function (data) {
            self.isMapPanned = false;
            mapSearch.appendData(data);
            LoadingIndicator.remove();
        });
    };

    /**
     * @public Prefs
     * Sets preferences for the
     * mapSearch object
     *
     * @param {Object} oPrefs
     */
    self.setPrefs = function (oPrefs) {
        var xI;

        for (xI in oPrefs) {
            //if (oPrefs.hasOwnProperty(xI)) {
            _prefs[xI] = oPrefs[xI];
            //}
        }
    };
    
    self.getPrefs = function () {
        return _prefs;
    };
    /**
     * @public getMapObj
     * Returns the EMAPI map instance
     *
     */
    self.getMapObj = function () {
        return _mapObj;
    };
    
    self.getLayers = function () {
        return {
            "markerLayer" : _markerLayer,
            "routingLayer" : _routingLayer
        };
    };
    
    /*************************
     * Alces: Initialization *
     *************************/
    // add browser detection & ie/browser-classes to html
    self.init = function (param) {
        self.setPrefs(param.prefs);

        $mapObj = $("#"+ _prefs.mapElem);

        if (!$mapObj.length) {
            _c.warn("Element #"+ _prefs.mapElem +" not found.");
            return false;
        }

        _mapObj = new EM.Map(_prefs.mapElem,  {
            // Controls come through EMAPI.
            /*controls: [
             new OpenLayers.Control.Navigation({
                 mouseWheelOptions: {interval: 100}
             })
             //new OpenLayers.Control.PanZoom(),
             //new OpenLayers.Control.LayerSwitcher({'ascending':false}),
             //new OpenLayers.Control.Permalink(),
             //new OpenLayers.Control.ScaleLine(),
             //new OpenLayers.Control.Permalink('permalink'),
             //new OpenLayers.Control.MousePosition(),
             //new OpenLayers.Control.OverviewMap(),
             //new OpenLayers.Control.KeyboardDefaults()
             ]*/
        });
        _mapObj.baseLayer.attribution = _prefs.copyrightText;
        _mapObj.baseLayer.maxScale = 17;
    
        _initMapView();
        _mapObj.addControl(new OpenLayers.Control.KeyboardDefaults());
    };

    return self;
};


/**
 * @constructor Common
 *
 * @return {Object} Common
 */
var Common = ( function () {
    var self = {},
    $ = jQuery,
    $mapObj = $("#"+ mapPreferences.mapElem),
    
    _contextualMenuItems,
    _getRadius,
    _zoomingAllowed = true,
    _showContextualMenu,
    _toPerformSearch = true,
    
    $mapContextualMenu; 

    // Right-click menu for route-search 
    _routeContextualMenuItems = {
        changeFrom: {
            label: "Vaihda reitin alku",
            id: "contextmenu-change-from",
            action: function (evt) {            // change address from first route-field
                 evt.preventDefault();
                 var data = evt.data;
                 var reverseGeo = Common.Geo.getAddressFromLonLat(data.lon, data.lat, function (address, lon, lat, municipality) {
                     _c.log(address);
                     if (typeof address !== undefined) {     // change address to first input field & submit search.
                         mapSearch.Routing.pointHandler.updateRoutePoint("A", address, data.lon, data.lat, municipality.municipality)                             
                     } else {       // no results for coordinate.
                         mapSearch.Routing.pointHandler.addUIRoutePoint("", data.lon, data.lat, 0);
                     }
                     mapSearch.Routing.submitSearch();
                     return false;
                 });
                _c.log('Contextmenu-change-from click' + evt.which);
            }
        },
        addWaypoint: {      // add a new route-field via-point
            label: "Lisää kautta-piste",
            id: "contextmenu-add-waypoint",
            action: function (evt) {
                evt.preventDefault();
                 var data = evt.data; 
                 var reverseGeo = Common.Geo.getAddressFromLonLat(data.lon, data.lat, function (address, lon, lat, municipality) {
                     _c.log('************ addWaypoint: ');
                     _c.log(municipality);
                     mapSearch.Routing.pointHandler.addRoutePoint(address, data.lon, data.lat, ( municipality && municipality.municipality ? municipality.municipality : undefined ));
                });
                return false;
            }
        },
        changeTo: {
            label: "Vaihda reitin päätepiste",
            id: "contextmenu-change-to",
            action: function (evt) {
                _c.log("Vaihda reitä päätepiste");
                // change address from last route-field
                 evt.preventDefault();
                 _c.log('which');                    
                 var data = evt.data,
                    $endPoint = $("#tab-route-content p").not("#route-waypoint-template").find("input.route-point").last(),
                    endPointName = ($endPoint.closest("p").attr("id")).replace("route-", "");

                 _c.log('change end point ' + endPointName + ' ');
                 _c.log(data.lon + ", " + data.lat);

                 var address = Common.Geo.getAddressFromLonLat(data.lon, data.lat, function (address, lon, lat, municipality) {
                     _c.log(address);
                     mapSearch.Routing.pointHandler.updateRoutePoint(endPointName, address, data.lon, data.lat, municipality.municipality);
                     mapSearch.Routing.submitSearch();
                 });
                 return false;
            }
        }
    }

    _getRadius = function () {
        var _bounds = mapSearch.getMapObj().calculateBounds(),
            _radius = 0;            // Radius in kilometers.
        
        if (_bounds) {              
            // bounds are in meters. So the 3000 means radius is about 0.33 of screen, making coverage about 67 %.
            var _width = parseInt((_bounds.right - _bounds.left) / 3000, 10);
            var _height = parseInt((_bounds.top - _bounds.bottom) / 3000, 10);

            _radius = Math.min(_width, _height);
            _radius = Math.max(_radius, 1);
        }
        
        return _radius;
    };
    
    _showRouteContextualMenu = function(evt) {
        var _lonlat = Common.Geo.getLonLatFromPixel(evt.pageX, evt.pageY);
        _c.log('right click '+ evt.pageX +', ' + evt.pageY + ' ' + _lonlat.lon + ' ' + _lonlat.lat);
        
        if (evt.preventDefault) {
            evt.preventDefault();
        } else {
            evt.returnValue = false;   
        }
        
        if (($mapContextualMenu !== undefined) && ($mapContextualMenu.length > 0)) {
            $mapContextualMenu.css({"left":evt.pageX, "top":evt.pageY}).empty();
            
            $.each(_routeContextualMenuItems, function(index, menuItem) {
                var $item = $("<li><a id='" + menuItem.id + "' href=''>" + menuItem.label + "</a></li>");
                
                $item.bind("click", {lon: _lonlat.lon, lat: _lonlat.lat}, menuItem.action);
                    
                $mapContextualMenu.append($item);    
            });
            
            $mapContextualMenu.show();
        } else {    // init popup
            var $menuHtml = $("<ul id='mapContextualMenu' style='left:" + evt.pageX + "px; top:" + evt.pageY + "px;'></ul>");

            // create contextul menu html & bind events based on _contextualMenuItems
            $.each(_routeContextualMenuItems, function(index, menuItem) {
                var $item = $("<li><a id='" + menuItem.id + "'>" + menuItem.label + "</a></li>");
                
                $item.bind("click", {lon: _lonlat.lon, lat: _lonlat.lat}, menuItem.action);
                    
                $menuHtml.append($item);    
            });
            $menuHtml.appendTo("body");
            $mapContextualMenu = $("#mapContextualMenu");
        }        
    }
 

    self.radius = function () {
        return _getRadius();
    }


    /**
     * @public addEventListeners
     *
     */
    self.addEventListeners = function (excludeSearch) {
        _c.log('Common.addEventListeners');
        var _mapObj = mapSearch.getMapObj();

        if (!excludeSearch) {
            /**
             * moveend is fired on panning, zooming and resizing
             */
            _mapObj.events.register("moveend", _mapObj, function () {
                if (_toPerformSearch) {
                    window.clearTimeout(_toPerformSearch);
                    mapSearch.isMapPanned = true;
                    _toPerformSearch = false;
                }

                // search performed 1.25 seconds after user stopped moving/zooming map. 
                _toPerformSearch = window.setTimeout( function () {
                    jQuery("#header-search-where").val("");
                    mapSearch.isMapPanned = true;
                    mapSearch.performSearch();
                }, 1250); 
            });
        }

        _mapObj.events.register("movestart", _mapObj, function () {
            _zoomingAllowed = false;
        });

        _mapObj.events.register("mouseup", function(evt) {
           _c.log('_mapObj.click');
           _c.log(evt);
        });

        _mapObj.events.register("moveend", _mapObj, function () {
            window.setTimeout( function () {
                _zoomingAllowed = true;
            },250);
        });

        // LMB click for zooming
        OpenLayers.Control.Click = OpenLayers.Class(OpenLayers.Control, {                
            defaultHandlerOptions: {
                'single': true,
                'double': false,
                'pixelTolerance': 0,
                'stopSingle': false,
                'stopDouble': false
            },
 
            initialize: function (options) {
                this.handlerOptions = OpenLayers.Util.extend({}, this.defaultHandlerOptions);
                OpenLayers.Control.prototype.initialize.apply(this, arguments); 
                this.handler = new OpenLayers.Handler.Click(
                    this, {'click': this.trigger},
                    this.handlerOptions);
            }, 
 
            trigger: function (evt) {
                // One-click zoom feature.
                var _mapObj = mapSearch.getMapObj();
                var lonlat = _mapObj.getLonLatFromViewPortPx(evt.xy);
                var activeToolTab = $("#measuringTools .active");

                if (ottoLayer && ottoLayer.getVisibility()) {
                    Common.getPOIDataFromPoint( evt.xy.x, evt.xy.y, 'fonecta:POI_ATM_Otto', 'Common.drawOttoPopup');
                }
                 
                if (guestharboursLayer && guestharboursLayer.getVisibility()) {
                    Common.getPOIDataFromPoint( evt.xy.x, evt.xy.y, 'fonecta:Guest_Harbours', 'Common.drawGuestHarboursPopup');
                }
                
                if (!!cancelMapZoom || !lonlat) {
                    cancelMapZoom = false;                    
                } else {
                    //_mapObj.setCenter(new OpenLayers.LonLat(lonlat.lon, lonlat.lat), parseInt(_mapObj.getZoom() +1, 10));
                }                
            }
 
        });
        
        var click = new OpenLayers.Control.Click();
        _mapObj.addControl(click);
        click.activate();
    
        $("#mapContextualMenu").live({
            mouseenter: function() {
                // reset / destroy timer if needed 
            },
            mouseleave: function() {
                $(this).fadeOut();
            }
        });
                    

        $mapObj.bind("contextmenu.zoom", function (evt) {
            _c.log('contextmenu.zoom');
            if (Rangifer.Core) {
                if (_zoomingAllowed) {
                    evt.preventDefault();
                    $("#header-search-where").val("");
                    mapSearch.getMapObj().zoomOut();
                }
            } else {
                //if (OpenLayers.Event.isRightClick(evt)){
                // commented right click menu functionality. Not ready -> not used in current release.
                
                if (Tabs.isRouteTabShown()) {
                    _showRouteContextualMenu(evt); 
                    
                } else if (_zoomingAllowed) {
                    $("#header-search-where").val("");
                    mapSearch.getMapObj().zoomOut();
                } 
                evt.preventDefault();
                evt.stopPropagation();
                return false;
            }
        });

        $(window).bind("resize", function () {
            Common.fixHeight();
            mapSearch.Sidebar.handleResultsHeight();
            $("#mapsearch_results").css("left", "0px");
        });

        // Enables printing of the route
        $("#route-print").live("click", function (evt) {
            evt.preventDefault();
            window.open(Common.getCurrentUIState().fullUrl +"&printLayout=true");
        });
        
        $("#map-print").live("click", function (evt) {
            evt.preventDefault();
            window.open(Common.getCurrentUIState().fullUrl +"&printLayout=true");
        });


        /**
         * Appends UI State variables to location search
         * when map has been panned (where field = empty)
         * and new search is made
         */
        $("#form-map-search").live("submit", function(evt) {
            var currentState = Common.getCurrentUIState(),
                neededValues = ["latitude", "longitude", "radius", "zoomLevel"],
                hiddenInputs = "",
                i = 0, l = neededValues.length;
            
            if (!currentState.where) {
                for (;i<l;i++) {
                    
                    hiddenInputs += "<input type='hidden' name='"+ neededValues[i] +"' value='"+ currentState[neededValues[i]] +"' />";
                }
                
                $(this).append(hiddenInputs);
            }
        });
        
    };


    /**
     * @public addPointsToMap
     * Populates the points on map object using the data array
     *
     */
    self.addPointsToMap = function (data) {
        _c.info("Common.addPointsToMap()");

        var mapPoints = data || mapSearch.Sidebar.getMapPoints();
        var resultsPerView = mapSearch.Sidebar.Pagination.getResultsPerView();
        var features = [], feature, point, lat, lon, address, venueTitle, websiteUrl, marker, companyUrl, tacticalAdLabel;
        var iI = 0,
        iL = mapPoints.length,
        adCount,
        index = 0;
        //index = (mapSearch.Sidebar.Pagination.getCurrentPage()-1) * mapSearch.Sidebar.Pagination.getResultsPerView()
        
        g_addPointsData = mapPoints;
        
        for (;iI<iL;iI++) {
            mapPoints[iI].title += "";            

            var singleMarker = Marker.create({
                "type": "default",
                "size": "normal",
                "index": index+iI + "",
                "label": (index+iI+1) + ""
            });

            var smallMarker = Marker.create({
                "type" : "default",
                "size" : "small",
                "index": index+iI + "",
                "label": ""
            });
            
            if (Rangifer.Core && mapPoints[iI].relatedTacticalAds) {
                 adCount = mapPoints[iI].relatedTacticalAds.length;
            } else {
                adCount = mapPoints[iI].dealCount;
            }

            if (adCount > 0) {
                //_c.log("creating tactical ad marker for : "+ iI);
                tacticalAdLabel = (iI < resultsPerView) ? (index+iI+1) + "" : ""; 
                marker = Marker.create({
                    "type" : "tactical",
                    "size" : "normal",
                    "index": index+iI,
                    "label": tacticalAdLabel + ""
                });
            }
            // This is the case when data is provided as a parameter (eg. similar locations)
            else if (data !== undefined) {
                marker = singleMarker;    
            } 
            else {
                marker = (iI < resultsPerView) ? singleMarker : smallMarker;
            }

            lat = mapPoints[iI].lat;
            lon = mapPoints[iI].lon;
            address = mapPoints[iI].address;
            venueTitle = mapPoints[iI].title + "";
            websiteUrl = mapPoints[iI].websiteUrl || "";
            companyUrl = mapPoints[iI].companyUrl || "";

            if (lat && lon) {
                point = new EM.LonLat(lon, lat);

                feature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(point.lon, point.lat),
                    {
                        tooltip: {},
                        onclickURL : mapPoints[iI].onclickURL || null
                    }, marker
                );

                features.push(feature);
            } else {
                // we generate a hidden marker when lat+lon is invalid
                singleMarker.display = "none";

                point = new EM.LonLat(Common.Geo.getLon(), Common.Geo.getLat());

                feature = new OpenLayers.Feature.Vector(
                    new OpenLayers.Geometry.Point(point.lon, point.lat),
                    {},singleMarker
                );

                features.push(feature);
            }
            
        }

        // Pushes points to layer and zooms the map to display
        // all the markers
        if (features.length) {
            mapSearch.getLayers().markerLayer.addFeatures(features);
        } else {
            _c.warn("No map points to add, check that data contains lat + lon values.");
        }

        return (!!features.length);
    };
    
    /**
     * @public addPOILayers
     * Inserts all the POI Layers into map
     */
    self.addPOILayers = function () {
        _c.info("Common.addPOILayers()");

        ottoLayer = new OpenLayers.Layer.WMS("Otto.-automaatit", EM.geoserverUrl+"/gwc/service/wms", {
            srs: 'EPSG:4326',
            layers: 'fonecta:POI_ATM_Otto_cached',
            tiled: 'true', 
            transparent: "true",
            format: 'image/png'
        }, {
            buffer: 0,
            displayOutsideMaxExtent: true, 
            alpha: true,
            isBaseLayer: false,
            displayInLayerSwitcher: true,
            icon: "otto",
            visibility: false
        });
        
        guestharboursLayer = new OpenLayers.Layer.WMS("Vierassatamat", EM.geoserverUrl+"/gwc/service/wms", {
            srs: 'EPSG:4326',
            layers: 'fonecta:Guest_Harbours_cached',
            tiled: 'true', 
            transparent: "true",
            format: 'image/png'
        }, {
            buffer: 0,
            displayOutsideMaxExtent: true, 
            alpha: true,
            isBaseLayer: false,
            displayInLayerSwitcher: true,
            icon: "guest_harbour",
            visibility: false
        });
        
        weatherLayer = new OpenLayers.Layer.WMS("Säätiedot", EM.geoserverUrl+"/gwc/service/wms", {
            srs: 'EPSG:4326',
            layers: 'fonecta:geo_poi_fonecta_weather_actual_cached',
            tiled: 'true', 
            transparent: "true",
            format: 'image/png'
        },{
            buffer: 0,
            displayOutsideMaxExtent: true, 
            alpha: true,
            isBaseLayer: false,
            displayInLayerSwitcher: true,
            icon: "weather",
            visibility: false
        });
        
        // When otto-layer hidden, clear away all POI-popups
        ottoLayer.events.register("visibilitychanged", null, function(evt) {
            if (ottoLayer.getVisibility() !== true) {
                poiPopup.clearPopups();
            }
        });

        // when harbourlayer hidden, clear away all POI-popups 
       guestharboursLayer.events.register("visibilitychanged", null, function(evt) {
            if (guestharboursLayer.getVisibility() !== true) {
                poiPopup.clearPopups();
            } 
        });         

        var _mapObj = mapSearch.getMapObj();
        var _toolbarLayerGroup = new EM.LayerGroup('GroupLeft', {
            layers: [guestharboursLayer, ottoLayer, weatherLayer],
            displayInOverlaySwitcherBar: true,
            positionInOverlaySwitcher: "left"
        });
        
        _mapObj.addLayerGroup(_toolbarLayerGroup);

        _mapObj.teaserLayer = [{
            "layer" : EM.Layer.NAUTICAL,
            "zoomLevels" : [10,11,12,13,14,15,16,17], 
            "showFeatures" : guestharboursLayer
        }];
        
        _mapObj.addLayers([guestharboursLayer, ottoLayer, weatherLayer]);
    };
    
    /**
     * @public addSingleVenue
     * Shows only a single venue / location on the map
     *
     * @return (OpenLayers.Feature.Vector) venue 
     * (Doesn't seem to return anything 26.9.2011 / MM)
     */
    self.addSingleVenue = function (single) {
        _c.log('Common.addSingleVenue ' + single.title);
        _c.log(single);

        Tabs.activateLocTab("#singleVenue");

        var locationMarker = Marker.create({
            "type": "location",
            "size": "medium"
        });

        var point = new EM.LonLat(single.longitude, single.latitude),
            address = single.streetAddress || "";

        $("#singleVenue-address").append("<span>"+ single.title + "</span>");

        if (single.title.length) {
                single.title += "<br /><br />";
        } else {
            var whereValue = jQuery("#header-search-where").val().split(",")[0];
            address = whereValue.substring(0,1).toUpperCase() + whereValue.substr(1);
        }

        if (!QueryParams.id && !single.noResults) {
            var venue = new OpenLayers.Feature.Vector(
            new OpenLayers.Geometry.Point(point.lon, point.lat), {
                'tooltip': {
                    "header": single.title + address
                },
                onclickURL : null
            }, locationMarker);

            var features = [];
            features.push(venue);

            mapSearch.getLayers().markerLayer.addFeatures(features);
        }
        
        if (single.noResults) {
            var UrlObject = Common.getCurrentUIState();
            
            if (UrlObject.what) {   // When search is made with what (and where) field and no results found
                Alerts.showNoResults();
            } else {                // when search is made only with where field
                Alerts.showPlaceNotFound();
            }
        }
    };
    

    /**
     * @public clearPreviousResults
     *
     * Clears previous search results when new search is made
     *
     */
    self.clearPreviousResults = function () {
        _c.info("clearPreviousResults");
        var $results = $("#mapsearch_results");

        /**
         * not working with 100% proof
         * if clearing is done in fadeTo callback
         */
        $results.html("");

        $results.fadeTo(FADETIMEFAST,0.0);

        mapSearch.getLayers().markerLayer.removeAllFeatures();
    };
    
    /*
     * Center map. 
     * If Lon/Lat in URL then use those for centering. If YP data given to function, use the first YP result. 
     * Otherwise center so that all marker layer markers are visible.
     */
    self.centerMap = function(YPData) {
        var _map = mapSearch.getMapObj(),
            UrlObject = Common.getCurrentUIState();
            
        if (UrlObject.latitude && UrlObject.longitude) {        
            // If no YP-data, use URL coordinates to center map.
            _map.setCenter(new EM.LonLat(UrlObject.longitude, UrlObject.latitude), UrlObject.zoomLevel, false);
        } else if (YPData && YPData.length === 1) {
            // If 1 YP result, center on that
            _map.setCenter(new EM.LonLat(YPData[0].longitude, YPData[0].latitude), UrlObject.zoomLevel, false, false);
        } else {                    
            // zoom to a level where the map accommodates all markers.
            _map.zoomToExtent(mapSearch.getLayers().markerLayer.getDataExtent());
        }       
    }


    /**
     * @public executeJSONSearch
     *
     * @param {String} searchURL
     *
     */
    self.executeJSONSearch = function (searchURL) {
        var $XHR; 
        
        _c.log("XHR to: "+ searchURL);
        $XHR = $.getJSON(searchURL, function (data) {
            var YPData = data.yellowPageInfo || false,
                UrlObject = Common.getCurrentUIState(),
                _map = mapSearch.getMapObj(),
                single,
                eventListenersExcludeSearch = false,
                hasWhere            = $.trim(UrlObject.where).length > 0 ? true : false, 
                isLocationSearch    = !(UrlObject.routeFrom && UrlObject.routeTo) && hasWhere ? true : false,
                isRouteSearch       = !!mapSearch.Routing.routeFromValue() && !!mapSearch.Routing.routeToValue() ? true : false,
                isYPSearch          = (!QueryParams.id && UrlObject.what.length) ? true : false,
                isPrintLayout       = !!UrlObject.printLayout ? true : false,
                isEmptyMap          = !isRouteSearch && !isLocationSearch && !isYPSearch ? true : false;
                hasLonLatInUrl      = (!!UrlObject.longitude && !!UrlObject.latitude && !!UrlObject.zoomLevel) ? true : false,
                hasMultipleLocations = (window.similarLocations && similarLocations.length > 1 && data.locationFound) ? true : false,
                hasYPResults        = (!!YPData && !!YPData.length && YPData.length > 0) ? true : false,
                screenLon           = UrlObject.longitude || false,
                screenLat           = UrlObject.latitude || false,
                screenZoomLevel     = UrlObject.zoomLevel || 17,   
                whereValue          = Common.getCurrentUIState().where;

                    
            _c.log('* isPrintLayout: ' + isPrintLayout);            
            _c.log('* isRouteSearch: ' + isRouteSearch);
            _c.log('* isLocationSearch: ' + isLocationSearch);
            _c.log('* isYPSearch:' + isYPSearch);
            _c.log('* isEmptyMap: ' + isEmptyMap);
            _c.log('* hasYPResults: ' + hasYPResults);
            _c.log('* hasLonLatInUrl: ' + hasLonLatInUrl);
            _c.log('* hasMultipleLocations: ' + hasMultipleLocations);
            _c.log('routeTo: ' + mapSearch.Routing.routeToValue());
            
            // Hides venue details in Eniro if we receive empty dataset
             
            if (YPData && (YPData.length === 0) || (!data.locationFound)) {
                $("#singleVenue-disclaimer").hide();       // #singleVenue contains "alueen yrityksiä". If no YP results or location is not found, hide it.
            }

            if (isPrintLayout && isLocationSearch) {
               $("#map-container").addClass('location-search');
            }
            
            if (isYPSearch && hasYPResults) {   // YPSearch, eg. when searched with company name "capris pizza-kebab" or location & term ("helsinginkatu", "pizza")
                mapSearch.appendData(data);
                Common.centerMap(YPData);
            } else if (QueryParams.id) {        // Indivudual venues (eg. companies), for example from Fonecta single company info to "show on map".
                $('#mapsearch_sidebar').removeClass("loading");
                mapSearch.appendData(data);

                if (hasYPResults) {
                    single = YPData[0];                    
                } else {
                    return false;
                }

                Common.addSingleVenue(single);
                mapSearch.getMapObj().setCenter(new EM.LonLat(screenLon || single.longitude, screenLat || single.latitude), screenZoomLevel, false);
                
            } else {
                $('#mapsearch_sidebar').removeClass("loading");

                if (YPData && YPData.length) {
                    // TODO: This case is wrong - it uses YP data and url lon/lat to place the map on the right location, but messes up the address handling 
                    _c.log('* USE YP DATA FOR LOCATION');
                    single = jQuery.extend(true, {}, YPData[0]);
                    single.title = single.title ? single.title : "";
                    
                    _c.log(single);
                } else {
                    _c.warn("Can't receive yellowPageInfo - check server status.");
                }
                
                if (hasMultipleLocations) {                   // multiple results - show "did you mean"
                    mapSearch.addSimilarLocations();
                } else if (window.similarLocations && similarLocations.length === 1) {
                    // 1 result, similarLocation[0] == searchResult 
                    single = {
                        "longitude" : similarLocations[0].lon,
                        "latitude" : similarLocations[0].lat,
                        "title" : similarLocations[0].address
                    };

                    if (Alces.Core) {
                         mapSearch.addVenuesNearby(data);       // add YP data to sidebar
                    }
                } else if (hasLonLatInUrl && !isYPSearch && !isLocationSearch && !isRouteSearch) {
                    single = {
                        "longitude": UrlObject.longitude,
                        "latitude": UrlObject.latitude,
                        "title": parseFloat(UrlObject.latitude).toFixed(5) + ", " + parseFloat(UrlObject.longitude).toFixed(5)
                    }
                } else if (single !== undefined && data.locationFound) {
                    // Currently: Rangifer && when lon/lat in url?  
                    _c.log('location found, get lon/lat. ');
                    single.longitude = Common.Geo.getLon();
                    single.latitude =  Common.Geo.getLat();
                    single.title = UrlObject.where;
                    
                    if (Alces.Core) {
                        mapSearch.addVenuesNearby(data);
                    }
                } else {
                    _c.log('* Search produced no results');
                    var $results = jQuery("#mapsearch_results");
                    
                    $results.fadeTo(800,1.0).html("<span id='no_results'>Ei yhtään hakutulosta.</span>");
                    single = {
                        longitude : self.Geo.getLon(),
                        latitude :  self.Geo.getLat(),
                        streetAddress : "",
                        title : Common.getCurrentUIState().what.length ? "Ei yhtään hakutulosta" : "Paikkaa ei löydy",
                        "noResults" : true
                    };
                    SiteCatalystEvents.sendMapSearchNoResults();
                    QueryParams.zoomLevel = 5;
                    screenZoomLevel = QueryParams.zoomLevel;
                    _map.zoomTo(screenZoomLevel);
                }
                eventListenersExcludeSearch = true;

                if (!window.similarLocations || similarLocations.length <= 1) {
                    _c.log(single);

                    Common.addSingleVenue(single);      // handles also the showing of "no results"
                    mapSearch.getMapObj().setCenter(new EM.LonLat(screenLon || single.longitude, screenLat || single.latitude), screenZoomLevel, false);
                                        
                    if (single.noResults) {
                        SiteCatalystEvents.sendSingleVenue(single.noResults, MapLayers.getVisibleLayer(), whereValue, isLoggedIn);                    
                    }
                }
            }
            mapSearch.addEventListeners(eventListenersExcludeSearch);
            LoadingIndicator.remove(/* map initialization */);
            MapLayers.init();

        })
        .error( function (data) {
            _c.warn("Common.executeJSONSearch :: error");
        });
    };
   
    
    self.fixHeight = function () {
        _c.info("Common.fixHeight()");
        var viewportHeight = $(window).height(),
            mainHeaderHeight = $("#main-header").height() || 0,
            routingHeight = $("#tab-route-content").height();

        if (Rangifer.Core) {
            $('#mapsearch_sidebar').css("height", viewportHeight - mainHeaderHeight);
        }
        else {
            $('#mapsearch_sidebar').css("height", viewportHeight - mainHeaderHeight - 300);
        }
        //Common.Routing.setManeuversHeight()
        $mapObj.css('height', viewportHeight - mainHeaderHeight);
    };
    
    self.getCurrentUIState = function () {
        var _mapObj = mapSearch.getMapObj(),
            _coordinates = _mapObj.getCenter(),
            $whereField = $("#header-search-where"),
            $whatField = $("#header-search-what"),
            routeFromVal = $("#route-from").val(),
            routeToVal = $("#route-to").val();
            
        if (!!_coordinates) {
            _coordinates.transform(_mapObj.getProjectionObject(), new OpenLayers.Projection("EPSG:4326"));
        }

        var obj = {
            "location" : "http://"+ location.host + /[\/|\w]*kartta/.exec(location.pathname),
            "id": QueryParams.id || false,
            "groupId" : QueryParams.groupId || false,
            "what" : ($whatField.val() !== $whatField.attr("placeholder") ? $whatField.val() : "") || "",
            "where" : ($whereField.val() !== $whereField.attr("placeholder") ? $whereField.val() : "") || "",
            "latitude": _coordinates ? _coordinates.lat : QueryParams.latitude,
            "longitude": _coordinates ? _coordinates.lon : QueryParams.longitude,
            "printLayout": QueryParams.printLayout,
            "radius" : _getRadius(),
            "routeStyle" : Common.getRouteStyle(),
            "routeFrom" :  encodeURIComponent($("#route-from").val()) || false,
            "routeTo" :  encodeURIComponent($("#route-to").val()) || false,
            "layer" : MapLayers.getVisibleLayer() || "MAP",
            "zoomLevel": mapSearch.getMapObj().zoom || QueryParams.zoomLevel
        };
        obj.routeFrom = obj.routefrom ? obj.routefrom : mapSearch.Routing.pointHandler.getAllRouteInputFields().first().val();
         
        var $routePoints = mapSearch.Routing.pointHandler.getAllRouteInputFields(),
            routeTo = "",
            routeTemp;
            
        $routePoints.each(function(index, input) {
            if (index > 0) {
                routeTemp = $(input).val();
                if (routeTemp.length == 0 || routeTemp == $(input).attr('placeholder') || routeTemp == Constants.definedOnMap) {
                    var point = mapSearch.Routing.pointHandler.getUIRoutePoint(index);
                    if (!!point.lon && !!point.lat) {
                        routeTemp = "(" + point.lon.toFixed(6) + "," + point.lat.toFixed(6) + ")";
                    }
                }
                routeTo += encodeURIComponent(routeTemp);
                if (index+1 < $routePoints.length) {
                    routeTo += "|";
                }
            }
        });
        
        // if From-field deleted from UI, recreates it to URL from the first waypoint (equivalent of "from")
        if ((obj.routeFrom) && (obj.routeFrom == "undefined")) {
            var i = routeTo.indexOf("|");
            obj.routeFrom = routeTo.slice(0, i);
            routeTo = routeTo.slice(i+1);
        }        
        obj.routeTo = routeTo;
        
        if (obj.routeFrom == Constants.definedOnMap) {
            var point = mapSearch.Routing.pointHandler.getUIRoutePoint(0);
            routeTemp = "(" + point.lon.toFixed(6) + "," + point.lat.toFixed(6) + ")";
            obj.routeFrom = encodeURIComponent(routeTemp);            
        } else {
            obj.routeFrom = encodeURIComponent(obj.routeFrom);
        }
        
        obj.fullUrl = obj.location +"/?zoomLevel="+ obj.zoomLevel +"&radius="+ obj.radius + "&latitude="+ obj.latitude +"&longitude="+ obj.longitude;
                    
        if (Tabs.isLocTabShown()) {
            obj.fullUrl += (obj.what) ? "&what="+ encodeURIComponent(obj.what) : "";
            obj.fullUrl += (obj.where) ? "&where="+ encodeURIComponent(obj.where) : "&where=";
            obj.fullUrl += (obj.id) ? "&id="+ obj.id : "";
        } else if (Tabs.isRouteTabShown() && (obj.routeFrom && obj.routeTo)) {
            obj.fullUrl += "&routeFrom="+ obj.routeFrom +"&routeTo="+ obj.routeTo+"&routeStyle="+ obj.routeStyle;
        }
        
        if (obj.layer !== "MAP") {
            obj.fullUrl += "&layer="+ obj.layer;
        }
        
        obj.queryString = obj.fullUrl.split("?")[1];
        
        return obj; 
    };

    self.getRouteStyle = function () {
        var routeStyle;
        
        if (QueryParams.routeStyle) {
            routeStyle = QueryParams.routeStyle; 
        } else {
            routeStyle = jQuery("input[name=routestyle]:checked").val() || "FASTEST";
        }
        return routeStyle;
    }
    
    
    /*
     * getPOIBoudingBox (x, y)
     * Calculates bounding box around given given coordinates. Size of the pounding box is relative to the zoomrate and the correspoding POI icon size.
     * Zoomrates 4 - 11  : icon size 14 x 14 px
     * Zoomrates 12 - 15 : icon size 26 x 26 px
     * Zoomrates 16 - 18 : icon size 40 x 40 px
     */        
    self.getPOIBoundingBox = function(x, y) {
        var _bounds = new OpenLayers.Bounds(),
            _mapObj = mapSearch.getMapObj(),
            _zoomRate = _mapObj.getZoom(),
            _dx, _dy, _x1, _y1, _x2, _y2, _lonlat1, _lonlat2, _point1, _point2;  
        
        if (_zoomRate >= 4 && _zoomRate <= 11 ) {
            _dx = 9;
            _dy = 9;
        } else if (_zoomRate >= 12 && _zoomRate <= 15) {
            _dx = 16; 
            _dy = 16;
        } else if (_zoomRate >= 16 && _zoomRate <= 18) {
            _dx = 26; 
            _dy = 26; 
        } else {    // "shouldn't happen"
            _dx = 25; 
            _dy = 25;
        }
        
        _x1 = x - _dx; 
        _y1 = y - _dy; 
        _x2 = x + _dx;
        _y2 = y + _dy;        
        _lonlat1 = _mapObj.getLonLatFromViewPortPx(new OpenLayers.Pixel(_x1, _y1));
        _lonlat2 = _mapObj.getLonLatFromViewPortPx(new OpenLayers.Pixel(_x2, _y2));
        _point1 = new OpenLayers.Geometry.Point(_lonlat1.lon, _lonlat1.lat);
        _point2 = new OpenLayers.Geometry.Point(_lonlat2.lon, _lonlat2.lat);
        _point1.transform(_mapObj.getProjectionObject(), new OpenLayers.Projection("EPSG:4326"));
        _point2.transform(_mapObj.getProjectionObject(), new OpenLayers.Projection("EPSG:4326"));
        _bounds.extend(_point1);
        _bounds.extend(_point2);

        return _bounds;        
    };
        
    /*
     * getPOIDataFromPoint(x, y, POILayer)
     * - Asks EMAPI what data from given POILayer is at given coordinates (x, y)
     * - calls callback-function with result data.
     */
    self.getPOIDataFromPoint = function(x, y, poiLayerName, callbackFunctionName) {
        _c.log('getPOIDataFromPoint:: ' + x + ', ' + y + ', ' + poiLayerName)
        x = parseInt(x, 10); 
        y = parseInt(y, 10);
        var _mapObj = mapSearch.getMapObj();
        var _poiBounds, _bbox;

        _poiBounds = self.getPOIBoundingBox(x, y);
        _bbox = _poiBounds.toBBOX();
        
        $.ajax("http://emapi.fonecta.fi.si.eurodir.eu/geo/ows",
            {
                dataType : 'jsonp',
                jsonp : 'format_options',
                jsonpCallback : 'callback:' + callbackFunctionName,
                data: {
                    service : 'WFS',
                    version : '1.0.0',
                    request : 'GetFeature',
                    typeName : poiLayerName,
                    maxFeatures : 50,
                    BBOX : _bbox,
                    outputFormat : 'json'
                }//,
                //success : function(response) {
                //}
                //,error : function(response, status, error) {
                    // Currently this is disabled, as jQuery will throw an error for the mismatch of jsonCallback-parameter value and callback function name coming as response. 
                //} 
            }
         );
    };   
    
    self.drawOttoPopup = function(response) {
        // TODO: proper handling of empty response data?
        _c.log('Common.drawOttoPopup');
        _c.log(response);
        
        if (response.features !== undefined && response.features.length > 0) {
            // has POI-data
            var data = response.features[0];
            
            var htmlContent =  "<div class='poiPopupContent'><div class='tail'></div><h5>Otto-automaatti</h5>";
                htmlContent += "<dl><dt>Osoite</dt><dd>" + data.properties.street + " " + data.properties.streetnumb + "<br />  " +  data.properties.postcode + " " +data.properties.town + "</dd>";
                htmlContent += "<dt class='additionalInfo'><strong>Lisätietoa</strong></dt><dd class='additionalInfo'><a target='_blank' href='http://www.otto.fi'>www.otto.fi</a></dd></dl>";
                htmlContent += "<span class='infoSource'>Lähde: Otto.fi</span></div>"
                
            _c.log(htmlContent);
            
            var _px = Common.Geo.getPixelFromLonLat(data.geometry.coordinates[0], data.geometry.coordinates[1]);
            
            poiPopup.show({
                lon: data.geometry.coordinates[1], 
                lat: data.geometry.coordinates[0], 
                contentHtml: htmlContent
            });
            // TODO: popup close on mouse exit (or when another popup opened?) 
        } else {
            // No POI-data
        }
    };
    
    self.drawGuestHarboursPopup = function(response) {
        // TODO: proper handling of empty response data?
        _c.log('Common.drawGuestHarboursPopup');
        _c.log(response);
        
        if (response.features !== undefined && response.features.length > 0) {
            // has POI-data
            var data = response.features[0];
            _c.log(data);
            
            var htmlContent =  "<div class='poiPopupContent'><div class='tail'></div><h5>" + data.properties.Nimi + "</h5>";
                htmlContent += "<dl><dt>Sijainti</dt><dd>" + data.properties.Koordinaat + "</dd>";
                htmlContent += "<dl><dt>Puhelin</dt><dd>" + data.properties.Puhelin + "</dd>";
                htmlContent += "<dl><dt>Internet</dt><dd><a target='_blank' href='http://" + data.properties.WWW + "'>" + data.properties.WWW + "</a></dd>";
                htmlContent += "<dt class='additionalInfo'><strong>Lisätietoa</strong></dt><dd class='additionalInfo'><a target='_blank' href='http://www.helsinginvierasvenesatama.fi'>www.helsinginvierasvenesatama.fi</a></dd></dl>";
                htmlContent += "<span class='infoSource'>Lähde: Vierassatamaopas.fi</span></div>"
                
            _c.log(htmlContent);
            
            var _px = Common.Geo.getPixelFromLonLat(data.geometry.coordinates[0], data.geometry.coordinates[1]);
            
            poiPopup.show({
                lon: data.geometry.coordinates[1], 
                lat: data.geometry.coordinates[0], 
                contentHtml: htmlContent, 
                width: 330,
                height: 125
            });
            // TODO: popup close on mouse exit (or when another popup opened?) 
        } else {
            // No POI-data
        }
    };
    
    self.Geo = (function () {
        var self = {};

        self.getLon = function () {
            return (mapPreferences.geoLon.length) ? mapPreferences.geoLon : "24.95";
        };
        
        self.getLat = function () {
            return (mapPreferences.geoLat.length) ? mapPreferences.geoLat : "60.15";
        };
        
        // getAddressFromLonLat:: Reverse-geocode lon/lat to address. Uses EMAPI / EDSA geocoder by default.
        self.getAddressFromLonLat = function(lon, lat, callback) {
            var address, municipality;
            
            if (!!lon && !!lat) {
                jQuery.ajax(
                    Settings.emapi_geocode_uri,
                    {
                        data: {
                            country: 'FIN',
                            yLat: lat,
                            xLng: lon 
                        },
                        dataType: 'jsonp', 
                        
                        success: function (response) {
                            _c.log('getAddressFromLonLat: AJAX reverse geocode success from lon:' + lon + ' lat: ' + lat);
                            _c.log(response);
                            
                            if (!!response && !!response.places && response.places.length > 0) {
                                // add new waypoint with address from response[0]
                                address = response.places[0].streetHouseNo + ", " + response.places[0].municipality;
                                municipality = response.places[0];
                                callback(address, lon, lat, municipality);
                            } else {
                                callback({"error":true});
                                _c.warn("no reverse geocode results");
                            }
                                                      
                            return { "address" : address, "municipality" : municipality };
                        },
                        error: function (response) {
                            _c.warn("Common.getAddressFromLonLat :: error");
                            _c.dir(response);
                            return false;
                        }
                });
            } else {
                return false;
            }
        };

        getGeocodeForAddress = function (address, callback) {
            _c.log('Common.Geo.getGeocodeForAddress :: ' + address);
            jQuery.ajax(
                Settings.emapi_geocode_uri,
                {
                    data: {
                        country: 'FIN',
                        address: address
                    },
                    dataType: 'jsonp', 
                    
                    success: function (data) {
                        callback(data);
                        return data;
                    },
                    error: function (data) {
                        _c.warn("Common.Geo.getGeocodeForAddress :: error");
                        _c.dir(data);
                        return data;
                    }
            });
        };

        self.getLonLatFromPixel = function(x, y) {
            var _mapObj = mapSearch.getMapObj(),
                _px = new OpenLayers.Pixel(x, y),
                _lonlat = _mapObj.getLonLatFromPixel(_px),
                _point = new OpenLayers.Geometry.Point(_lonlat.lon, _lonlat.lat);
            
            //_point.transform(_mapObj.getProjectionObject(), new OpenLayers.Projection("EPSG:4326"));
            _lonlat.transform(_mapObj.getProjectionObject(), new OpenLayers.Projection("EPSG:4326"));
            // map default is EPSG900913 = meters, EPSG:4326 = lonLot coordinates in degrees
            return _lonlat;
        }
        
        self.getPixelFromLonLat = function(lon, lat) {
            var _mapObj = mapSearch.getMapObj(),
            
            _lonlat = OpenLayers.Layer.SphericalMercator.forwardMercator(lon, lat);       
            _px = _mapObj.getPixelFromLonLat(_lonlat);
            
            return _px;            
        }
        
        /*
         * Converts position (lon or lat) from decimals to DMS (days, minutes, seconds, also called sexagesimal). Eg.decimalToSexagesimal(60.18285) ==  "60° 10.971'"
         * Using the format in Eniro.fi, not the 60° 10' 58.26" 
         */
        self.decimalToSexagesimal = function(posDec) {
            var __posSex = parseInt(posDec, 10);
            var __posSexMinutes = (posDec - __posSex) * 60;
            __posSex += "\xB0 " + __posSexMinutes.toFixed(3) + "' ";
            
            return __posSex;
        };        
        
        return self;
    }());
    
    return self;
}());


var poiPopup = (function() {
    var self = this,
        $popup = $("<div id='poiPopup' class='poiPopup'><a class='close'></a><div class='box'></div><div class='tail'></div></div>"),
        detailspopup,
        defaults = {
            closeCallback   : undefined, 
            contentHtml     : "",
            elemClass       : "poiPopup",
            elemId          : "poiPopup",
            lon             : undefined,
            lat             : undefined,
            height          : 110,
            width           : 294
        }, 
        popups = [];

    this.clearPopups = function() {
        var index = 0,
            length = popups.length;
            
        if (length > 0) {
            for (; index < length; index++) {
                var oldPopup = popups.pop();
                if (oldPopup.destroy) {
                    oldPopup.destroy();
                }
            }
        }
    }
              
    this.show = function(params) {
        var options = $.extend({}, defaults, params);
        var _mapObj = mapSearch.getMapObj(), 
            _lonlat = OpenLayers.Layer.SphericalMercator.forwardMercator(options.lat, options.lon),
            _point = new OpenLayers.LonLat(_lonlat.lon, _lonlat.lat),
            _closeButton = document.createElement("a"),
            _px = _mapObj.getLayerPxFromLonLat(_lonlat);
            
        
        _c.log(' XX poiPopup: ');
        _c.log(options);
        
        _closeButton.setAttribute("class", "close");
        if ($("#poiPopup").length <= 0) {
            $popup.appendTo("body");
        }
        
        detailsPopup = new OpenLayers.Popup(options.elemId,
                            _point,
                            new OpenLayers.Size(options.width, options.height),
                            options.contentHtml,
                            true);
        detailsPopup.disableFirefoxOverflowHack = true;
        detailsPopup.panMapIfOutOfView = true;
        
        self.clearPopups();
        // remove oold & other popups (YP) popups                     
        
        _mapObj.addPopup(detailsPopup);
        popups.push(detailsPopup);

    };
    
    this.close = function() {
        
        
        return this;
    };

    this.onPopupClose = function() {
        return true;  
    };

    this.init = function () {
    }
        
    return this; 
})();

/**
 * @function initEMAPI()
 * Works as an onload function from
 * EMAPI point-of-view.
 *
 */

function initEMAPI() {
    _c.info("initEMAPI()");
    
    // This is mandatory hack to get IE's to work:
    jQuery("html, body").css("height", "100%");
    jQuery("#"+ mapPreferences.mapElem).css('height', window.innerHeight - 75);
    
    var setCorrectZoomLevel = (function () {
        // If set through URL
        if (QueryParams && QueryParams.zoomLevel) {
            QueryParams.zoomLevel = parseInt(QueryParams.zoomLevel, 10);
            return;
        }
        
        var type = (mapPreferences) ? mapPreferences.type : -1;
        
        QueryParams.zoomLevel = 15; // Default zoom level
        
        // 7 = Geo
        if (type === "7") {
            QueryParams.zoomLevel = 10;
        }
        
        // 1 = Municipality, 2 = Town
        if (type === "" || type === "1" || type === "2") {
            QueryParams.zoomLevel = 12;
        }

        // 3 = District, 4 = Landmark, 5 = Postcode
        if (type === "3" || type === "4" || type === "5") {
            QueryParams.zoomLevel = 15;
        }
        
        // 6 = Street
        if (type === "6") {
            QueryParams.zoomLevel = 17;
        }

        _c.info("Zoom level set to "+ QueryParams.zoomLevel); 
    }());
    
    var applyAutoSuggest = (function ($) {
        var autoSuggestObject = Rangifer.autoSuggest || Alces.autoSuggest || false;
        var autoSuggestCompositeObject = Rangifer.autoSuggestComposite || Alces.autoSuggestComposite || false;
        
        if (!autoSuggestObject) {
            return false;
        }
        
        var targets = [{
            "input" : "header-search-where",
            "container" : (Rangifer.autoSuggest) ? "main-header" : "tab-location",
            "type" : "where",    
            "isCategoryInput" : null,
            "composite" : false,
            "autoposition" : false
        },
        {
            "input" : "header-search-what",
            "container" : (Rangifer.autoSuggest) ? "main-header" : "tab-location",    
            "type" : "what",
            "isCategoryInput" : (Alces.autoSuggest) ? $("#header-search-is-category") : null,
            "composite" : (Alces.autoSuggest) ? true : false,
            "autoposition" : false
        },
        {
            "input" : "route-from",
            "container" : (Rangifer.autoSuggest) ? "route-data" : "tab-route",                
            "type" : "where",
            "isCategoryInput" : null,
            "composite" : false,
            "autoposition" : false
        },
        {
            "input" : "route-to",
            "container" : (Rangifer.autoSuggest) ? "route-data" : "tab-route",    
            "type" : "where",    
            "isCategoryInput" : null,
            "composite" : false,
            "autoposition" : (Alces.autoSuggest) ? true : false
        }];
        
        var autoSuggests = [],
            i = 0,
            l = targets.length;

        _c.info("Initializing autosuggest");            
        for (;i<l;i++) {      
            if (!!targets[i].composite) {
                autoSuggests.push(new autoSuggestCompositeObject({
                    id: "autosuggest-" + targets[i].input,
                    container : $("#" + targets[i].container),
                    input : $("#"+ targets[i].input),
                    isCategoryInput : targets[i].isCategoryInput,
                    autoposition : targets[i].autoposition,
                    types: [
                        {
                            type: 'what',
                            title: 'Hakusanat'
                        },
                        {
                            type: 'company',
                            title: 'Yritykset'
                        }
                    ]
                }));
            } else { 
                autoSuggests.push(new autoSuggestObject({
                    id: "autosuggest-" + targets[i].input,
                    container : $("#" + targets[i].container),
                    input : $("#"+ targets[i].input),
                    isCategoryInput : targets[i].isCategoryInput,
                    autoposition : targets[i].autoposition,
                    type : targets[i].type
                }));
            }
        }
    }(jQuery));


    Extend.init();

    mapSearch = (window.Rangifer && Rangifer.mapSearch) ? new Rangifer.mapSearch(jQuery) : new Alces.mapSearch(jQuery);
    if (mapPreferences) {
        mapSearch.init({
            "prefs": mapPreferences
        });
    }
};


// TEMP:
(function oym($) {
    
    if (~window.name.indexOf("oym")) {
        $("#oym").attr("checked", "checked");
        $("#oym-test").attr("name", "provider").attr("value", "onYourMapService");        
    }
    
    $("#oym").live("click", function () {
        if ($(this).is(":checked")) {
            window.name += "oym";
            $("#oym-test").attr("name", "provider").attr("value", "onYourMapService");        
        }
        else {
            window.name = window.name.replace(/oym/gi, "");
            $("#oym-test").removeAttr("name").removeAttr("value");
        }
    });
    
}(jQuery));
