/**
 *
 * @project     fcmq2009
 * @purpose     main javascript file that initialize the application object.
 * @author      Alexandre Dubé - Mapgears inc. (adube@mapgears.com)
 * @copyright
 * <b>Copyright (c) 2009 Mapgears Inc.</b>
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

var fcmq, lang = [], options;
/**
 * Method: initialize
 * Called when the DOM has finished to load.  Create a fcmq application with
 *     the options object.
 */
function initialize() {
    options['lang'] = gLANG;

    fcmq = new fcmqApplication(options);
    onBodyResize();
    window.onresize = beforeBodyResize;
};

/**
 * Method: fcmqApplication
 * Used as a class to create the application object.
 *
 * Parameters:
 * options - {Object} An object of options used to create the application
 *                    components
 */
function fcmqApplication(options) {
    OpenLayers.Util.extend(this, options);

    this.DEFAULT_OPTIONS = {};

    OpenLayers.Util.applyDefaults(this, this.DEFAULT_OPTIONS);

    /*if (!GBrowserIsCompatible()) {
        alert("Your browser is not compatible to browse this page");
        return false;
        }*/

    // Initialization
    this.tempOverlays = [];
    this.selectedTempOverlays = [];
    this.lastSelectedTempOverlayIds = [];
    this.allMarkers = [];
    this.lastDataLevel = null;
    this.timerId = null;
    this.tempOverlayInfoWindow = false;
    this.otherOverlayInfoWindow = false;
    this.tempOverlayPopup = document.getElementById('divTempOverlayPopupMenu');
    this.pleaseWaitDiv = document.getElementById('divPleaseWait');
    this.pleaseWaitText = document.getElementById('pleaseWaitText');

    // marker types
    this.routingStartMarkerType = 1;
    this.routingEndMarkerType = 2;
    this.shieldMarkerType = 3;
    this.serviceMarkerType = 4;
    this.groomerMarkerType = 5;
    this.routingRelayMarkerType = 6;
    this.routingTempRelayMarkerType = 7;

    // Browser compatibility adjustments
    if(BrowserDetect['browser'] == 'Explorer') {
        if(BrowserDetect['version'] < 7){
            options['more']['offsetx'] += 75;
        } else {
            options['more']['offsetx'] += 10;
        }
    }
    if(BrowserDetect['OS'] == 'Linux') {
        options['more']['offsetx'] -= 65;
    }
    //alert(BrowserDetect['browser']+" "+BrowserDetect['version']);

    // Map object & basic controls
    this.map = new GMap2(document.getElementById("map"));

    // ArgParser used to set user-custom center/zoom and layers (groomers)
    if (this.enableArgParser) {
        this.argParser = new ArgParser(this.map, this, {});
    }
    if (!this.argParser || !this.argParser.centered) {
        this.map.setCenter(
            new GLatLng(this.initLat, this.initLng), this.initZoom
        );
    }

    this.map.setUIToDefault();
    this.map.setMapType(G_PHYSICAL_MAP);
    this.lastMapType = this.map.getCurrentMapType();
    this.map.enableDoubleClickZoom();
    this.map.addMapType(G_SATELLITE_3D_MAP);
    this.map.addControl(new GOverviewMapControl());
    this.map.main = this;

    // "More layers"
    this.layers = [];
    if (this.enableMoreLayers) {
        this.layers[0] = new GLayer("org.wikipedia.fr");
        this.layers[0].Visible = false;
        this.layers[0].Added = false;

        this.layers[1] = new GLayer("org.wikipedia.en");
        this.layers[1].Visible = false;
        this.layers[1].Added = false;

        this.layers[2] = new GLayer("com.panoramio.all");
        this.layers[2].Visible = false;
        this.layers[2].Added = false;

        // === Create the layerControl, but don't addControl() it ===
        // = Pass it an array of names for the checkboxes =
        this.layerControl = new LayerControl(
            this, ["Wiki FR", "Wiki EN", "Photos"]);

        // === Create the MoreControl(), and do addControl() it ===
        this.moreControl = new MoreControl(this)
        this.map.addControl(this.moreControl);
    }

    // Legend
    if (this.enableLegend) {
        this.legend = new LegendControl();
        this.map.addControl(this.legend);
        this.legend.initText();
    }

    // MarkerManager
    if (this.enableShieldLayer || this.enableServiceLayer) {
        this.markerManager = new MarkerManager(this.map, {trackMarkers:true});
    }

    // Shields (added to the MarkerManager)
    if (this.enableShieldLayer) {
        var oOptions = {'extent': this.QCBBOX,'layer': 'shield','level': 19};
        if(this.debug !== true) {
            this.getFeatures(this.addShields, oOptions);
        }
    }

    // Services
    if (this.enableServiceLayer) {
        oOptions['layer'] = "services";
        if(this.debug !== true) {
            this.getFeatures(this.addServices, oOptions);
        }
    }

    // Event listeners
    this.mapDragEnd = GEvent.bind(this.map, "moveend", this, this.onMapMoveEnd);
    this.mapZoomEnd = GEvent.bind(this.map, "zoomend", this, this.onMapZoomEnd);
    this.mapMouseOut = GEvent.bind(this.map, "mouseout", this, 
                                   this.onMapMouseOut);
    this.addMapMouseMoveListener();
    this.mapInfoWindowOpen = GEvent.bind(this.map, "infowindowopen", this,
                                         this.onMapInfoWindowOpen);
    this.mapInfoWindowClose = GEvent.bind(this.map, "infowindowclose", this,
                                         this.onMapInfoWindowClose);

    if (this.enableShieldLayer || this.enableServiceLayer) {
        this.mapTypeChanged = GEvent.addListener(
            this.map, "maptypechanged", this.onMapTypeChanged
        );
    }
    // StrategyBBOX
    var oOptions = {};
    if(this.ratio) {
        oOptions['ratio'] = this.ratio;
    }
    this.strategyBBOX = new StrategyBBOX(this.map, this, oOptions);

    // Groomers
    if (this.enableGroomers) {
        var oOptions = {
            'groomerMarkerType': this.groomerMarkerType
        };
        this.groomers = new Groomers(this.map, this, oOptions);
    }

    // Distance Calculator.  Always added even if not enabled, only the "click"
    // event is disabled.
    var oOptions = {
        'kmValue': this.kmValue,
        'milesValue': this.milesValue,
        'lengthProperty': this.lengthProperty,
        'lengthUnit': this.lengthUnit,
        'lengthPrecision': this.lengthPrecision
    };
    this.distanceCalculator = new DistanceCalculator(
        this.map, this, oOptions
    );
    this.selectStyle = this.getStyleObject("select");
    this.hoverStyle = this.getStyleObject("hover");
    this.routingStyle = this.getStyleObject("routing");
    this.map.distanceCalculator = this.distanceCalculator;
    if (this.enableDistanceCalculator) {
        GEvent.bind(this.map,'click',this,this.onMapClick);
    }


    // Routing object initialization
    if (this.enableRouting) {
        var oOptions = {
            'directionTableClassName': this.directionTableClassName,
            'directionDivId': this.directionDivId,
            'directionDivTableContainerId': this.directionDivTableContainerId,
            'transQuebecCheckBox': this.transQuebecCheckBox,
            'directionProperty': this.directionProperty,
            'angleProperty': this.angleProperty,
            'angleFactorProperty': this.angleFactorProperty,
            'cardinalDirections' : this.cardinalDirections,
            'cardinalDirectionsOrder' : this.cardinalDirectionsOrder,
            'turnDirections': this.turnDirections,
            'turnDirectionsOrder': this.turnDirectionsOrder,
            'numberProperty': this.numberProperty,
            'startIdProperty': this.startIdProperty,
            'endIdProperty': this.endIdProperty,
            'autoRecenterCheckBox': this.autoRecenterCheckBox,
            'routingRatio': this.routingRatio,
            'directionTitle': this.directionTitle,
            'startMarkerType': this.routingStartMarkerType,
            'endMarkerType': this.routingEndMarkerType,
            'relayMarkerType': this.routingRelayMarkerType,
            'tempRelayMarkerType': this.routingTempRelayMarkerType
        };
        this.routing = new RoutingControl(
            this.map, this, this.distanceCalculator, oOptions
        );
        this.map.addControl(this.routing);
        this.routing.createInitMarkers();
        this.map.routing = this.routing;
        this.routing.initText();
        //hideDiv(document.getElementById('divRoutingMenu'), true);
    }

    // Search
    if (this.enableSearch) {
        this.geocoder = new GClientGeocoder();
    }

    if (this.enableAdPopup) {
        var oOptions = {
		'hideDelay': this.adPopupHideDelay,
    'popupIds': ["groomerAdPopup"]
           
        };
        this.adPopupManager = new AdPopupManager(oOptions);
        window.setTimeout(
            "fcmq.adPopupManager.hidePopups()",
            this.adPopupManager.getHideDelay()
        );
    }

    // Print bounds
    //this.addPrintBounds();

   // Language selection
    this.langSelector = new LangSelector(lang);
    this.langSelector.setLang(options.lang);

    // Current location initializations
    this.currentDataLevel = this.getDataLevel(this.map.getZoom());
    // the first tempOverlays are fetched from the onBodyResizeFunction

    if(this.debug) {
        document.getElementById('debugDiv').style.display = "";
        document.getElementById('myLevel').innerHTML = "";
        document.getElementById('myLevel').innerHTML = this.map.getZoom();
    }

    // activate groomers if checkbox is checked
    if (this.enableGroomers && this.groomers.isActive()) {
        this.groomers.onActivate();
    }
};


