/*
 *
 *  GTToolkitClass - Handles the communication between the components of the Good Thing campaign element.
 *
 *  This class is primarily a event distribution handler for the components of the campaign element. It
 *  is designed to be fairly general purpose, and as such it contains no direct references to the components.
 *  All functions that need to know about the specifics of the campaign element DOM are contained in the
 *  GTCampaignElementHandlerClass.
 *
 *  Currently, the supported event types are:
 *
 *   HOME_LOCATION_CHANGED: Sent when the home location has been updated. The following data is provided:
 *          {            
 *            longitude: (decimal) e.g. 55.12321,
 *            latitude:  (decimal) e.g. 1.3231,
 *            city: (string) e.g. 'Stockholm',
 *            countryCode: (string) e.g. 'SE',
 *            countryName: (string) e.g. 'Sweden',
 *            region: (string) e.g. 'Stockholm'
 *          }
 *
 *   NEW_GOOD_THINGS_RECEIVED: Sent when new good things are received (following an initiateNewGoodThingsDownload
 *                             request). The following data is provided:
 *          [
 *           gt_1, gt_2, ..., gt_n
 *          ]
 *
 *          where gt_i is an object like 
 *
 *          {
 *		        title: (string) e.g. 'Doug's birthday party',
 *		        link: (string) e.g. 'http://goodthings.ovi.com/goodthing/321',
 *		        description: (string) e.g. 'The most splendid party ever!',
 *		        latitude: (decimal) e.g. 59.3333,
 *		        longitude: (decimal) e.g. 18.05,
 *		        pubDate: (date) e.g. (2009-10-01),
 *		        category: (string) e.g. 'EVENT',
 *		        guid: (string) e.g. '321',
 *		        author: (string) e.g. 'Barny',
 *              distanceFromHome: (decimal) e.g. 34.52 (kilometers from home position)
 *              directionFromHome: (decimal) e.g. 102.23 (initial direction from home position in degrees, 0 is north)
 *		    }
 *
 *
 *   FOCUS_ON_GOOD_THING: Sent when a request to focus on a specific good thing has been received. The following data
 *                        is provided:
 *
 *          guid: (string) e.g. '321'
 *
 *   SHOW_GOOD_THING_SUBSET: Sent when a component has selected a subset of the retrieved Good Things to show.
 *                           (Typically, the top flash component notifies the map of which GT:s to display.)
 *                           The following data is provided:
 *          [index_of_first_item_to_show, index_of_last_item_to show] e.g. [0, 9] (zero-based indexing)
 *
 *   MAP_CLICKED: Sent when the map received a mouse click. The following data is provided:
 *
 *          {
 *              latitude: (decimal) e.g. 59.3333
 *              longitude: (decimal) e.g. 18.05
 *              x: (decimal) e.g. 55 (pixel on the map component)
 *              y: (decimal) e.g. 33 (pixel on the map component)
 *          }
 *
 *   SEARCH_RESULT_RECEIVED: Sent when new address search result have been received (following a initiatSearch request). The following data is provided:
 *
 *          [
 *           addr_1, addr_2, ..., addr_n
 *          ]
 *
 *          {
 *               title:String
 *               latititude:String
 *               longitude:String
 *               thetype:String
 *               country:String
 *               state:String
 *               district:String
 *               city:String
 *               street:String
 *               housenr:String
 *               housealpha:String
 *               iconid:String
 *          }
 *
 *   CONTAINER_VIEW_CHANGED: Sent when the container view has changed. The following data is provided:
 *
 *      containerid:string
 *
 *
 *   CRANE_VIEW_CHANGED: Sent when the crane view has changed. The following data is provided:
 *
 *      craneid:string
 *
 *
 *   CRANE_LOG_UPDATE_RECEIVED: Sent when the crane log has been received. The following data is provided:
 *
 *          [
 *           gt_1, gt_2, ..., gt_n
 *          ]
 *
 *          where gt_i is an object like
 *
 *          {
 *		        title: (string) e.g. 'Doug's birthday party',
 *		        link: (string) e.g. 'http://goodthings.ovi.com/goodthing/321',
 *		        description: (string) e.g. 'The most splendid party ever!',
 *		        latitude: (decimal) e.g. 59.3333,
 *		        longitude: (decimal) e.g. 18.05,
 *		        pubDate: (date) e.g. (2009-10-01),
 *		        category: (string) e.g. 'EVENT',
 *		        guid: (string) e.g. '321',
 *		        author: (string) e.g. 'Barny',
 *              distanceFromHome: (decimal) e.g. 34.52 (kilometers from home position)
 *              directionFromHome: (decimal) e.g. 102.23 (initial direction from home position in degrees, 0 is north)
 *		    }
 *
 */