// ==== toggleLayer adds and removes the layers ====
fcmqApplication.prototype.toggleLayer = function(i) {
    if (this.layers[i].Visible) {
        this.layers[i].hide();
    } else {
        if(this.layers[i].Added) {
            this.layers[i].show();
        } else {
            this.map.addOverlay(this.layers[i]);
            this.layers[i].Added = true;
        }
    }
    this.layers[i].Visible = !this.layers[i].Visible;
};

/**
 * Method: onMapMoveEnd
 * Called when the map is moved.  Call the StrategyBBOX update function to
 *     check if a new call to the php script is necessary.  Since "zoomend"
 *     events are triggered before "moveend" events, the change of dataLevel
 *     is always made before the map finished to move.
 *
 * "mapmove" is used instead of "dragend" because the minimap (bottom right) 
 *     doesn't trigger the "dragend" event when the map is panned with it.
 */
fcmqApplication.prototype.onMapMoveEnd = function() {
    // todo: if poor performance, destroy tempOverlays before
    this.strategyBBOX.update();
};

/**
 * Method: onMapZoomEnd
 * Called when the map level changes.  
 *     If the new zoom level requires a new dataset (dataLevel), unselect all
 *     currently selected tempOverlays (keeping their ids for further 
 *     reselection), clear all tempOverlays.  Also, either force the update
 *     of the StrategyBBOX.
 *
 *     Else (if the new zoom level has the same dataset as its previous level),
 *     update only.
 *
 * Parameters:
 * nOldLevel - {Integer} The previous map level
 * nNewLevel - {Integer} The new map level
 */
fcmqApplication.prototype.onMapZoomEnd = function(nOldLevel, nNewLevel) {
    this.lastDataLevel = this.getDataLevel(nOldLevel);
    var nDataLevel = this.getDataLevel(nNewLevel);

    if(this.lastDataLevel != nDataLevel) {
        this.currentDataLevel = nDataLevel;
        this.saveSelectedTempOverlayIds();
        this.unselectAllTempOverlays();
        this.clearTempOverlays();
        
        var oOptions = {'force': true};
        if(this.lastSelectedTempOverlayIds.length > 0) {
            oOptions.moreValues = this.getLastSelectedTempOverlayIdsList();
            oOptions.moreValueField = this.getValueField();
        }
        this.strategyBBOX.update(oOptions);

    } else {
        this.strategyBBOX.update();
    }

    if(this.debug) {
        document.getElementById('myLevel').innerHTML = "";
        document.getElementById('myLevel').innerHTML = this.map.getZoom();
    }
};

/**
 * Method: onMapMouseOut
 * When the mouse leaves the map, if a popup is currently existent, restore all
 *     "current" styles of the tempOverlay and remove the popup (since we don't
 *     know which tempOverlay is being hovered).
 *
 * Parameters:
 */
fcmqApplication.prototype.onMapMouseOut = function(oLatLng) {
    var oMain = fcmq;
    if(oMain.tempOverlayInfoWindow){
        oMain.restoreAllTempOverlayStyles();
    }
};

/**
 * Method: getStyleObject
 * Given a specific style type and optionally a class type, get a style object
 *     ready to be used for setting overlay style.
 *
 * Parameters:
 * szStyleType - {String} The style type to use
 * nClassType  - {Integer} (Optional) The class type to use
 *
 * Returns:
 * {Object} An object ready to be used as options for setting Overlay styling.
 */
fcmqApplication.prototype.getStyleObject = function(szStyleType, nClassType) {
    var oStyle = {};
    szStyleType = szStyleType || 'default';

    if(this.styles && this.styles[szStyleType]) {
        if(!nClassType) {
            oStyle = this.styles[szStyleType];
            if(!oStyle.weight) {    
                oStyle.weight = oStyle.width;
            }
        } else if (this.styles[szStyleType][nClassType]){
            oStyle = this.styles[szStyleType][nClassType];
            if(!oStyle.weight) {    
                oStyle.weight = oStyle.width;
            }
        }
    } else {
        oStyle = {'color':'#ff0000', 'weight':8};
    }
    
    return oStyle;
};

/**
 * Method: getMarkerStyleObject
 * Given a specific marker style type and optionally a class type, get a
 *     style object ready to be used for setting marker style.
 *
 * Parameters:
 * szStyleType - {String} The style type to use
 * nClassType  - {Integer} (Optional) The class type to use
 *
 * Returns:
 * {Object} An object ready to be used as options for setting Overlay styling.
 */
fcmqApplication.prototype.getMarkerStyleObject = function(szStyleType, nClassType, oOptions) {
    var oStyle = {}, oMarkerStyle = {};
    oOptions = oOptions || {};
    szStyleType = szStyleType || 'default';

    if(this.markers && this.markers[szStyleType]) {
        if(!nClassType) {
            oStyle = this.markers[szStyleType];
        } else if (this.markers[szStyleType][nClassType]){
            oStyle = this.markers[szStyleType][nClassType];
        }
    } else {
        oStyle = {'color':'#ff0000', 'weight':8};
    }
    
    if(oStyle.image && oStyle.image.image &&
       oStyle.image.height && oStyle.image.width){
        oMarkerStyle.image = oStyle.image.image;
        oMarkerStyle.iconSize = 
           new GSize(oStyle.image.width,oStyle.image.height);
        if(oOptions['iconAnchor'] == "CC") {
            oMarkerStyle.iconAnchor = 
                new GPoint(oStyle.image.width/2,oStyle.image.height/2);
        } else {
            oMarkerStyle.iconAnchor = 
                new GPoint(oStyle.image.width,oStyle.image.height/2);
        }
    } else {
        // unsupported
    }

    if(oStyle.shadow) {
        // unsupported
    } else {
        oMarkerStyle.shadow = "";
    }

    return oMarkerStyle;
};

/**
 * Method: getDataLevel
 * Given a specific map level, return the according data level to use for
 *     requests, such as getTempOverlays requests.
 *
 * Parameters:
 * nLevel - {Integer} A map level
 *
 * Returns:
 * {Integer} The data level to use for the given map level
 */
fcmqApplication.prototype.getDataLevel = function(nLevel) {
    if(!this.dataLevels) {
        return nLevel;
    }

    var nReturnLevel = null;

    for(var i=0, len=this.dataLevels.length; i<len; i++) {
        var nDataLevel = this.dataLevels[i];
        if (nDataLevel >= nLevel) {
            if(!nReturnLevel) {
                nReturnLevel = nDataLevel;
            } else if(nDataLevel < nReturnLevel) {
                nReturnLevel = nDataLevel;
            }
        }
    }

    return nReturnLevel;
};

/**
 * Method: addTempOverlays
 * Called when the map has to get new tempOverlays (from the pg2geojson script).
 *     Read the geojson returned to get the overlays.  Add them to the map, to
 *     the tempOverlays array and add "mouseover" and "mouseout" listeners to
 *     display a popup when the mouse gets over an overlay.
 *
 * Parameters:
 * doc - {AJAXResponse} The response from the AJAX query
 */
fcmqApplication.prototype.addTempOverlays = function(doc) {
    var aoTempOverlays, oTempOverlay;
    
    aoTempOverlays = fcmq.readGeoJSON(doc);

    for(i=0; i<aoTempOverlays.length; i++) {
        oTempOverlay = aoTempOverlays[i];

        // if overlay is already added, skip it
        if(fcmq.getTempOveralyById(oTempOverlay['id'])) {
            continue;
        }

        fcmq.map.addOverlay(oTempOverlay);
        fcmq.tempOverlays.push(oTempOverlay);

        GEvent.bind(
            oTempOverlay,
            'mouseover',
            {
                'overlay': oTempOverlay,
                'main': fcmq
            },
            fcmq.onTempOverlayMouseOver
        );
        GEvent.bind(
            oTempOverlay,
            'mouseout',
            {
                'overlay': oTempOverlay,
                'main': fcmq
            },
            fcmq.onTempOverlayMouseOut
        );

        if (fcmq.enableDistanceCalculator) {
            GEvent.addListener(oTempOverlay, "overlayselected", 
                               fcmq.distanceCalculator.onTempOverlaySelect);
            GEvent.addListener(oTempOverlay, "overlayunselected", 
                               fcmq.distanceCalculator.onTempOverlayUnselect);
        }
    }

    // hide pleaseWait
    fcmq.hidePleaseWait();

    // reselect previously selected temp overlays
    fcmq.reselectLastSelectedTempOverlays();
};