function GTToolkitClass() {
    var me = this;
    var MAX_RANDOM_ITEMS = 20;
    var RANDOM_SELECTION_SIZE = 350;
    var BIG_CHUNK_OF_SPACE = 1000 * 50000;
    var SMALL_CHUNK_OF_SPACE = 1000 * 100;
    var CRANE_ID_WARSAW = "London";
    var CRANE_ID_LONDON = "Warsaw";
    var CRANE_STATE_WARSAW = "";
    var CRANE_STATE_LONDON = "";
    var CRANE_LOG_URL_WARSAW = "";
    var CRANE_LOG_URL_LONDON = "";
    var CRANE_STATUS_URL_LONDON = "";
    var CRANE_STATUS_URL_WARSAW = "";
    var CRANE_WARSAW_LATLONG = [];
    var CRANE_LONDON_LATLONG = [];
    var BASE_FEED_URL = "";
    var BASE_LATEST_FEED_URL = "";
    var BASE_SEARCH_URL = "";
    var GEO_IP_SERVICE = "";
    var USE_GEO_IP = -1;
    var startLong = 0;
    var startLat = 0;
    var VIDEO_PLAYER_PATH = "";
    var BASE_OVI_SERVICE_URL = "http://maps.ovi.com/services";
    var OVI_ADD_GT_SUFFIX = "/"
    var OVI_TRACKING_ID = "";
    var OVI_SERVICE_IN_NATIVE_LANG = "1";
    var STARTVIEW = 0;
    var LARGEVIDEO = "";
    var VIDEO_ENDTEXT= "";
    var VIDEO_AGAINTEXT = "";
    var VIDEO_LARGETEXT = "";
    var VIDEO_CUEPOINTT = "";
    var INIT_FILES_READ = 0;
    var CURRENT_CRANE_ID = CRANE_ID_WARSAW;
    var CURRENT_CONTAINER_MODE = 0;
    var commsHandler = null;
    var homeLocation = null;
    var currentFeedParams = null;
    var NO_FLASH_IMAGE = "";
    var FLASH_DOWNLOAD_URL = "";
    var DISCLAIMER_URL = "";
    var expanded = false;

    var FARFAR_AWAY_MAX_DIST_LON = 4000; // base/2 of the square box used in far-far away search (kilometers), should be BIG!
    var FARFAR_AWAY_MAX_DIST_LAT = 30000; // base/2 of the square box used in far-far away search (kilometers), should be BIG!
    var NEARBY_MAX_DIST = 20; // base/2 of the square box used in nearby search (kilometers)

    var GTCOMMSHANDLERSTARTED = false;
    var TOP_CONTAINER_STARTED = false;
    var BOTTOM_CONTAINER_STARTED = false;
    var CAMPAIGN_ELEM_HANDLER_STARTED = false;
    var copyResources = new Array(600);
    // Event identifiers
    var allEventIds = {
        HOME_LOCATION_CHANGED: 100,    // Callbackname : homeLocationChanged
        NEW_GOOD_THINGS_RECEIVED: 101, // Callbackname : newGoodThingsReceived
        FOCUS_ON_GOOD_THING: 102,      // Callbackname : focusOnGoodThing
        SHOW_GOOD_THING_SUBSET: 103,   // Callbackname : showGoodThingSubset
        MAP_CLICKED: 104,              // Callbackname : mapClicked
        SEARCH_RESULT_RECEIVED: 105,   // Callbackname : searchResultReceived
        CONTAINER_VIEW_CHANGED: 106,   // Callbackname : containerViewChanged
        CRANE_VIEW_CHANGED: 107,       // Callbackname : craneViewChanged
        CRANE_LOG_UPDATE_RECEIVED: 108,// Callbackname : craneLogUpdateReceived
        CRANE_STATUS_RECEIVED: 109,    // Callbackname : craneStatusReceived
        CLOSE_INTRO_AND_EXPAND: 110,    // Callbackname : closeIntroAndExpand
        GOOD_THINGS_DOWNLOAD_INITIATED: 111, // Callbackname : goodThingsDownloadInitiated
        SHOW_NAV_TO_OVI_BOX: 112,      // Callbackname : showNavToOviBox
        GOOD_THINGS_FEED_DESCRIPTION: 113, // Callbackname : goodThingsFeedDescription
        FOCUS_ON_GOOD_THING_ARROW: 114      // Callbackname : focusOnGoodThing, hack for dispatching the "same" evets to two receivers in the same flash...

    };

    var listenersMap = {};
    for (var ident in allEventIds) {
        listenersMap[allEventIds[ident]] = [];
        me[ident] = allEventIds[ident];
    }

    /*
     * Subscribe to a specific event.
     *
     * eventId: The ID (int) or string literal (string) of the event to subscribe to
     * listenerObject: The object that will receive event notifications. Accepted inputs
     *                 are javascript objects and object ids (string) of flash components.
     *
     */
    this.addListener = function(eventId, listenerObject) {
        me.showError("Adding listener " + listenerObject.toString() + " for event " + eventId);

        var numId = me[eventId];
        if (numId == null) {
        	numId = eventId;
        }
        me.showError("EventId of event " + eventId + " is " + numId);

        var listeners = listenersMap[numId];
        if (listeners == null) {
            this.showError('gtToolkit.addListener received undefined event id: ' + numId);
            return; // This will only happen on erroneous event id!
        }

        if (flashReference(listenerObject) != null) {
            listeners.push(flashReference(listenerObject));
        } else {
            listeners.push(listenerObject);
        }
    }

    /*
     * Initiate the download of new good things.
     *
     * location: Either through textual representation of country/city or via position and range:
     *    {
     *      longitude: (decimal),
     *      latitude: (decimal),
     *      range: (decimal, in meters),
     *    }
     *      or
     *    {
     *      city: (string),
     *      street: (string),
     *      state: (string),
     *      postCode: (string),
     *      countryCode: (string)
     *    }
     *      or
     *    {
     *      longitude: (decimal),
     *      latitude: (decimal),
     *      base: (decimal, in kilometers),
     *      extension: (decimal, in kilometers),
     *      direction: (string, "N", "S", "E", or "W"),
     *    }
     *
     *  categoryId: The id of the category of GTs that is of interest
     */
    this.initiateNewGoodThingsDownload = function(location, categoryId, text, maxItems, page) {
        // TODO: make sure the parameter names are compatible with the Ovi interface
//        $("body").css("cursor", "progress");
        var params = [];
        var theUrl = BASE_FEED_URL;
        if (location != null) {
            currentFeedParams = location;
        } else {
            location = currentFeedParams;
        }
        var randomSelection = false;
        if (location) {
            if (location['random']) {
                // Ignore all other location params, and generate a random set from a large collection of the latest feed.
                me.showError("Getting a random selection of GTs");
                randomSelection = true;
            }

            if (location['direction']) {
                var lon = location['longitude'];
                var lat = location['latitude'];
                var base = location['base'];
                var extension = location['extension'];
                var direction = location['direction'];
                var directionBox = me.getDirectionBox(lat, lon, base, extension, direction, false);
                params.push('minLatitude=' + encodeURIComponent(directionBox['minLat']));
                params.push('maxLatitude=' + encodeURIComponent(directionBox['maxLat']));
                params.push('minLongitude=' + encodeURIComponent(directionBox['minLon']));
                params.push('maxLongitude=' + encodeURIComponent(directionBox['maxLon']));
            } else if (location['farfaraway']) {
                var direction = "E";
                var extension = FARFAR_AWAY_MAX_DIST_LAT;
                var base = FARFAR_AWAY_MAX_DIST_LON;
                var lat, lon;
                if (location['latitude']) {
                    lat = location['latitude'];
                } else {
                    lat = me.getHomeLocation().latitude;
                }
                if (location['longitude']) {
                    lon = location['longitude'];
                } else {
                    lon = me.getHomeLocation().longitude;
                }
                var opposite = getOppositePosition(lat, lon);
                var directionBox = me.getDirectionBox(opposite.latitude, opposite.longitude, base, extension, direction, true);
                params.push('minLatitude=' + encodeURIComponent(directionBox['minLat']));
                params.push('maxLatitude=' + encodeURIComponent(directionBox['maxLat']));
                params.push('minLongitude=' + encodeURIComponent(directionBox['minLon']));
                params.push('maxLongitude=' + encodeURIComponent(directionBox['maxLon']));
            } else if (location['nearby']) {
                var lat, lon, range;
                if (location['latitude']) {
                    lat = location['latitude'];
                } else {
                    lat = me.getHomeLocation().latitude;
                }
                if (location['longitude']) {
                    lon = location['longitude'];
                } else {
                    lon = me.getHomeLocation().longitude;
                }
                if (location['range']) {
                    range = location['range']/1000.0;
                } else {
                    range = NEARBY_MAX_DIST;
                }
                var directionBox = me.getDirectionBox(lat, lon, range, range, "E", true);
                params.push('minLatitude=' + encodeURIComponent(directionBox['minLat']));
                params.push('maxLatitude=' + encodeURIComponent(directionBox['maxLat']));
                params.push('minLongitude=' + encodeURIComponent(directionBox['minLon']));
                params.push('maxLongitude=' + encodeURIComponent(directionBox['maxLon']));
            } else {
                if (location['countryCode']) {
                    params.push('country=' + encodeURIComponent(location['countryCode']));
                }
                if (location['postCode']) {
                    params.push('postCode=' + encodeURIComponent(location['postCode']));
                }
                if (location['state']) {
                    params.push('state=' + encodeURIComponent(location['state']));
                }
                if (location['street']) {
                    params.push('street=' + encodeURIComponent(location['street']));
                }
                if (location['city']) {
                    params.push('city=' + encodeURIComponent(location['city']));
                }
                if (location['longitude']) {
                    params.push('longitude=' + encodeURIComponent(location['longitude']));
                }
                if (location['latitude']) {
                    params.push('latitude=' + encodeURIComponent(location['latitude']));
                }
                if (location['range']) {
                    params.push('range=' + encodeURIComponent(location['range']));
                }
            }
        }

        if (params.length == 0 || randomSelection) {
            theUrl = BASE_LATEST_FEED_URL;
        }
        var callback = 'gtToolkit.receiveNewGoodThings';
        if (randomSelection) {
            params = [];
            params.push('pageSize=' + RANDOM_SELECTION_SIZE);
            callback = 'gtToolkit.receiveNewGoodThingsAndRandomize';
        } else{
            if (maxItems) {
                params.push('pageSize=' + encodeURIComponent(maxItems));
            } else {
                params.push('pageSize=' + encodeURIComponent(20));
            }

            if (page) {
                params.push('pageNumber='+ encodeURIComponent(page));
            }
        }

        var paramString = params.join('&');
        var combineStr = '?';
        if (theUrl.indexOf('?') >= 0) {
            combineStr = '&';
        }
        me.showError("Initiate download from " + theUrl +
                                   (paramString.length > 0 ? (combineStr + paramString ) : '') );
        commsHandler.getGoodThings(theUrl +
                                   (paramString.length > 0 ? (combineStr+ paramString ) : ''),
                                   callback);
        
        me.goodThingsDownloadInitiated();
    }

    /*
     * Initiate the download of new good things.
     *
     * location: :
     *    {
     *      longitude: (decimal),
     *      latitude: (decimal),
     *      query: (searched address as string),
     *    }
     *
     */
    this.initiateSearch = function(query) {
        var location = gtToolkit.getHomeLocation();
        // TODO: make sure the parameter names are compatible with the Ovi interface
        var params = [];

        if (location) {

            if (location['longitude']) {
                params.push('lon=' + encodeURIComponent(location['longitude']));
            }
            if (location['latitude']) {
                params.push('lat=' + encodeURIComponent(location['latitude']));
            }

        }
        if (query) {
                params.push('q=' + encodeURIComponent(query));
            }

        var paramString = params.join('&');
        commsHandler.doSearch(BASE_SEARCH_URL +
                                   (paramString.length > 0 ? ("&" +paramString ) : ''),
                                   'gtToolkit.receiveSearchResult');
    }

    this.getGeoIP = function() {
        me.showError("init geo ip from " + GEO_IP_SERVICE) ;
        commsHandler.getGeoIP(GEO_IP_SERVICE,"gtToolkit.geoIpReceived");
        //commsHandler.getGeoIP("http://qa.goodthings.ovim.gate5.de/services/iplookup/get","gtToolkit.geoIpReceived");
    }

    this.setGtCommsHandlerStarted = function() {
        me.showError("GtCommsHandler Started");
        me.GTCOMMSHANDLERSTARTED = true;
    }

    this.setTopContainerStarted = function() {
        me.showError("TopContainer Started");
        TOP_CONTAINER_STARTED = true;
    }

     this.setBottomContainerStarted = function() {
        me.showError("BottomContainer Started");
        BOTTOM_CONTAINER_STARTED = true;
    }

     this.setCampaignElemHandlerStarted = function() {
        me.showError("CampaignElemHandler Started");
        CAMPAIGN_ELEM_HANDLER_STARTED = true;
    }

    /*
    * Initiate an update of the specified crane log
    *
    *     craneid : 0..1
     */
    this.updateCraneLog = function(craneid) {
        var url;
        if (typeof craneid == 'undefined'){
            craneid = CURRENT_CRANE_ID;
        } else {
            CURRENT_CRANE_ID = craneid;
        }
        me.showError("UpdateCraneLog called for crane " + craneid);

        if (craneid == CRANE_ID_WARSAW) {
            url = CRANE_LOG_URL_WARSAW;
        } else {
            url = CRANE_LOG_URL_LONDON;
        }
        me.showError("Issuing request to " + url);
        commsHandler.getCraneLog(url, 'gtToolkit.receiveCraneLogUpdate');
    }

    /*
     * Initiate a request for the status of a specific crane
     *
     *     craneid : 0..1
     */
    this.getCraneStatus = function(craneid) {
        var url;
        me.showError("GetCraneStatus called for crane " + craneid);

        if (craneid == CRANE_ID_WARSAW) {
            url = CRANE_STATUS_URL_WARSAW;
        } else {
            url = CRANE_STATUS_URL_LONDON;
        }
        me.showError("Issuing request to " + url);
        commsHandler.getCraneStatus(url, 'gtToolkit.receiveCraneStatusUpdate');
    }

    /*
     * Get the current list of Good Things. See the docs for the NEW_GOOD_THINGS_RECEIVED event for
     * specifications of the return value.
     */
    this.getCurrentGoodThings = function() {
        return currentGoodThings;
    }

     /*
     * Get the current list of Good Things. See the docs for the NEW_GOOD_THINGS_RECEIVED event for
     * specifications of the return value.
     */
    this.getCurrentCraneLog = function() {
        return currentCraneLog;
    }

    /*
     * Get the current list of Good Things. See the docs for the NEW_GOOD_THINGS_RECEIVED event for
     * specifications of the return value.
     */
    this.getCopyResources = function() {
        return copyResources;
    }

    /*
     * Get the current home location. See the docs for the HOME_LOCATION_CHANGED event for specifications
     * of the return value.
     */
    this.getHomeLocation = function() {
        return me.homeLocation;
    }

    this.hasHomeLocation = function() {
        if (typeof me.homeLocation != 'undefined' && me.homeLocation != null
                && typeof me.homeLocation.latitude  != 'undefined' && typeof me.homeLocation.longitude  != 'undefined') {
            return true;
        }
        else {
            return false;
        }
    }


    /*
     * Set the current home location.
     *
     * newHomeLocation: See the docs for the HOME_LOCATION_CHANGED event for specifications of the parameter.
     *
     */
    this.setHomeLocation = function(newHomeLocation) {
        // TODO: do parameter verification and geo coding / reverse geo coding here depending on the given parameters?
        me.homeLocation = newHomeLocation;
        this.goToHomeLocation();
    }

    this.initiateHomeLocation = function(newHomeLocation) {
        me.homeLocation = newHomeLocation;
        me.showError("Home location initially set to " + me.homeLocation.latitude + " " + me.homeLocation.longitude);
    }

    /*
     * Send the HOME_LOCATION_CHANGED event with the current location.
     *
     * Parameters: cancelNewGTPush (boolean, default false)
     */
    this.goToHomeLocation = function(cancelNewGTPush) {
        if (currentFeedParams['direction']) {
            me.initiateNewGoodThingsDownload({direction:true, latitude: this.getHomeLocation().latitude, longitude: this.getHomeLocation().longitude, base: currentFeedParams['base'], extension:currentFeedParams['extension'], direction: currentFeedParams['direction']});
        } else if (currentFeedParams['nearby'] && !(currentFeedParams['latitude'] && currentFeedParams['longitude'])) {
            me.initiateNewGoodThingsDownload({nearby:currentFeedParams['nearby']});
        }  else {
            if (typeof currentGoodThings != 'undefined' && currentGoodThings != null && currentGoodThings.length > 0 &&
                (cancelNewGTPush == 'undefined' || cancelNewGTPush == null || cancelNewGTPush == false)) {
                this.receiveNewGoodThings(currentGoodThings);
            }
        }
        this.homeLocationChanged(this.getHomeLocation());
    }

    /*
     * Send the SHOW_GOOD_THING_SUBSET event.
     *
     *  interval: See the event specification for details
     *
     */
    this.setGoodThingSubsetToShow = function(interval) {
        this.showGoodThingSubset(interval);
    }

    /*
     * Send the FOCUS_ON_GOOD_THING event.
     *
     * guid: Id of the good thing (string)
     *
     */
    this.setFocusOnGoodThing = function(guid) {
        me.showError("Issue focusOnGoodThingArrow event");
        this.focusOnGoodThingArrow(guid);
        me.showError("Issue focusOnGoodThing event");        
        this.focusOnGoodThing(guid);
    }

    /*
     * Send the CRANE_VIEW_CHANGED event.
     *
     * craneid: Id of the crane (string)
     *
     */
    this.changeCraneView = function(craneid) {
        if (typeof craneid == 'undefined'){
            craneid = CURRENT_CRANE_ID;
        } else {
            CURRENT_CRANE_ID = craneid;
        }
        this.craneViewChanged(craneid);
    }

    /*
     * Returns the id of the current (or otherwise default read from config) crane. (string)
     */
    this.getCurrentCraneId = function() {
        return CURRENT_CRANE_ID;
    }

    this.getAddGoodThingUrl = function() {
        return BASE_OVI_SERVICE_URL + OVI_ADD_GT_SUFFIX;
    }

    /*
     * Send the CONTAINER_VIEW_CHANGED event.
     *
     * containerid: Id of the container (int)
     *
     */
    this.changeContainerView = function(containerid) {
        CURRENT_CONTAINER_MODE = containerid;
        this.containerViewChanged(containerid);
    }

    this.getCurrentContainerView = function() {
        return CURRENT_CONTAINER_MODE;
    }

    /*
     * Send the GOOD_THINGS_FEED_DESCRIPTION event
     */
    this.updateGoodThingsFeedDescription = function(description) {
        this.goodThingsFeedDescription(description);
    }

    /*
     * Send the SHOW_NAV_TO_OVI_BOX event
     */
    this.sendShowNavToOviBox = function() {
        this.showNavToOviBox();
    }

    /*
     * Initialize the gtToolkit object.
     *
     * gtCommsHandler: (string) Name of the flash object that implements the getGoodThings function
     *
     */
    this.initialize = function(gtCommsHandlerId) {
        commsHandler = flashReference(gtCommsHandlerId);
        /*
        homeLocation = {
            longitude: parseFloat(geoip_longitude()),
            latitude:  parseFloat(geoip_latitude()),
            city: geoip_city(),
            countryCode: geoip_country_code(),
            countryName: geoip_country_name(),
            region: geoip_region_name()
        };
        */
    }

    /*
     * Returns true if the object has been initialized with a valid communications handler object
     */
    this.initialized = function() {
        return (commsHandler != null && me.GTCOMMSHANDLERSTARTED);
    }

    this.isTopContainerStarted = function() {
      return TOP_CONTAINER_STARTED;
    }

    this.isBottomContainerStarted = function() {
      return BOTTOM_CONTAINER_STARTED;
    }

    this.isCampaignElemHandlerStarted = function() {
      return CAMPAIGN_ELEM_HANDLER_STARTED;
    }

    /*
     *  Display error messages.
     */
    this.showError = function(error) {
//        alert(error);
        if (typeof console != 'undefined') {
            console.log(error);
        } 
    }

    this.hasListener = function(eventType) {
        if (listenersMap != null && listenersMap[eventType] != null && listenersMap[eventType].length > 0) {
            return true;
        } else {
            return false;
        }
    }
    
    ////////////////////////////////////////////////////////////////////////
    // Event propagation functions
    ////////////////////////////////////////////////////////////////////////

    this.homeLocationChanged = function(newLocation) {
        var listeners = listenersMap[me.HOME_LOCATION_CHANGED];
        for (var i = 0; i < listeners.length; i++) {
            var listener = listeners[i];
            try {
                listener.homeLocationChanged(newLocation);
            } catch (e) { this.showError(e); }
        }
    }

    this.newGoodThingsReceived = function(listOfGts) {
        var listeners = listenersMap[me.NEW_GOOD_THINGS_RECEIVED];
        if (listeners.length == 0) {
            me.showError("newGoodThingsReceived, but no-one is listening...");
        }
        for (var i = 0; i < listeners.length; i++) {
            var listener = listeners[i];
            try {
                listener.newGoodThingsReceived(listOfGts);
            } catch (e) { this.showError(e); }
        }
    }

    this.searchResultReceived = function(listOfAddresses) {
        var listeners = listenersMap[me.SEARCH_RESULT_RECEIVED];
        for (var i = 0; i < listeners.length; i++) {
            var listener = listeners[i];
            try {
                listener.searchResultReceived(listOfAddresses);
            } catch (e) { this.showError(e); }
        }
    }


    this.focusOnGoodThing = function(guidOfGt) {
        var listeners = listenersMap[me.FOCUS_ON_GOOD_THING];
        for (var i = 0; i < listeners.length; i++) {
            var listener = listeners[i];
            try {
                me.showError("Issuing FOCUSONGT event to " + listener.toString());                
                listener.focusOnGoodThing(guidOfGt);
            } catch (e) { this.showError(e); }
        }
    }

    this.focusOnGoodThingArrow = function(guidOfGt) {
        var listeners = listenersMap[me.FOCUS_ON_GOOD_THING_ARROW];
        if (listeners.length == 0) {
            me.showError("Trying to issue FOCUSONGT_ARROW event, but no listeners!");
        }
        for (var i = 0; i < listeners.length; i++) {
            var listener = listeners[i];
            try {
                me.showError("Issuing FOCUSONGT_ARROW event to " + listener.toString());
                listener.focusOnGoodThingArrow(guidOfGt);
            } catch (e) { this.showError(e); }
        }
    }


    this.showGoodThingSubset = function(subsetIntervalBounds) {
        var listeners = listenersMap[me.SHOW_GOOD_THING_SUBSET];
        for (var i = 0; i < listeners.length; i++) {
            var listener = listeners[i];
            try {
                listener.showGoodThingSubset(subsetIntervalBounds);
            } catch (e) { this.showError(e); }
        }
    }

    this.craneLogUpdatetReceived = function(listOfGts) {
        var listeners = listenersMap[me.CRANE_LOG_UPDATE_RECEIVED];
        for (var i = 0; i < listeners.length; i++) {
            var listener = listeners[i];
            try {
                me.showError("Sending notification to " + listener.toString());                
                listener.craneLogUpdatetReceived(listOfGts);
            } catch (e) { this.showError(e); }
        }
    }

    this.craneStatusReceived = function(status) {
        var listeners = listenersMap[me.CRANE_STATUS_RECEIVED];
        for (var i = 0; i < listeners.length; i++) {
            var listener = listeners[i];
            try {
                me.showError("Sending notification to " + listener.toString());
                listener.craneStatusReceived(status);
            } catch (e) { this.showError(e); }
        }
    }

    this.closeIntroAndExpand = function(status) {
        if (!expanded) {
            var listeners = listenersMap[me.CLOSE_INTRO_AND_EXPAND];
            for (var i = 0; i < listeners.length; i++) {
                var listener = listeners[i];
                try {
                    me.showError("Sending closeIntroAndExpand to  " + listener.toString());
                    listener.closeIntroAndExpand();
                    expanded = true;
                } catch (e) { this.showError(e); }
            }
        }
    }

    this.mapClicked = function(location) {
        var listeners = listenersMap[me.MAP_CLICKED];
        for (var i = 0; i < listeners.length; i++) {
            var listener = listeners[i];
            try {
                listener.mapClicked(location);
            } catch (e) { this.showError(e); }
        }
    }

    this.containerViewChanged = function(containerid) {
        var listeners = listenersMap[me.CONTAINER_VIEW_CHANGED];
        for (var i = 0; i < listeners.length; i++) {
            var listener = listeners[i];
            try {
                listener.containerViewChanged(containerid);
            } catch (e) { this.showError(e); }
        }
    }

    this.craneViewChanged = function(craneid) {
        var listeners = listenersMap[me.CRANE_VIEW_CHANGED];
        for (var i = 0; i < listeners.length; i++) {
            var listener = listeners[i];
            try {
                listener.craneViewChanged(craneid);
            } catch (e) { this.showError(e); }
        }
    }

    this.goodThingsDownloadInitiated = function() {
        var listeners = listenersMap[me.GOOD_THINGS_DOWNLOAD_INITIATED];
        for (var i = 0; i < listeners.length; i++) {
            var listener = listeners[i];
            try {
                listener.goodThingsDownloadInitiated();
            } catch (e) { this.showError(e); }
        }
    }

    this.showNavToOviBox = function() {
        var listeners = listenersMap[me.SHOW_NAV_TO_OVI_BOX];
        for (var i = 0; i < listeners.length; i++) {
            var listener = listeners[i];
            try {
                listener.showNavToOviBox();
            } catch (e) { this.showError(e); }
        }
    }

    this.goodThingsFeedDescription = function(description) {
        var listeners = listenersMap[me.GOOD_THINGS_FEED_DESCRIPTION];
        for (var i = 0; i < listeners.length; i++) {
            var listener = listeners[i];
            try {
                listener.goodThingsFeedDescription(description);
            } catch (e) { this.showError(e); }
        }
    }

    this.parseXML = function(xml) {
        if (jQuery.browser.msie) {
            var xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
            xmlDoc.loadXML(xml);
            xml = xmlDoc;
        }
        return xml;
    }

     this.initGeoIPIfCommsAvail = function() {
         if (gtToolkit.initialized()) {
             gtToolkit.showError("Init request of GeoIP");
             gtToolkit.getGeoIP();
         } else {
             gtToolkit.showError("Re-issue download of geoIp");
             setTimeout(me.initGeoIPIfCommsAvail, 500);
         }
     }

    this.readLocalConfig = function(xml) {
        
        xml = me.parseXML(xml);
        STARTVIEW =  $(xml).find('startView').text();
        if (STARTVIEW == 1) {
            CURRENT_CRANE_ID = CRANE_ID_LONDON;
            CURRENT_CONTAINER_MODE = 1;
        } else if (STARTVIEW == 2) {
            CURRENT_CRANE_ID = CRANE_ID_WARSAW;
            CURRENT_CONTAINER_MODE = 2;
        } else {
            CURRENT_CONTAINER_MODE = 0;
        }
        USE_GEO_IP = parseInt($(xml).find('useGeoIp').text());
        me.showError("USE_GEO_IP : "  + USE_GEO_IP);
        startLong =  $(xml).find('startLong').text();
        startLat =  $(xml).find('startLat').text();
        if (USE_GEO_IP != 1) {
            me.initiateHomeLocation({
                longitude : parseFloat(startLong),
                latitude : parseFloat(startLat),
                city: "",
                countryCode: "",
                countryName: "",
                region: ""
            });
        }
        LARGEVIDEO =  $(xml).find('videoLargeUrl').text();
        VIDEO_ENDTEXT =  $(xml).find('videoLargeEndText').text();
        VIDEO_AGAINTEXT =  $(xml).find('videoLargeAgainText').text();
        VIDEO_LARGETEXT =  $(xml).find('videoLargeLargeText').text();
        VIDEO_CUEPOINTT =  $(xml).find('videoLargeCuePoint').text();
        BASE_OVI_SERVICE_URL = $(xml).find('oviMapsUrl').text();
        OVI_SERVICE_IN_NATIVE_LANG = $(xml).find('oviMapsInYourLanguage').text();
        DISCLAIMER_URL = $(xml).find('disclaimerUrl').text();
        FLASH_DOWNLOAD_URL = $(xml).find('flashDownloadUrl').text();
        NO_FLASH_IMAGE = $(xml).find('noflashImage').text();
        me.applyNoFlashTexts();
        INIT_FILES_READ += 1;
    }

    this.readCopyResources = function(xml) {
        me.showError("Parse copy resources");
        xml = me.parseXML(xml);
        $(xml).find("resource").each(function(){
            var id_text = $(this).find('id').text();
            var value_text = $(this).find('value').text();
            if (id_text != null && id_text.length > 0) {
                copyResources[id_text] = value_text;
            }
        });

        me.addDisclaimer(copyResources['CONTENT_SHOWN_ON_THIS_SITE_IS_SUPPLIED_BY_WEB_USERS_ON_OVI_MA'], DISCLAIMER_URL);

        me.applyCopyTexts();
    }

    this.readGlobalConfig = function(xml) {
        xml = me.parseXML(xml);
        BASE_FEED_URL = $(xml).find('oviGoodThingsServiceUrl').text();
        BASE_LATEST_FEED_URL = $(xml).find('oviGoodThingsLatestFeedUrl').text();
        BASE_SEARCH_URL = $(xml).find('oviMapsSearchUrl').text();
        GEO_IP_SERVICE = $(xml).find('oviGeoIPService').text();
        VIDEO_PLAYER_PATH = $(xml).find('videoplayerpath').text();
        OVI_TRACKING_ID = $(xml).find('trackingId').text();
        me.showError("Using geoip service: " + GEO_IP_SERVICE);
        gtToolkit.initGeoIPIfCommsAvail();


        $(xml).find('crane').each(function(){
            var id = $(this).attr('id');
                         if (id == "1") { // London
                             CRANE_STATE_LONDON = $(this).find('state').text();
                             me.showError("Crane state London: " + CRANE_STATE_LONDON);
                             CRANE_LOG_URL_LONDON = $(this).find('logFeedUrl').text();
                             CRANE_STATUS_URL_LONDON = $(this).find('statusUrl').text();
                             var lat = $(this).find('latitude').text();
                             var lon = $(this).find('longitude').text();
                             CRANE_LONDON_LATLONG = [lat,lon];
                         } else if ( id == "0") {  // Warsaw
                             CRANE_STATE_WARSAW = $(this).find('state').text();
                             me.showError("Crane state Warsaw: " + CRANE_STATE_WARSAW);

                             CRANE_LOG_URL_WARSAW = $(this).find('logFeedUrl').text();
                             CRANE_STATUS_URL_WARSAW = $(this).find('statusUrl').text();
                             var lat = $(this).find('latitude').text();
                             var lon = $(this).find('longitude').text();
                             CRANE_WARSAW_LATLONG = [lat,lon];
                         } else {
                             me.showError("Found another id : " + id);
                         }
                     });
        INIT_FILES_READ += 1;
    }

    var retryDelay = 0;
    this.initNewGTDownloadIfCommsAvail = function() {
        gtToolkit.showError("Testing if gt can be fetched...");
        var useConfiguredIP = false;
        if (gtToolkit.initialized() && gtToolkit.hasHomeLocation() && gtToolkit.hasListener(gtToolkit.NEW_GOOD_THINGS_RECEIVED) && gtToolkit.isTopContainerStarted()) {
            gtToolkit.showError("Issue download of items ");
            gtToolkit.initiateNewGoodThingsDownload();
        } else {
            if (retryDelay >= 3 && !gtToolkit.hasHomeLocation()) {
                me.showError("Setting Geo pos to default value due to slow GeoIP service...");
                me.initiateHomeLocation({
                    longitude : parseFloat(startLong),
                    latitude : parseFloat(startLat),
                    city: "",
                    countryCode: "",
                    countryName: "",
                    region: ""
                });
                useConfiguredIP = true;
            }
            if (! useConfiguredIP) {
                gtToolkit.showError("Re-issue download of items "  +gtToolkit.hasHomeLocation() +   gtToolkit.hasListener(gtToolkit.NEW_GOOD_THINGS_RECEIVED) + gtToolkit.isTopContainerStarted());
                retryDelay++;
                setTimeout(gtToolkit.initNewGTDownloadIfCommsAvail, retryDelay * 500);
            }  else {
                gtToolkit.showError("Issue download of items ");
                gtToolkit.initiateNewGoodThingsDownload();
            }
        }
    }

    this.addDisclaimer = function(text, theurl) {
        if (theurl.indexOf("?") >= 0) { theurl += "&lid=navigation_goodthings_displayad_giantarrows_disclaimer_0x0&lpos=goodthings_you_view"; }
        else { theurl += "?lid=navigation_goodthings_displayad_giantarrows_disclaimer_0x0&lpos=goodthings_you_view";}
        $("#gtdisclaimer").html("<a name='&lid=navigation_goodthings_displayad_giantarrows_disclaimer_0x0&lpos=goodthings_you_view' href='" + theurl + "'>" + text + "</a>");
    }

    this.applyCopyTexts = function() {
        me.showError("Applying copy texts");
        $('#usb_close').html(copyResources['CLOSE']);
        $('#usb_heading').html(copyResources['UNSUPPORTED_BROWSER']);
        $('#usb_disclaimer1').html(copyResources['THIS_SITE_IS_DESIGNED_TO_WORK_WITH_THE_FOLLOWING_BROWSERS_']);
        $('#usb_browserlist').html(copyResources['SAFARI__INTERNET_EXPLORER_7__PC___FIREFOX__GOOGLE_CHROME_']);
        $('#usb_disclaimer2').html(copyResources['YOU_MAY_OPEN_THE_SITE__BUT_RESULTS_MAY_BE_UNEXPECTED_ON_YOUR']);
    }

    this.applyNoFlashTexts = function() {
        if (FLASH_DOWNLOAD_URL.length > 0 && NO_FLASH_IMAGE.length > 0) {
            if (FLASH_DOWNLOAD_URL.indexOf("?") >= 0) { FLASH_DOWNLOAD_URL += "&lid=navigation_goodthings_displayad_giantarrows_getflash_0x0&lpos=goodthings_you_view"; }
            else { FLASH_DOWNLOAD_URL += "?lid=navigation_goodthings_displayad_giantarrows_getflash_0x0&lpos=goodthings_you_view";}
            $('#noflashContainer').html("<a name='&lid=navigation_goodthings_displayad_giantarrows_getflash_0x0&lpos=goodthings_you_view' href='" + FLASH_DOWNLOAD_URL + "' target='_blank'><img src='" + NO_FLASH_IMAGE + "' border='0' /></a>" ) ;
        }
    }

    this.applySeoPhrases= function() {
        // Get a BIG chunk of text from the copyresources and add it to the hidden div
        var seoStr = "";
        for (var key in copyResources) {
            seoStr += " " + copyResources[key];
        }
        $('#seoPhrases').html(seoStr);
        if ($.browser.msie && $.browser.version.substring(0,1) == 6) {
            $('#seoPhrases').hide(); // Fix for spurious IE6 showing of this div
        }

    }


    this.getLargeVideoSnippet = function() {
        var embedStr = "<embed id=\"nokia\" height=\"455\" width=\"756\" flashvars=\"video_path=" + LARGEVIDEO+"&end_txt=" + VIDEO_ENDTEXT+"&cue_point=" + VIDEO_CUEPOINTT+ "&again_txt="+ VIDEO_AGAINTEXT + "&large_txt="+ VIDEO_LARGETEXT + "&flv_width=756&flv_height=425&close_button_visible=true" + "\" allowscriptaccess=\"always\" base=\""+ this.getLargeVideoBase()+ "\"  quality=\"high\" wmode=\"transparent\" bgcolor=\"#ffffff\" name=\"nokia\" style=\"\" src=\"" + this.getVideoPlayerPath() + "\" type=\"application/x-shockwave-flash\"/>";
        return embedStr;
    }

    this.getLargeVideoUrl = function() {
        return LARGEVIDEO;
    }

    this.getLargeVideoBase = function() {
        return video_path;
    }


    this.getVideoPlayerPath = function() {
        return VIDEO_PLAYER_PATH;
    }


    this.getCraneLatLong = function(craneid) {
        if (craneid == CRANE_ID_LONDON) {
            return CRANE_LONDON_LATLONG;
        } else {
            return CRANE_WARSAW_LATLONG;
        }
    }
    
     this.getCraneState = function(craneid) {
        if (craneid == CRANE_ID_LONDON) {
            return CRANE_STATE_LONDON;
        } else {
            return CRANE_STATE_WARSAW;
        }
    }


    ////////////////////////////////////////////////////////////////////////
    // Helper functions
    ////////////////////////////////////////////////////////////////////////

    var currentGoodThings = [];
    var currentCraneLog = [];
    var currentCraneStatus = "";
    this.receiveNewGoodThings = function(listOfGts) {
//        $("body").css("cursor", "auto");
        me.showError("Received " + listOfGts.length);  
        applyDistanceFromHome(listOfGts);
        me.showError("Distance recalculated");

        currentGoodThings = listOfGts;
        me.newGoodThingsReceived(listOfGts);
    }

    this.receiveNewGoodThingsAndRandomize = function(listOfGts) {
//        $("body").css("cursor", "auto");
        me.showError("Received " + listOfGts.length + " to get a random selection of " + MAX_RANDOM_ITEMS + " from");
        listOfGts = me.makeRandomSelection(listOfGts, MAX_RANDOM_ITEMS);
        me.showError("Random selection made,  " + listOfGts.length + " items");        
        applyDistanceFromHome(listOfGts);
        me.showError("Distance recalculated");

        currentGoodThings = listOfGts;
        me.newGoodThingsReceived(listOfGts);
    }

    this.makeRandomSelection = function(listOfGts, maxItems){
        if (maxItems > listOfGts.size) {
            me.showError("List is too small to make a random selection from");
            return listOfGts;
        }
        var result = new Array();
        var hasNumber = new Array();
        var rand_no;
        for (x = 0; x < maxItems; x++)
        {
            rand_no = Math.floor(Math.random() * RANDOM_SELECTION_SIZE);
            if (!me.arrayContains(hasNumber, rand_no)) {
                me.showError("RandomNo:"+rand_no);
                result.push(listOfGts[rand_no]);
            }
        }
        return result;
    }

    this.arrayContains = function(arr, obj)  {
        var i = arr.length;
        while(i--) {
            if (arr[i] == obj) { return true; }
        }
        return false;
    }

    this.receiveSearchResult = function(listOfAddresses) {
        me.searchResultReceived(listOfAddresses);
    }

    this.geoIpReceived = function(data) {
        if (USE_GEO_IP < 1) {
            me.showError("Ignoring received location due to config");
            return;
        }
        me.showError("Received geoip data: " + data);
        if (data.length > 0 ) {
            var geoipstruct = eval('(' + data + ')');
            if (geoipstruct.result == "1") {
                var lon = geoipstruct.location.longitude;
                var lat = geoipstruct.location.latitude;
                me.setHomeLocation({
                    longitude : geoipstruct.location.longitude,
                    latitude : geoipstruct.location.latitude,
                    city: "",
                    countryCode: geoipstruct.location.isoCountryCode,
                    countryName: "",
                    region: ""
                });
            } else {
                var errors = geoipstruct.errors;
                me.showError("Failed to get GeoIP data. Reason: " + errors[0].message);
                me.showError("Falling back to configured location (" +startLat + "," +  startLong + ")");
                 me.setHomeLocation({
                    longitude :  parseFloat(startLong),
                    latitude : parseFloat(startLat),
                    city: "",
                    countryCode: "",
                    countryName: "",
                    region: ""
                });
            }
        }
    }

    this.receiveCraneLogUpdate = function(listOfGts) {
        applyDistanceFromHome(listOfGts);
        currentCraneLog = listOfGts;
        me.craneLogUpdatetReceived(listOfGts);
    }

    this.receiveCraneStatusUpdate = function(statusXml) {
        xml = me.parseXML(statusXml);
        me.showError("Crane status XML: " + xml);
        var status = {};

        status["status"] = $(xml).find("status").text();
        status["name"] = $(xml).find("name").text();
        status["id"] = $(xml).find("id").text();
        status["longitude"] = parseFloat($(xml).find("longitude").text());
        status["latitude"] = parseFloat($(xml).find("latitude").text());

        currentCraneStatus = status;
        me.showError("Crane status for " + status.name + ": " + status.status);
        me.craneStatusReceived(status);
    }

    this.showVideo = function () {
        $('#hiResFlash').html(me.getLargeVideoSnippet());
        $('#hiResFlashContainer').addClass('showvideo')  ;
        $('#hiResFlashContainer').removeClass('hidevideo')  ;
        $.scrollTo($('#hiResFlashContainer'), 800 );
    }

    function flashReference(movieName) {
        if (navigator.appName.indexOf("Microsoft") != -1) {
            return window[movieName];
        } else {
            return document[movieName];
        }
    }

    function applyDistanceFromHome(items) {
        var cur_lat = 0;
        var cur_lon = 0;
        if (me.hasHomeLocation()) {
            cur_lat = me.getHomeLocation().latitude;
            cur_lon = me.getHomeLocation().longitude;
        }
        for (var i = 0; i < items.length; i++) {
            var item = items[i];
            item.distanceFromHome = LatLon.distHaversine(cur_lat, cur_lon, item.latitude, item.longitude);
            item.directionFromHome = LatLon.bearing(cur_lat, cur_lon, item.latitude, item.longitude);

            if (typeof item.permaLink != 'undefined' && BASE_OVI_SERVICE_URL != null) {
                item.link = BASE_OVI_SERVICE_URL + item.permaLink + OVI_TRACKING_ID;
            }

        }
        me.showError("Done recalculating distance");
    }


    /*
     * Get a rectangle with base length of basesz, with the base centered on (lat, lon)
     * that extends extension in the direction of direction.
     *
     * Parameters:
     *     lat: latitude (float), e.g. 28.21
     *     lon: longitude (float), e.g. -33.12
     *     basesz: rectangle base length (float, kilometers), e.g. 5.0
     *     extension: rectangle extension length (float, kilometers), e.g. 500.0
     *     direction: rectangle extension direction (string: "N", "E", "S", or "W"), e.g. "N"
     *
     * Output:
     *     {
     *       minLat: <rectangle minimum latitude>,
     *       maxLat: <rectangle maximum latitude>,
     *       minLon: <rectangle minimum latitude>,
     *       maxLon: <rectangle maximum latitude>
     *     }
     *
     *
     *  Example with direction="E":
     *
     *                extension   (maxLat, maxLon)
     *          +----------------------*
     *          |                      |
     *          |                      |
     *          *(lat,lon)             | basesz
     *          |                      |
     *          |                      |
     *          *----------------------+
     *  (minLat, minLon)
     *
     */
    this.getDirectionBox = function(lat, lon, basesz, extension, direction, extendBothWays) {
        var minLat, maxLat, minLon, maxLon;

        var EXT_LIMIT = 38000;
        if (extension > EXT_LIMIT) {
            me.showError("Limiting extension to " + EXT_LIMIT + " km from " + extension + " km. (To avoid looping the earth too many times..");
            extension = EXT_LIMIT;
        }

        var base = new LatLon(lat, lon);
        var extDir;
        var baseA, baseB, extA, extB;
        if (direction == 'N' || direction == 'S') {
            baseA = extendWest(base, basesz/2);
            baseB = extendEast(base, basesz/2);
            if (extendBothWays) {
                extA = extendNorth(baseA, extension/2);
                extB = extendSouth(baseB, extension/2);
            } else {
                if (direction == 'N') {
                    extA = extendNorth(baseA, extension);
                    extB = extendNorth(baseB, extension);
                } else if (direction == 'S') {
                    extA = extendSouth(baseA, extension);
                    extB = extendSouth(baseB, extension);
                }
            }
        } else {
            baseA = extendNorth(base, basesz/2);
            baseB = extendSouth(base, basesz/2);
            if (extendBothWays) {
                extA = extendEast(baseA, extension/2);
                extB = extendWest(baseB, extension/2);
            } else {
                if (direction == 'E') {
                    extA = extendEast(baseA, extension);
                    extB = extendEast(baseB, extension);
                } else if (direction == 'W') {
                    extA = extendWest(baseA, extension);
                    extB = extendWest(baseB, extension);
                }
            }
        }
        me.showError("base: " + base);
        me.showError("baseA: " + baseA);
        me.showError("baseB: " + baseB);
        me.showError("extA: " + extA);
        me.showError("extB: " + extB);
        me.showError("base distance is: " + LatLon.distHaversine(baseA.lat, baseA.lon, baseB.lat, baseB.lon));
        me.showError("ext distance is: " + LatLon.distHaversine(baseA.lat, baseA.lon, extA.lat, extA.lon));
        me.showError("---------------------");
        minLat = Math.min(baseA.lat, baseB.lat, extA.lat, extB.lat);
        maxLat = Math.max(baseA.lat, baseB.lat, extA.lat, extB.lat);
        minLon = Math.min(baseA.lon, baseB.lon, extA.lon, extB.lon);
        maxLon = Math.max(baseA.lon, baseB.lon, extA.lon, extB.lon);

        return {minLat: minLat, maxLat: maxLat, minLon: minLon, maxLon: maxLon};
    }

    function extendNorth(point, distance) {
        var dst = point.destPoint(0, distance);

        // Check if we crossed the pole
        if (Math.abs(point.lon - dst.lon) > 10 || dst.lat < point.lat) {
            return new LatLon(89.9999, point.lon);
        }

        return dst;
    }
    function extendSouth(point, distance) {
        var dst = point.destPoint(180, distance);

        // Check if we crossed the pole
        if (Math.abs(point.lon - dst.lon) > 10 || dst.lat > point.lat) {
            return new LatLon(-89.9999, point.lon);
        }

        return dst;
    }
    function extendEast(point, distance) {
        var dst = point.destPoint(90, distance);
        dst.lat = point.lat;
        if (dst.lon >= 180 || dst.lon < point.lon) {
            dst.lon = 179.99;
        }

        return dst;
    }

    function extendWest(point, distance) {
        var dst = point.destPoint(270, distance);
        dst.lat = point.lat;
        if (dst.lon <= -180 || dst.lon > point.lon) {
            dst.lon = -179.99;
        }
        return dst;
    }

    function getOppositePosition(lat, lon) {
        var oppLat, oppLon;

        oppLat = -lat;
        oppLon = lon <= 0.0 ? (lon + 180.0) : (lon - 180.0);


        me.showError("opposite of (" + lat + ":" + lon + ") is (" + oppLat + ":" + oppLon + ")");
        return {latitude: oppLat, longitude: oppLon};
    }

    
};



var gtToolkit = new GTToolkitClass();