/**
 * Method: readGeoJSON
 * From a given GeoJSON ajax response, create overlays object and put them in 
 *     an array.  Add some properties and an id to the overlay at the same time.
 *
 * Parameters:
 * doc - {AJAXResponse} The response from the AJAX query
 *
 * Returns:
 * {Array(<GOverlay>)}
 */
fcmqApplication.prototype.readGeoJSON = function(doc) {
    // todo : how to use 'this' instead of fcmq ?

    var jsonData, aoFeatures, oFeature, nId, szColor, nWidth, szStyle, szClass;
    var szGeometryType, aoCoordinates, aCoordinate, bEncoded, oOverlay;
    var aoOverlays = [];

    // === Parse the JSON document === 
    jsonData = eval('(' + doc + ')');
    aoFeatures = jsonData;

    for(var i=0; i<aoFeatures.length; i++){
        oFeature = aoFeatures[i];
        
        if(!oFeature.type) {
            continue;
        }

        nId = oFeature[fcmq['overlayId']];
        szStyle = oFeature.properties[fcmq['styleProperty']];
        szClass = oFeature.properties[fcmq['classProperty']];

        var oStyle;

        if(!(szStyle && szClass)) {
            oStyle = fcmq.getStyleObject("default");
        } else {
            oStyle = fcmq.getStyleObject(szStyle,szClass);
        }

        szGeometryType = oFeature.geometry.type;
        aCoordinates = oFeature.geometry.coordinates;
        bEncoded = (typeof(aCoordinates) == 'string');

        switch (szGeometryType) {

          case "LineString":
            if(bEncoded) {
                szLevels = oFeature.geometry['levels'];
                oOverlay = new GPolyline.fromEncoded({
                    'clickable': true,
                    'points': aCoordinates,
                    'color': oStyle.color,
                    'weight': oStyle.weight,
                    'opacity': oStyle.opacity,
                    'levels': szLevels,
                    'zoomFactor': 2,
                    'numLevels': 18
                });
            } else {
                aoLatLng = [];
                for(var j=0; j<aCoordinates.length; j++) {
                    aCoordinate = aCoordinates[j];
                    aoLatLng.push(new GLatLng(aCoordinate[1],aCoordinate[0]));
                }
                oOverlay = new GPolyline(
                    aoLatLng, oStyle.color, oStyle.weight, {'opacity':oStyle.opacity});
            }

            break;

          case "Point":

            if(bEncoded) {
                // not supported
            } else {
                oLatLng = new GLatLng(aCoordinates[1],aCoordinates[0]);
                oOverlay = new GMarker(oLatLng);
            }

            break;
          case "Polygon":
            toto = 1;
            break;
        }

        oOverlay['id'] = nId;
        oOverlay.defaultStyle = oStyle;
        oOverlay.properties = oFeature.properties;

        aoOverlays.push(oOverlay);
    }

    return aoOverlays;
};

/**
 * Method: getTempOveralyById
 * Get the tempOverlay that has the specified id.
 *
 * Parameters:
 * nId - {Integer} The id of the tempOveraly to search.
 *
 * Returns:
 * {<GOverlay>} The tempOverlay that has the specific id.
 */
fcmqApplication.prototype.getTempOveralyById = function(nId) {
    var oOverlayFound = false, oOverlay;

    for(var i=0; i<this.tempOverlays.length; i++) {
        oOverlay = this.tempOverlays[i];
        
        if(oOverlay['id'] == nId) {
            oOverlayFound = oOverlay;
            break;
        }
    }

    return oOverlayFound;
};

/**
 * Method: getTempOverlayCurrentStyle
 * Get the tempOverlay current effective style
 *
 * Parameters:
 * oOverlay - {<GOverlay>} The tempOverlay to get the style from
 *
 * Returns:
 * {Object} The current style of the overlay.
 */
fcmqApplication.prototype.getTempOverlayCurrentStyle = function(oOverlay) {
    var oStyle = oOverlay.currentStyle || oOverlay.defaultStyle;
    return oStyle;
};

/**
 * Method: getFeatures
 * Called when the map moves (by dragging or on zoom change).  Launches a
 *     GetFeatures request with current extent.  The features (tempOverlays)
 *     returned will be read by the readGeoJSON function.
 *
 * If options['extent'] false is passed, then skip the extent parameter.
 *
 * Parameters:
 * options {Object} An object of options used to force a specific parameters
 *                  when necessary.
 */
fcmqApplication.prototype.getFeatures = function(callback, options) {
    options = options || {};
    this.setPleaseWait("pwGetFeatures");

    var szURL = this.pg2geojsonURL;

    var nLevel = options['level'] || this.currentDataLevel;

    if(this.debug) {
        //nLevel = (nLevel > 6) ? nLevel-3 : 4;
    }

    var oParams = {
        'request': 'GetFeatures',
        'layer': options['layer'] || this.layer,
        'style': options['style'] || this.currentStyle,
        'level': nLevel,
        'encode': options['encode'] || this.encode,
        'random': Math.random()
    };

    if(options['extent'] !== false){
        oParams['extent'] = options['extent'] || this.map.getBounds().toBBOX();
    }

    if(options.moreValues && options.moreValueField) {
        oParams['morevalues'] = options.moreValues;
        oParams['morevaluefield'] = options.moreValueField;
    }

    szURL += OpenLayers.Util.getParameterString(oParams);

    // nogeom support
    szURL += (options['nogeom']) ? "&nogeom" : "";

    GDownloadUrl(szURL, callback);
};

/**
 * Method: getClosestPoint
 * Called when one of the "Routing" markers is moved.  Launches a getPoint
 *     request to find the closest feature of a given layer.  The "callback"
 *     function is called when the query has finished.
 *
 * Parameters:
 * szPoint  - {String} A 'lat,lng' string of the point
 * callback - {function} The function to call when the query has finished
 * options {Object} An object of options used to force a specific parameters
 *                  when necessary.
 */
fcmqApplication.prototype.getClosestPoint = function(
    szPoint, callback, options) {
    this.setPleaseWait("pwGetClosestPoint");

    options = options || {};

    var szURL = this.pg2geojsonURL;

    var oParams = {
        'request': 'GetPoint',
        'point': szPoint,
        'layer': options['layer'] || this.layer,
        'level':  options['level'] || this.currentDataLevel,
        'encode': false,
        'random': Math.random()
    };

    szURL += OpenLayers.Util.getParameterString(oParams);

    GDownloadUrl(szURL, callback);
};

/**
 * Method: getRouting
 * Called when both the "Routing" markers have both been moved and adjusted.
 *     Launches a getRoute request with the location of both markers.  The 
 *     "callback" function is called when the query has finished.
 *
 * Parameters:
 * szStart  - {String} A 'lat,lng' string of the starting point
 * szEnd    - {String} A 'lat,lng' string of the ending point
 * callback - {function} The function to call when the query has finished
 * options {Object} An object of options used to force a specific parameters
 *                  when necessary.
 */
fcmqApplication.prototype.getRouting = function(
    szStart, szEnd, callback, options) {
    this.setPleaseWait("pwGetRouting");

    options = options || {};
    var szURL = this.pg2geojsonURL;

    var oParams = {
        'request': 'GetRoute',
        'start': szStart,
        'end': szEnd,
        'layer':  this.layer,
        'level':  this.currentDataLevel,
        'encode':  this.encode,
        'random': Math.random()
    };

    oParams = OpenLayers.Util.applyDefaults(options, oParams);

    szURL += OpenLayers.Util.getParameterString(oParams);

    GDownloadUrl(szURL, callback);
};

/**
 * Method: clearTempOverlays
 * Called when the map zooms to a new data level.  Remove all temporary overlays
 *     (overlays that are simplified) in order to get new ones.
 */
fcmqApplication.prototype.clearTempOverlays = function() {
    for(var i=0; i<this.tempOverlays.length; i++) {
        this.map.removeOverlay(this.tempOverlays[i]);
    }
    // todo: destroy the objects ?
    this.tempOverlays = [];
};

// #############################################################
// TempOverlay events functions && Distance Calculator functions
// #############################################################

/**
 * Method: onMapInfoWindowOpen
 * Called when a InfoWindow is opened on the map.  Used to check if the opened
 *     window is from a tempOverlay.  If it's not, set a flag to notice that the
 *     window is from an other overlay.
 */
fcmqApplication.prototype.onMapInfoWindowOpen = function() {
    if (!this.tempOverlayInfoWindow) {
        this.otherOverlayInfoWindow = true;
    }
};

/**
 * Method: onMapInfoWindowClose
 * Called when a InforWindow is closed.  Reset InfoWindow flags.
 */
fcmqApplication.prototype.onMapInfoWindowClose = function() {
    this.tempOverlayInfoWindow = false;
    this.otherOverlayInfoWindow = false;
};

/**
 * Method: addMapMouseMoveListener
 * Adds a "mousemove" listener binded to the map.
 */
fcmqApplication.prototype.addMapMouseMoveListener = function() {
    if(!this.mapMouseMove) {
        this.mapMouseMove = GEvent.addListener(this.map, "mousemove",
                                               this.onMapMouseMove);
    }
};

/**
 * Method: removeMapMouseMoveListener
 * Removes the "mousemove" listener currently binded to the map.
 */
fcmqApplication.prototype.removeMapMouseMoveListener = function() {
    if(this.mapMouseMove) {
        GEvent.removeListener(this.mapMouseMove);
        this.mapMouseMove = null;
    }
};

/**
 * Method: onMapMouseMove
 * Called when the mouse moves over the map.  Used to keep track of the mouse
 *     position.  The object is putted in a global variable so that anything
 *     can access it from anywhere.
 *
 * Also calls the moveTempOverlayPopup function.
 * 
 * Parameters:
 * oLatLng   - {<GLatLng>} The coordnates of the mouse
 */
fcmqApplication.prototype.onMapMouseMove = function(oLatLng) {
    var oMain = fcmq;

    if(!oMain.tempOverlayInfoWindow) {
        return;
    }

    oMain.moveTempOverlayPopup(oLatLng);

    if(oMain.debug) {
        document.getElementById('myLat').innerHTML = "";
        document.getElementById('myLat').innerHTML = oLatLng.lat();
        document.getElementById('myLng').innerHTML = "";
        document.getElementById('myLng').innerHTML = oLatLng.lng();
    }
};

/**
 * Method: onMapMouseMove
 * Given a specific GLatLng object, move the tempOverlay popup to the location
 *     with some offsets.
 * 
 * Parameters:
 * oLatLng   - {<GLatLng>}
 */
fcmqApplication.prototype.moveTempOverlayPopup = function(oLatLng) {
    var oPos = this.map.main.tempOverlayPopupOptions['offset'];
    var nXBorder = this.map.main.tempOverlayPopupOptions['xBorder'];
    var nYBorder = this.map.main.tempOverlayPopupOptions['yBorder'];

    var oPoint = this.map.fromLatLngToContainerPixel(oLatLng);

    var topDiv = document.getElementById('top');
    var nTopOffsetLeft = topDiv.offsetLeft;
    var nTopOffsetHeight = topDiv.offsetHeight;
    var nX = nTopOffsetLeft + oPoint.x;
    var nY = nTopOffsetHeight + oPoint.y;

    var oCenter = this.map.fromLatLngToContainerPixel(this.map.getCenter());
    var nMapHeight = this.map.getSize().height;
    var nMapWidth = this.map.getSize().width;

    var szMouseXPos;
    if(oPoint.x < nXBorder) {
        szMouseXPos = "left";
    } else if (oPoint.x + nXBorder > nMapWidth) {
        szMouseXPos = "right";
    } else {
        szMouseXPos = "center";
    }

    var szMouseYPos = (oPoint.y < oCenter.y ||
                       (oPoint.y + nYBorder < nMapHeight)) ? "top" : "bottom";

    nX += oPos[szMouseXPos];
    nY += oPos[szMouseYPos];
    
    this.tempOverlayPopup.style.top = nY + "px";
    this.tempOverlayPopup.style.left = nX + "px";
};


/**
 * Method: onTempOverlayMouseOver
 * Called when the mouse is over a tempOverlay.  If there's no 'other' 
 *     InfoWindow currently being displayed or if no routing marker is currently
 *     being dragged, highlight the tempOverlay and display an InfoWindow
 *     containing some info about the tempOverlay.
 */
fcmqApplication.prototype.onTempOverlayMouseOver = function(a,b,c) {
    var oMain = this.main;

    // currently do nothing while the maptype is Earth
    var oCurrentMapType = oMain.map.getCurrentMapType();
    if(oCurrentMapType == G_SATELLITE_3D_MAP) {
        return;
    }

    var bDraggingMarker = (oMain.routing && oMain.routing.draggingMarker);

    if(oMain.otherOverlayInfoWindow || bDraggingMarker){
        return;
    }

    var oOverlay = this.overlay;

    // highlight the overlay
    oOverlay.setStrokeStyle(oMain.hoverStyle);

    // get information from the overlay and put them in the according dom 
    // elements
    var oDistCalc = this.main.distanceCalculator;
    var oLang = lang[oMain.lang];

    var szStyle = oOverlay.properties[oMain.styleProperty];
    var szClass = oOverlay.properties[oMain.classProperty];
    var anNumber = oOverlay.properties[oMain.numberProperty];
    var szTitleId = "trail-"+szStyle+"-"+szClass;
    var szTitle = oLang[szTitleId];

    var fLength = oDistCalc.getOverlayLength(oOverlay);
    var fKM = oDistCalc.getKilometers(fLength);
    var fMiles = oDistCalc.getMiles(fLength);
    
    var szKM = oLang['Km'];
    var szMiles = oLang['Miles'];
    var szMsg = oLang['pleaseClickOnOverlay'];

    document.getElementById("tempOverlayPopupTitle").innerHTML = szTitle;
    document.getElementById("tempOverlayPopupKMValue").innerHTML = fKM;
    document.getElementById("tempOverlayPopupMilesValue").innerHTML = fMiles;

    // todo : add the shields depending on the "number" property
    //var oShieldsDOM = document.getElementById("tempOverlayPopupShields");
    var oTable = document.getElementById("tempOverlayPopupTable");

    oTable.deleteRow(0);
    var oRow = oTable.insertRow(0);

    // single cell
    var oCell = oRow.insertCell(0);
    oCell.colSpan = 2;
    oCell.id = "tempOverlayPopupShields";
    oMain.appendShieldsToElement(anNumber, oCell);

    // toggle information depending if the trail is closed or open
    var oWhenOpen = document.getElementById("tempOverlayPopupPleaseClick");
    var oWhenClosed = document.getElementById("tempOverlayPopupClosed");
    if(oMain.getTempOverlayClosed(oOverlay)) { // if trail is closed
        hideDiv(oWhenClosed, false);
        hideDiv(oWhenOpen, true);
    } else {
        hideDiv(oWhenClosed, true);
        if (oMain.enableDistanceCalculator) {
            hideDiv(oWhenOpen, false);
        } else {
            hideDiv(oWhenOpen, true);
        }
    }

    // show the div and flag the visibility
    oMain.tempOverlayPopup.style.display = "";
    oMain.tempOverlayInfoWindow = true;

    if(oMain.debug) {
        document.getElementById("myOverLine").innerHTML = "";
        document.getElementById("myOverLine").innerHTML = "in";
    }
};

/**
 * Method: appendShieldsToElement
 * From an array of numbers, create "img" DOM elements and append them to the
 *     given oElement DOM object.
 *
 * Parameters:
 * anNumber - {Array} Array of numbers used to get shield images.
 *
 * oElement - {DOMElement} The DOM element to append the created "img" elements
 *                         to.
 */
fcmqApplication.prototype.appendShieldsToElement = function(anNumber, oElement) {
    // for each found images :
    for(var i=0; i<anNumber.length; i++) {
        var nNumber = anNumber[i];

        if(!nNumber || nNumber == "") {
            continue;
        }
        var oImg = document.createElement("img");
        oImg.src = "./img/shield/"+nNumber+".png";

        if(nNumber > 99) {
            oImg.className = "RGShield";
        } else {
            oImg.className = "TQShield";
        }
        oElement.appendChild(oImg);
    }
};

/**
 * Method: onTempOverlayMouseOut
 * Called when the mouse cease to be over a tempOverlay.  Hide the popup and 
 *     unhighlight the overlay.  If the overaly had a "currentStyle" property
 *     set (from being selected or whatever), set to this style else use the
 *     defaultStyle property.
 */
fcmqApplication.prototype.onTempOverlayMouseOut = function() {
    var oMain = this.main;
    var oOverlay = this.overlay;

    // currently do nothing while the maptype is Earth
    var oCurrentMapType = oMain.map.getCurrentMapType();
    if(oCurrentMapType == G_SATELLITE_3D_MAP) {
        return;
    }

    if(oMain.tempOverlayInfoWindow){
        oMain.hideTempOverlayPopup();
        oOverlay.setStrokeStyle(oMain.getTempOverlayCurrentStyle(oOverlay));
    }
};

/**
 * Method: restoreAllTempOverlayStyles
 * Called when the mouse cease to be over a tempOverlay, but without knowing 
 *     which overlay.  Restore all tempOverlay current style and hide the
 *     popup.
 *
 * Used in "onmouseover" events directly in html tags in index.html and when
 *     the mouse gets out of the map (onMapMouseOut).
 */
fcmqApplication.prototype.restoreAllTempOverlayStyles = function() {
    if(this.tempOverlayInfoWindow){
        this.hideTempOverlayPopup();
        for(var i=0; i<this.tempOverlays.length; i++) {
            var oOverlay = this.tempOverlays[i];
            oOverlay.setStrokeStyle(this.getTempOverlayCurrentStyle(oOverlay));
        }
    }
};

/**
 * Method: hideTempOverlayPopup
 * Hide the tempOverlayPopup div.
 */
fcmqApplication.prototype.hideTempOverlayPopup = function() {
    //this.map.closeInfoWindow();
    this.tempOverlayPopup.style.display = "none";
    this.tempOverlayInfoWindow = false;

    if(this.debug) {
        document.getElementById("myOverLine").innerHTML = "";
        document.getElementById("myOverLine").innerHTML = "out";
    }
};

/**
 * Method: onMapClick
 * Called when the map is clicked.  If the click was over an overlay, select it.
 *     If the click was over nothing, unselect all tempOverlays currently
 *     selected (if resetSelectionOnClickOut is set to true).
 *
 * If an overlay is "closed", it can't be selected.
 *
 * Parameters:
 * oPoint   - {<GPoint>} A point on the my by its pixel coordinates.
 * oSrc     - {DOM} DOM element
 * oOverlay - {<GOverlay>} The overlay clicked
 */
fcmqApplication.prototype.onMapClick = function(
    oOverlay, oLatLng, oOverlayLatLng){

    // currently do nothing while the maptype is Earth
    var oCurrentMapType = this.map.getCurrentMapType();
    if(oCurrentMapType == G_SATELLITE_3D_MAP) {
        return;
    }

    if(oOverlay) {
        // if overlay is already selected
        if(OpenLayers.Util.indexOf(this.selectedTempOverlays, oOverlay) != -1 &&
           this.tempOverlayUnselectionIsValid()) {
            this.unselectTempOverlay(oOverlay);
        } else if(!this.getTempOverlayClosed(oOverlay)) {
            this.selectTempOverlay(oOverlay);
        }
    } else if(this.resetSelectionOnClickOut) {
        this.unselectAllTempOverlays();
    }
};

/**
 * Method: selectTempOverlay
 * If overlay is a tempOverlay and is not already selected, add it to the
 *     selectedTempOverlays array and change its style to the "select" style.
 *     Store the style in the "currentStyle" property of the overlay.
 *
 * Parameters:
 * oOverlay - {<GOverlay>} The overlay to select
 */
fcmqApplication.prototype.selectTempOverlay = function(oOverlay) {
    if(OpenLayers.Util.indexOf(this.tempOverlays,oOverlay) == -1) {
        return;
    }

    if(OpenLayers.Util.indexOf(this.selectedTempOverlays, oOverlay) != -1) {
        //alert("overlay already selected");
    } else {
        this.selectedTempOverlays.push(oOverlay);
        oOverlay.setStrokeStyle(this.selectStyle);
        oOverlay.currentStyle = this.selectStyle;
        GEvent.trigger(oOverlay, "overlayselected",
                       oOverlay, this.distanceCalculator);
    }
};

/**
 * Method: unselectTempOverlay
 * If overlay is a tempOverlay and is not already unselected, add it to the
 *     unselectedTempOverlays array and change its style to the "unselect"
 *     style. Store the style in the "currentStyle" property of the overlay.
 *
 * Parameters:
 * oOverlay - {<GOverlay>} The overlay to unselect
 */
fcmqApplication.prototype.unselectTempOverlay = function(oOverlay) {
    if(OpenLayers.Util.indexOf(this.tempOverlays,oOverlay) == -1) {
        return;
    }
    
    var nIndex = OpenLayers.Util.indexOf(this.selectedTempOverlays, oOverlay);
    if(nIndex != -1){
        this.selectedTempOverlays.splice(nIndex, 1);
        delete oOverlay.currentStyle;
        oOverlay.setStrokeStyle(this.getTempOverlayCurrentStyle(oOverlay));
        GEvent.trigger(oOverlay, "overlayunselected",
                       oOverlay, this.distanceCalculator);
   }
};

/**
 * Method: unselectAllTempOverlays
 * Unselects all tempOverlays, restoring their original style and removing them
 *     from the selectedTempOverlays array.
 */
fcmqApplication.prototype.unselectAllTempOverlays = function() {
    for(var i=0; i<this.selectedTempOverlays.length; i++) {
        var oOverlay = this.selectedTempOverlays[i];
        delete oOverlay.currentStyle;
        oOverlay.setStrokeStyle(this.getTempOverlayCurrentStyle(oOverlay));
    }
    this.selectedTempOverlays = [];
    GEvent.trigger(this.distanceCalculator, "alloverlaysunselected");
};

/**
 * Method: saveSelectedTempOverlayIds
 * Get all currently selected tempOverlays id and store them in an array.  Used
 *     for reselection after zooming and data level changes.
 */
fcmqApplication.prototype.saveSelectedTempOverlayIds = function() {
    for(var i=0; i<this.selectedTempOverlays.length; i++) {
        var oOverlay = this.selectedTempOverlays[i];
        this.lastSelectedTempOverlayIds.push(oOverlay.id);
    }
};

/**
 * Method: getLastSelectedTempOverlayIdsList
 * Returns a string of currently stored selected tempOverlay ids, separated by
 *     ','.
 * 
 * Returns:
 * {String} The list of selected tempOverlay ids.
 */
fcmqApplication.prototype.getLastSelectedTempOverlayIdsList = function() {
    var aIdList = [];

    for(var i=0; i<this.lastSelectedTempOverlayIds.length; i++) {
        aIdList.push(this.lastSelectedTempOverlayIds[i]);
    }

    var szIdList = aIdList.join(",");

    return szIdList;
};

/**
 * Method: getTempOverlayClosed
 * Check if the temp overlay is closed.
 *
 * Parameters:
 * oOverlay - {<GOverlay>} The overlay to check
 *
 * Returns:
 * {Boolean}
 */
fcmqApplication.prototype.getTempOverlayClosed = function(oOverlay) {
    return (parseInt(oOverlay.properties[this.classProperty]) > 4);
};

/**
 * Method: getValueField
 * Compare the last data level with current data level to find out which 'gid' 
 *     column to use for further reselection.
 * 
 * Returns:
 * {String} The value field
 */
fcmqApplication.prototype.getValueField = function() {
    var szValueField;
    var nLastLevel = this.tempOverlaysIdsDataLevelRelations[this.lastDataLevel];
    var nCurrentLevel = this.tempOverlaysIdsDataLevelRelations[this.currentDataLevel];

    if(nLastLevel == nCurrentLevel){
        szValueField = this.valueFieldFromLevel['same'];
    } else {
        szValueField = this.valueFieldFromLevel[nLastLevel];
    }

    return szValueField;
};


/**
 * Method: reselectLastSelectedTempOverlays
 * From the ids stored before zooming, get its corresponding overlay currently
 *     on the map and select it.
 */
fcmqApplication.prototype.reselectLastSelectedTempOverlays = function() {
    // if not ids where stored, no need to do anything.
    if(this.lastSelectedTempOverlayIds.length == 0) {
        return;
    }

    var nLastLevel = this.tempOverlaysIdsDataLevelRelations[this.lastDataLevel];
    var nCurrentLevel = this.tempOverlaysIdsDataLevelRelations[this.currentDataLevel];

    // only reselects if the user zoomed in or if we are on the same level
    if(nLastLevel && nCurrentLevel >= nLastLevel) {
        for(var i=0; i<this.tempOverlays.length; i++) {
            var oOverlay = this.tempOverlays[i];
            var nLastId;

            if(nLastLevel == nCurrentLevel) {
                nLastId = oOverlay[this['overlayId']];
            } else {
                nLastId = oOverlay.properties[this['idFromLevel'][nLastLevel]];
            }
        
            if(OpenLayers.Util.indexOf(
                   this.lastSelectedTempOverlayIds, nLastId) != -1) {
                this.selectTempOverlay(oOverlay);
            }
        }
    }

    this.lastSelectedTempOverlayIds = [];
};

/**
 * APIFunction: onMapTypeChanged
 * Triggered on map "maptypechanged".  This is a fix to remove all markers when
 * switching to the 3D map (in Windows only) because the markers become red X.
 *
 * Restore them when changing back to an other type of map.
 */
fcmqApplication.prototype.onMapTypeChanged = function() {
    var oCurrentMapType = this.getCurrentMapType();
    
    if(oCurrentMapType == G_SATELLITE_3D_MAP) {
        this.main.markerManager.clearMarkers();
    } else if (this.main.lastMapType == G_SATELLITE_3D_MAP) {
        // Shields (added to the MarkerManager)
        if (this.main.enableShieldLayer) {
            this.main.addShields(this.main.shieldsResponse);
        }

        // Services
        if (this.main.enableServiceLayer) {
            this.main.addServices(this.main.servicesResponse);
        }
    }

    this.main.lastMapType = oCurrentMapType;
};

// ####################
// PleaseWait functions
// ####################

/**
 * APIFunction: setPleaseWait
 * Shows the "pleaseWait' popup given a specific key from the current lang file
 *     to use.
 */
fcmqApplication.prototype.setPleaseWait = function(szLangKey) {
    if(this.pleaseWait) {
        setDomInnerHTML(this.pleaseWaitText, szLangKey);
        hideDiv(this.pleaseWaitDiv, false);
    }
};

/**
 * APIFunction: hidePleaseWait
 * Hides the "pleaseWait' popup.
 */
fcmqApplication.prototype.hidePleaseWait = function() {
    if(this.pleaseWait) {
        hideDiv(this.pleaseWaitDiv, true);
    }
};

// #####################
// UnselectKey functions
// #####################

/**
 * APIFunction: tempOverlayUnselectionIsValid
 * Called when a tempOverlay was clicked for unselection.  Checks if a key is
 *     needed to be pressed for that.
 *
 * The 'alt' key doesn't seem to work well for some reason.
 *
 * Returns:
 * {Boolean} The overlay can be unselected
 */
fcmqApplication.prototype.tempOverlayUnselectionIsValid = function() {
    var bValid = false;
    switch (this.unselectKey) {
      case "none":
        bValid = true;
        break;
      case "shift":
        bValid = Util.SHIFT_KEY_PRESSED;
        break;
      case "ctrl":
        bValid = Util.CTRL_KEY_PRESSED;
        break;
/*
      case "alt":
        bValid = Util.ALT_KEY_PRESSED;
        break;
*/
    }

    return bValid;
};

/* PRINT */
/*
fcmqApplication.prototype.adjustPrintBounds = function() {
    var oSize = this.map.getSize();
    
    var nLeft = oSize.width/2 - this.printPreviewWidth/2;
    var nTop = oSize.height/2 - this.printPreviewHeight/2;

    var oCentre = new GSize(nLeft, nTop);
    var gPos = new GControlPosition(G_ANCHOR_TOP_LEFT, oCentre);

    gPos.apply(document.getElementById("myFloatyDiv"));
    this.map.getContainer().appendChild(document.getElementById("myFloatyDiv")); 
};
*/

