﻿GMap2.prototype.addOverlays=function(a){ 
    for (i=0;i<a.length;i++) { 
            try { 
                    this.overlays.push(a[i]); 
                    a[i].initialize(this); 
                    a[i].redraw(true); 
            } catch(ex) { 
                    alert('err: ' + i + ', ' + ex.toString()); 
            } 
    } 
    this.reOrderOverlays(); 
}; 

// Large map (on homepage)
RXC_MAPTYPE_LARGE = 0;
// Smal map displaying a single job
RXC_MAPTYPE_SINGLE = 1;
// Map for saved ads
RXC_MAPTYPE_SAVED_ADS = 2;
// Map for branches from a particular agent
RXC_MAPTYPE_BRANCHES = 3;


var Jobs24Map = {

    // set this to true if you want to see debugging messages in the GLog
    enableDebugging: false,

    enableBoundary: false,

    // default latitude, longitude (to show the initial map)
    lat: 51.6,

    lng: -2.15,

    // default zoom level
    zoom: 7,

    // name of the map-container (div)
    mapName: "map",

    // GMap2 object
    map: null,

    icons: new Array(7),

    // width, height
    iconSizes: new Array(
        new GSize(12, 12), // job
        new GSize(23, 20), // school
        new GSize(17, 20), // parking
        new GSize(20, 20), // hotel
        new GSize(16, 20), // hospital
    //new GSize(19, 20), // airport
        new GSize(20, 24) // branch
    ),

    clusterIcons: new Array(7),

    clusterIconSizes: new Array(
        new GSize(16, 16), // job
        new GSize(34, 30), // school
        new GSize(25, 30), // parking
        new GSize(30, 30), // hotel
        new GSize(24, 30), // hospital
    // GSize(29, 30), // airport
        new GSize(24, 24) // branch    
    ),

    poiTypes: new Array('job', 'school', 'parking', 'hospital', 'hotel', /*'airport',*/'branch'),
    poiNames: new Array("Schools", 'Car Parks', "Hospitals", "Hotels", /*'Airports',*/"Branches"),

    poiPositions: new Array(
        new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(100, 73)),
        new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(7, 73)),
    //new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(7, 113)),
        new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(100, 33)),
        new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(7, 33))
    ),

    poiZooms: new Array(15, 15, 15, 15/*, 7*/),

    poiControls: new Array(4), //5
    poiInfos: new Array(4), //5  

    tooltip: null,

    // an info window is opened at the moment
    infoWindowOpen: false,

    // search parameters as a query string
    // might need to be split up into its components for display purposes
    searchParams: null,

    // mapBox can be set externally
    mapBox: null,

    // search is done for an area bigger than the map that we can physically see. 
    // the area for which we have results at the moment
    // type: GLatLngBounds
    outerBounds: null,

    markersCache: null,

    // here we store all jobs for JS-clustering
    jobCache: null,

    imageServer: null,

    locationText: '',

    mapType: RXC_MAPTYPE_LARGE,

    initialized: false,

    boundingBox: null,

    locationBounds: null,

    useBoundingBox: true,

    // locationBounds could be null
    load: function(sLocationBounds) {

        if (GBrowserIsCompatible()) {
            this.map = new GMap2($('#' + this.mapName)[0]);

            if (this.mapType == RXC_MAPTYPE_SINGLE) {
                this.loadControls();
            }

            this.map.addControl(new jobs24MapLoadingControl());
            this.map.addControl(new GMapTypeControl());

            this.setPoiControls();

            this.locationBounds = this.initializeLocation(sLocationBounds);

            this.setCenter();

            this.showBoundary();

            GEvent.bind(this.map, "click", this, this.debugLocation);
            GEvent.bind(this.map, "zoomend", this, this.onZoomEnd);
            GEvent.bind(this.map, "moveend", this, this.onMoveEnd);
            GEvent.bind(this.map, "move", this, this.onMove);
            GEvent.bind(this.map, "infowindowopen", this, this.onInfoWindowOpen);
            GEvent.bind(this.map, "infowindowclose", this, this.onInfoWindowClose);

            this.tooltip = $.create("div").get(0);
            this.map.getPane(G_MAP_FLOAT_PANE).appendChild(this.tooltip);

            this.tooltip.style.visibility = "hidden";

            this.markersCache = new Array();

            if (this.mapType == RXC_MAPTYPE_SAVED_ADS) {
                this.jobCache = new Array();
            }

            this.initializeIcons();
            if (this.useBoundingBox)
                this.showBoundingBox();
        }
    },

    initializeBoundingBox: function(bounds, backgroundcolor, borderColor, opacity) {
        if (this.boundingBox == null) {
            this.boundingBox = new BoundingBox(bounds, backgroundcolor, borderColor, opacity);
            this.map.addOverlay(this.boundingBox);
        }
    },

    showBoundingBox: function() {
        if (this.locationBounds != null)
            this.initializeBoundingBox(this.locationBounds, '#C4DBFF', '#FF8800', 30);
    },

    loadControls: function(nrClusters) {

        if (!this.initialized) {
            if (this.mapType == RXC_MAPTYPE_LARGE) {
                if (nrClusters > 0 || this.enableBoundary) {
                    this.map.enableDoubleClickZoom();
                    this.map.addControl(new GLargeMapControl());
                    this.map.addControl(new GOverviewMapControl());
                }
                else {
                    var control = new jobs24NoResultsControl();
                    var mapDimensions = this.map.getSize();
                    var offset = new GSize((mapDimensions.width - 230) / 2,
                                            (mapDimensions.height - 70) / 2);
                    this.map.addControl(control, new GControlPosition(G_ANCHOR_TOP_LEFT, offset));
                    this.map.disableDragging();
                }
            }
            else if (this.mapType == RXC_MAPTYPE_BRANCHES) {
                this.map.enableDoubleClickZoom();
                this.map.addControl(new GSmallMapControl());
            }
            else if (this.mapType == RXC_MAPTYPE_SAVED_ADS) {
                this.map.enableDoubleClickZoom();
                this.map.addControl(new GSmallMapControl());
            }
            else if (this.mapType == RXC_MAPTYPE_SINGLE) {
                this.map.enableDoubleClickZoom();
                this.map.addControl(new GSmallMapControl());
            }
            this.initialized = true;
        }
    },

    setPoiControls: function() {
        for (var i = 0; i < this.poiInfos.length; i++) {
            this.poiInfos[i] = new Jobs24PoiInfo(this.poiTypes[i + 1], this.poiZooms[i]);
            if (this.mapType == RXC_MAPTYPE_LARGE) {
                this.poiControls[i] = new Jobs24PoiControl(this.poiInfos[i], this.poiNames[i], this.poiPositions[i], this.map);
            }
        }
    },

    showPoiControls: function() {
        if (this.mapType == RXC_MAPTYPE_LARGE) {
            for (var i = 0; i < this.poiControls.length; i++) {
                var control = this.poiControls[i];
                if (this.map.getZoom() >= this.poiInfos[i].zoom) {
                    control.add();
                }
                else {
                    control.remove();
                }
            }
        }
    },

    initializeIcons: function() {
        for (var i = 0; i < this.poiTypes.length; i++) {
            var icon = new GIcon();
            icon.image = '/include/images/maps/' + this.poiTypes[i] + '_detail.gif';
            icon.iconSize = this.iconSizes[i];
            icon.iconAnchor = new GPoint(icon.iconSize.width / 2, icon.iconSize.height / 2);
            icon.infoWindowAnchor = new GPoint(icon.iconSize.width / 2, 6);
            this.icons[i] = icon;

            var clusterIcon = new GIcon();
            clusterIcon.image = '/include/images/maps/' + this.poiTypes[i] + '_cluster.gif';
            clusterIcon.iconSize = this.clusterIconSizes[i];
            clusterIcon.iconAnchor = new GPoint(clusterIcon.iconSize.width / 2, clusterIcon.iconSize.height / 2);
            clusterIcon.infoWindowAnchor = new GPoint(clusterIcon.iconSize.width, 6);
            this.clusterIcons[i] = clusterIcon;
        }
    },

    getIcon: function(iconArray, name) {
        for (var i = 0; i < this.poiTypes.length; i++) {
            if (this.poiTypes[i] == name) return iconArray[i];
        }
    },

    parseBounds: function(sBounds) {
        // N, S, W, E
        var locationParts = sBounds.split(',');
        // GLatLngBounds constructor: SW, NE
        return new GLatLngBounds(
            new GLatLng(locationParts[1], locationParts[2]),
            new GLatLng(locationParts[0], locationParts[3]));
    },

    initializeLocation: function(sLocationBounds) {
        // set the center
        if (sLocationBounds != null && sLocationBounds != '') {
            var locationBounds = this.parseBounds(sLocationBounds);
            var center = locationBounds.getCenter();
            this.lat = center.lat();
            this.lng = center.lng();
            var zoom = this.map.getBoundsZoomLevel(locationBounds);
            this.zoom = zoom;
            return locationBounds;
        }
        else {
            return null;
        }

    },

    unload: function() {
        GUnload();
    },

    search: function() {
        if (this.searchParams == null || this.map.getZoom() < 5) { return; }

        // don't bother searching if we already have the data
        if (this.insideOuterBounds()) { return false; }

        this.showLoading();

        this.setOuterBounds();

        var extraParams = {
            zoom: this.map.getZoom(),
            mbox: this.convertBoundsToString(this.outerBounds)
        };

        $.post("mapjax.html", $.param(extraParams) + "&" + this.searchParams, $.globalEval);
        return false;
    },

    insideOuterBounds: function() {
        if (this.outerBounds != null) {
            if (this.outerBounds.containsBounds(this.map.getBounds())) {
                return true;
            }
        }
        return false;
    },

    convertBoundsToString: function(bounds) {

        var northEast = bounds.getNorthEast();
        var southWest = bounds.getSouthWest();

        return northEast.lat() + ',' + southWest.lat() + ',' + southWest.lng() + ',' + northEast.lng();
    },

    setSearchParams: function(action, params) {
        // replace all &amp; by & in params
        this.searchParams = '_a=' + action;
        if (params != null) {
            this.searchParams += '&' + params.replace(/&amp;/g, '&');
        }
    },

    setOuterBounds: function() {
        var innerBounds = this.map.getBounds();

        var innerNE = innerBounds.getNorthEast();
        var innerSW = innerBounds.getSouthWest();

        //var factor = 2;
        var factor = 2;

        var latIncrement = (innerNE.lat() - innerSW.lat()) / factor;
        var lngIncrement = (innerSW.lng() - innerNE.lng()) / factor;

        var outerNE = new GLatLng(innerNE.lat() + latIncrement, innerNE.lng() - lngIncrement);
        var outerSW = new GLatLng(innerSW.lat() - latIncrement, innerSW.lng() + lngIncrement);

        this.outerBounds = new GLatLngBounds(outerSW, outerNE);

        // if the outer bounds change, set all poi controls to unloaded
        for (i = 0; i < this.poiInfos.length; i++) {
            this.poiInfos[i].loaded = false;
        }
    },

    cleanupMarkers: function() {
        // removes markers from the map, that are not in (or near) the current view
        for (var j = 0; j < this.markersCache.length; j++) {
            if (!this.outerBounds.contains(this.markersCache[j].getPoint())) {
                // this.debug('Removing marker at ' + this.markersCache[j].getPoint() );
                this.map.removeOverlay(this.markersCache[j]);
                this.markersCache.splice(j, 1);
                j--;
                // this.debug('Continuing check at ' + j + ', cache contains ' + this.markersCache.length + ' elements');
            };
        };

    },

    addJobCacheItem: function(lat, lng, job) {
        if (this.mapType == RXC_MAPTYPE_SAVED_ADS) {
            var cacheItem = this.getJobFromCache(lat, lng);
            if (cacheItem == null) {
                this.jobCache.push(new Jobs24JobCacheItem(lat, lng, job));
            }
            else {
                cacheItem.count++;
            }
        }
    },

    getJobFromCache: function(lat, lng) {
        for (var i = 0; i < this.jobCache.length; i++) {
            var item = this.jobCache[i];
            if (item.lat == lat && item.lng == lng) {
                return item;
            };
        };

        return null;
    },

    flushJobCache: function() {
        for (var i = 0; i < this.jobCache.length; i++) {
            var item = this.jobCache[i];

            if (item.count == 1) {
                this.addJobMarker(item.lat, item.lng, item.job);
            }
            else {
                this.addJobClusterMarker(item.lat, item.lng, item.count, null, item.job);
            }
        };

        return null;
    },

    // Real estate marker
    addJobMarker: function(lat, lng, job) {
        if (!this.inMarkersCache(lat, lng, 'job')) {
            var point = new GLatLng(lat, lng);
            var marker = new GMarker(point, this.icons[0]);

            marker.tooltip = '<div class="tooltip">' + job.getTooltipText() + '</div>';
            marker.poiType = 'job';

            if (this.mapType == RXC_MAPTYPE_LARGE || this.mapType == RXC_MAPTYPE_SAVED_ADS) {
                GEvent.addListener(marker, "click", $.bind(this, this.showJobInfo, marker, job));
            }

            GEvent.addListener(marker, "mouseover", $.bind(this, this.showTooltip, marker));
            GEvent.addListener(marker, "mouseout", $.bind(this, this.hideTooltip));

            this.map.addOverlay(marker);
            this.markersCache.push(marker);
        };
    },

    showJobInfo: function(marker, job) {
        this.hideTooltip();
        var point = marker.getPoint();

        var content = '<div>';
        content += '<div style="font-weight:bold; text-align:center">' + job.cityName.toUpperCase() + '</div>';

        if (job.image != '' && job.image != null) {
            content += '<div style="text-align:center"><img id="job_img_' + job.id +
                '" + width="82" height="61" src="' + this.imageServer + job.image + '" onerror="Jobs24Map.onImageLoadError(' + job.id + ')"></div>';
        }

        content += '<div>' + job.getTooltipText() + '</div>';

        if (this.map.getZoom() < 17) {
            content += '<div><a href="javascript:Jobs24Map.zoomIn(' + point.lat() + ', ' + point.lng() + ')">zoom in</a></div>';
        }

        content += '<div>';

        if (this.mapType == RXC_MAPTYPE_SAVED_ADS) {
            content += '<a href="#" onclick="void(Jobs24Map.onJobInfoClick(' + job.id + ') )">';
        }
        else {
            content += '<a href="/doc.html?_a=view&id=' + job.id + '" onclick="Jobs24Map.writeCookie()">';
        }

        content += "view job details</a></div>";
        content += '</div>';

        this.map.openInfoWindowHtml(point, content);
    },

    onImageLoadError: function(job_id) {
        // try again a few seconds later
        var image = $('#job_img_' + job_id).get(0);
        if (image != null && image.src.indexOf("?") == -1) {
            setTimeout('Jobs24Map.setImageSource(' + job_id + ')', 2000);
        }
    },

    setImageSource: function(job_id) {
        var image = $('#job_img_' + job_id).get(0);
        if (image != null && image.src.indexOf("?") == -1) {
            image.src = image.src + "?1";
        }
    },

    onJobInfoClick: function(id) {
        var url = '/doc.html?_a=view&id=' + id;

        if (this.mapType == RXC_MAPTYPE_SAVED_ADS) {
            var mainWindow = window.opener;
            if (mainWindow == null) {
                alert("Sorry, the window from which you opened\n this popup is not open anymore");
            }
            else {
                mainWindow.location = url;
                mainWindow.focus();
            }
        }
        else {
            window.location = url;
        }
    },

    // Real estate cluster marker
    addJobClusterMarker: function(lat, lng, count, box, job) {

        if (!this.inMarkersCache(lat, lng, 'job')) {
            var point = new GLatLng(lat, lng);

            this.debug(this.clusterIcons[0].image);

            var marker = new GMarker(point, this.clusterIcons[0]);

            marker.tooltip = '<div class="tooltip">' + count + ' jobs in group</div>';
            marker.poiType = 'job';

            GEvent.addListener(marker, "click", $.bind(this, this.showJobClusterInfo, marker, count, box, job));
            GEvent.addListener(marker, "mouseover", $.bind(this, this.showTooltip, marker));
            GEvent.addListener(marker, "mouseout", $.bind(this, this.hideTooltip, marker));

            this.map.addOverlay(marker);
            this.markersCache.push(marker);
        }
    },



    showJobClusterInfo: function(marker, count, box, job) {
        this.hideTooltip();
        var point = marker.getPoint();

        var content = '<div>';

        if (job != null) {
            content += '<div style="font-weight:bold; text-align:center">' + job.cityName.toUpperCase() + '</div>';
        }

        content += '<div>Found ' + count + ' jobs</div>';

        if (box != null) {
            var mboxBounds = this.parseBounds(box);
            content += '<div><a href="search.html?' + this.adjustSearchParam(this.searchParams, point, mboxBounds) +
                       (this.isPointInCurrentLocation(point, mboxBounds) ? ('loc=' + escape(this.locationText) + '&') : '') +
                       'mbox=' + box + '&edit=false" onclick="Jobs24Map.writeCookie()">view these results</a></div>';
        }
        if (this.map.getZoom() < 17) {
            content += '<div><a href="javascript:Jobs24Map.zoomIn(' + point.lat() + ', ' + point.lng() + ')">zoom in</a></div>';
        }

        content += '</div>';
        this.map.openInfoWindowHtml(point, content);
    },

    isPointInCurrentLocation: function(point, mboxBounds) {
        return this.locationBounds != null &&
            this.locationBounds.contains(new GLatLng(point.lat(), point.lng())) &&
            this.locationBounds.containsBounds(mboxBounds);
    },

    adjustSearchParam: function(searchParameters, point, mboxBounds) {
        if (!(this.isPointInCurrentLocation(point, mboxBounds))) {
            var resultString = '';
            var params = searchParameters.split('&');
            for (var i = 0, length = params.length; i < length; i++) {
                if (params[i].indexOf('lbox') < 0)
                    resultString += params[i] + '&';
            }
            return resultString;
        }
        else
            return searchParameters + '&';
    },

    // Branch marker
    addBranchMarker: function(lat, lng, branch) {

        if (!this.inMarkersCache(lat, lng, 'branch')) {
            var point = new GLatLng(lat, lng);

            var marker = new GMarker(point, this.icons[6]);

            marker.tooltip = '<div class="tooltip">' + branch.getTooltipText() + '</div>';
            marker.poiType = 'branch';

            GEvent.addListener(marker, "mouseover", $.bind(this, this.showTooltip, marker));
            GEvent.addListener(marker, "mouseout", $.bind(this, this.hideTooltip, this));

            this.map.addOverlay(marker);
            this.markersCache.push(marker);
        };
    },

    showPoiData: function(poiInfo) {
        this.debug("showPoiData( type:" + poiInfo.poiType + ", show:" + poiInfo.show + ")");

        if (poiInfo.zoom > this.map.getZoom()) return;

        if (poiInfo.show) {

            var insideOuterBounds = this.insideOuterBounds();

            if (!insideOuterBounds || !poiInfo.loaded) {

                if (!insideOuterBounds) this.setOuterBounds();
                // outside outer bounds or not yet loaded                  
                this.getPoiData(poiInfo.poiType);
            }
            else {
                // inside outer bounds + already loaded
                // get them from the cache
                for (var i = 0; i < this.markersCache.length; i++) {
                    var marker = this.markersCache[i];
                    if (marker.poiType == poiInfo.poiType) {
                        this.debug("Show marker " + i);
                        this.map.addOverlay(this.markersCache[i]);
                    }
                }
            }
        }
        else {
            // remove (but not from cache), except if the marker is outside the outer bounds
            for (var i = 0; i < this.markersCache.length; i++) {
                var marker = this.markersCache[i];
                if (marker.poiType == poiInfo.poiType) {
                    if (this.outerBounds != null && !this.outerBounds.contains(marker.getPoint())) {
                        // remove the marker from the cache
                        this.markersCache.splice(i, 1);
                        i--;

                        this.debug("Removed from cache");
                    }
                    marker.added = false;
                    this.map.removeOverlay(marker);
                }
            }
        }
    },

    showPoiDataByType: function(poiType) {
        var info = this.getPoiInfo(poiType);
        info.show = true;
        this.showPoiData(info);
    },


    showPoiDataExclusive: function(poiType) {
        for (var i = 0; i < this.poiInfos.length; i++) {
            var info = this.poiInfos[i];
            var nextShow = (info.poiType == poiType);
            if (info.show != nextShow) {
                info.show = nextShow;
                this.showPoiDataWithOldItems(info);
            }
        }
    },
    showPoiDataWithOldItems: function(poiInfo) {
        this.debug("showPoiDataWithOldItems( type:" + poiInfo.poiType + ", show:" + poiInfo.show + ")");
        if (poiInfo.zoom > this.map.getZoom()) {
            this.debug("Don't show POI: zoom level too high");
            return;
        }

        if (poiInfo.show) {
            var insideOuterBounds = this.insideOuterBounds();
            if (!insideOuterBounds || !poiInfo.loaded) {
                if (!insideOuterBounds) this.setOuterBounds();
                // outside outer bounds or not yet loaded                  
                this.getPoiData(poiInfo.poiType);
            }
            else {
                // inside outer bounds + already loaded
                // get them from the cache
                for (var i = 0; i < this.markersCache.length; i++) {
                    var marker = this.markersCache[i];

                    if (marker.poiType == poiInfo.poiType) {
                        this.debug("Show marker " + i);
                        this.map.addOverlay(this.markersCache[i]);
                    }
                }
            }
        }
    },

    removeMarkersFromMap: function(poiType) {
        for (var i = 0; i < this.poiInfos.length; i++) {
            var poiInfo = this.poiInfos[i];
            if (poiInfo.poiType == poiType) {
                poiInfo.show = false;
                for (var i = 0; i < this.markersCache.length; i++) {
                    var marker = this.markersCache[i];
                    if (marker.poiType == poiInfo.poiType) {

                        if (this.outerBounds != null && !this.outerBounds.contains(marker.getPoint())) {
                            // remove the marker from the cache
                            this.markersCache.splice(i, 1);
                            i--;

                            this.debug("Removed from cache");
                        }
                        marker.added = false;
                        this.map.removeOverlay(marker);
                    }
                }
                return;
            }
        }
    },

    showCheckedPoiData: function() {
        for (var i = 0; i < this.poiInfos.length; i++) {
            this.showPoiData(this.poiInfos[i]);
        }
    },

    getPoiData: function(poiType) {

        this.debug("getPoiData( " + poiType + ")");

        this.showLoading();

        // don't set new outerbounds unless there is none
        if (this.outerBounds == null) {
            this.setOuterBounds();
        }

        var params = {
            _a: 'poi',
            zoom: this.map.getZoom(),
            mbox: this.convertBoundsToString(this.outerBounds),
            type: poiType
        };

        $.post("mapjax.html", params, $.globalEval);

        return false;
    },

    addPoiMarker: function(poi) {

        this.debug("Adding poi marker");

        var markerInCache = this.getMarkerInCache(poi.pos.lat(), poi.pos.lng(), poi.poiType);

        if (markerInCache == null) {
            var marker = new GMarker(poi.pos, this.getIcon(this.icons, poi.type));
            marker.tooltip = '<div class="tooltip">' + poi.name + '</div>';
            marker.poiType = poi.type;
            marker.added = true;

            if (this.mapType == RXC_MAPTYPE_LARGE) {
                GEvent.addListener(marker, "click", $.bind(this, this.showPoiInfo, marker, poi));
            }

            GEvent.addListener(marker, "mouseover", $.bind(this, this.showTooltip, marker));
            GEvent.addListener(marker, "mouseout", $.bind(this, this.hideTooltip));

            this.map.addOverlay(marker);
            this.markersCache.push(marker);
        }
        else {
            this.debug("Already in cache");

            if (!markerInCache.added) {
                markerInCache.added = true;
                this.map.addOverlay(markerInCache);
            }
        }
    },

    showPoiInfo: function(marker, poi) {
        this.hideTooltip();
        var point = marker.getPoint();

        var content = '<div style="text-align:center">';

        content += '<div style="font-weight:bold;">' + poi.name + '</div>';
        content += '<div>' + poi.phone + '</div>';

        if (this.map.getZoom() < 17) {
            content += '<div><a href="javascript:Jobs24Map.zoomIn(' + point.lat() + ', ' + point.lng() + ')">zoom in</a></div>';
        }

        content += '</div>';

        this.map.openInfoWindowHtml(point, content);
    },

    inMarkersCache: function(lat, lng, poiType) {
        // returns true if we have a marker on this location, in the markercache
        return (this.getMarkerInCache(lat, lng, poiType) != null);
    },

    getMarkerInCache: function(lat, lng, poiType) {
        for (var i = 0; i < this.markersCache.length; i++) {
            var marker = this.markersCache[i];
            var point = marker.getPoint();
            if (point.lat() == lat && point.lng() == lng && marker.poiType == poiType) {
                return marker;
            };
        };

        return null;
    },

    showTooltip: function(marker) {
        if (this.infoWindowOpen) return;
        var tooltipEl = $(this.tooltip);
        tooltipEl.html(marker.tooltip);

        var projection = this.map.getCurrentMapType().getProjection();
        var zoom = this.map.getZoom();

        var point = projection.fromLatLngToPixel(this.map.fromDivPixelToLatLng(new GPoint(0, 0), true), zoom);
        var offset = projection.fromLatLngToPixel(marker.getPoint(), zoom);

        var icon = marker.getIcon();
        var anchor = icon.iconAnchor;
        var width = icon.iconSize.width;

        var pos = new GControlPosition(G_ANCHOR_TOP_LEFT,
                    new GSize(offset.x - point.x - anchor.x + width - (tooltipEl.width() / 2),
                              offset.y - point.y - anchor.y - tooltipEl.height() - 2));

        pos.apply(this.tooltip);

        tooltipEl.css("visibility", "visible");
    },

    hideTooltip: function() {
        $(this.tooltip).css("visibility", "hidden");
    },

    zoomIn: function(lat, lng) {
        this.map.setCenter(new GLatLng(lat, lng), this.map.getZoom() + 1);
    },

    onSearchDone: function(nrClusters) {
        this.hideLoading();
        this.loadControls(nrClusters);
        this.showPoiControls();
        this.showCheckedPoiData();
    },

    onPoisLoaded: function(type) {
        var info = this.getPoiInfo(type);
        info.loaded = true;
        this.hideLoading();
    },

    getPoiInfo: function(type) {
        for (i = 0; i < this.poiInfos.length; i++) {
            if (this.poiInfos[i].poiType == type) return this.poiInfos[i];
        }
    },

    showError: function(msg) {
        this.debug("Error! " + msg);
    },

    debugLocation: function(overLay, point) {
        if (point) {
            this.debug("Lat:" + point.lat() + ", Lng:" + point.lng());
        }
    },

    debug: function(msg) {
        if (this.enableDebugging) {
            GLog.write(msg);
        }
    },

    // event handling
    onZoomEnd: function(oldLevel, newLevel) {
        if (this.mapType == RXC_MAPTYPE_SINGLE) {
            this.clearPois(newLevel);
            //this.clear();
            this.search();
            this.showCheckedPoiData();
        }
        else if (this.mapType != RXC_MAPTYPE_BRANCHES && this.mapType != RXC_MAPTYPE_SAVED_ADS) {
            this.clear();
            this.search();
            this.showBoundary();
        }
        if (this.boundingBox != null) {
            this.map.removeOverlay(this.boundingBox);
            this.boundingBox = null;
            this.showBoundingBox();
        }
    },

    onMoveEnd: function() {
        this.search();
    },

    onMove: function() {
        if (this.boundingBox != null)
            this.boundingBox.redraw(true);
    },

    onInfoWindowOpen: function() {
        this.infoWindowOpen = true;
    },

    onInfoWindowClose: function() {
        this.infoWindowOpen = false;
    },

    // really only done after zoom
    clear: function() {
        this.outerBounds = null;
        this.map.clearOverlays();
        this.markersCache = new Array();
        $.each(this.poiInfos, function() { this.loaded = false; });
    },

    clearPois: function(newLevel) {
        this.outerBounds = null;

        for (i = 0; i < this.poiInfos.length; i++) {
            var info = this.poiInfos[i];

            if (info.loaded && info.zoom > newLevel) {
                for (var j = 0; j < this.markersCache.length; j++) {
                    var marker = this.markersCache[j];
                    if (marker != null && marker.poiType == info.poiType) {
                        marker.added = false;
                        this.map.removeOverlay(marker);
                    }
                }

                info.show = false;
                info.loaded = false;
            }

            var showPoiLink = $('#showPois_' + info.poiType);
            if (showPoiLink.length > 0) {
                if (info.zoom > newLevel) {
                    this.debug("Poi [" + info.poiType + "] zoom: [" + info.zoom + "] new level: [" + newLevel + "]");
                    showPoiLink.hide();
                    var ckbId = 'ckb_' + info.poiType;
                    if (ckbId != "ckb_airport")
                        $('#' + ckbId).get(0).checked = false;
                }
                else {
                    showPoiLink.show();
                }
            }
        }
    },

    showLoading: function() {
        this.map.disableDragging();
        this.map.disableDoubleClickZoom();
        $('#mapLoading').show();
    },

    hideLoading: function() {
        this.map.enableDragging();
        this.map.enableDoubleClickZoom();
        $('#mapLoading').hide();
    },

    getMapTypeStr: function() {
        var maptype = this.map.getCurrentMapType();
        var strmaptype;

        // WARNING! getCurrentMapType().GetName() is not culture neutral!!! 
        switch (maptype) {
            case G_NORMAL_MAP:
                strmaptype = "G_NORMAL_MAP";
                break;
            case G_SATELLITE_MAP:
                strmaptype = "G_SATELLITE_MAP";
                break;
            case G_HYBRID_MAP:
                strmaptype = "G_HYBRID_MAP";
                break;
        }
        return strmaptype;
    },

    writeCookie: function() {
        var center = this.map.getCenter();
        document.cookie = "RXC_MAP=lat=" + center.lat() + "&lng=" + center.lng() + "&z=" + this.map.getZoom() + "&maptype=" + this.getMapTypeStr();
    },

    getCookie: function(name) {
        arg = name + "=";
        alen = arg.length;
        clen = document.cookie.length;
        i = 0;
        while (i < clen) {
            j = i + alen;
            if (document.cookie.substring(i, j) == arg) {
                return this.getCookieVal(j);
            }
            i = document.cookie.indexOf(" ", i) + 1;
            if (i === 0) { break; }
        }
    },

    getCookieVal: function(offset) {
        endstr = document.cookie.indexOf(";", offset);
        if (endstr == -1) { endstr = document.cookie.length; }
        return unescape(document.cookie.substring(offset, endstr));
    },

    setCenter: function() {
        var maptype = null;
        if (this.mapType == RXC_MAPTYPE_LARGE) {
            var mapCookie = this.getCookie("RXC_MAP");

            if (mapCookie != null) {
                var nameValues = mapCookie.split('&');
                for (var i = 0; i < nameValues.length; i++) {
                    var nameValue = nameValues[i].split('=');
                    switch (nameValue[0]) {
                        case 'lat':
                            this.lat = parseFloat(nameValue[1]);
                            break;

                        case 'lng':
                            this.lng = parseFloat(nameValue[1]);
                            break;

                        case 'z':
                            this.zoom = parseInt(nameValue[1]);
                            break;
                        case 'maptype':
                            if (this.getMapTypeStr() == nameValue[1]) { break; }
                            var maptypeString = nameValue[1];
                            switch (maptypeString) {
                                case "G_NORMAL_MAP":
                                    maptype = G_NORMAL_MAP;
                                    break;
                                case "G_SATELLITE_MAP":
                                    maptype = G_SATELLITE_MAP;
                                    break;
                                case "G_HYBRID_MAP":
                                    maptype = G_HYBRID_MAP;
                                    break;
                            }
                            break;
                    }
                }
            }
        }
        this.map.setCenter(new GLatLng(this.lat, this.lng), this.zoom);
        if (maptype != null)
            this.map.setMapType(maptype);
    },

    showBoundary: function() {
        if (this.locationBounds != null && this.enableBoundary) {
            var ne = this.locationBounds.getNorthEast();
            var sw = this.locationBounds.getSouthWest();
            var nw = new GLatLng(ne.lat(), sw.lng());
            var se = new GLatLng(sw.lat(), ne.lng());

            var polyline = new GPolyline([nw, ne, se, sw, nw], "#FF0000", 10);
            this.map.addOverlay(polyline);
        }
    }
}

// Job object

var Jobs24Job = Class.create();

Jobs24Job.prototype = {

    id: null,

    cityName: 'default',

    jobTitle: null,
    jobSector: '-1',
    jobSectorText: null,

    jobType  : '-1',
    jobTypeText: null,

    minSalary : -1,
    maxSalary: -1,

    salaryType: '-1',

    initialize: function( id, cityName, jobTitle, jobSector, jobSectorText, jobType, jobTypeText, minSalary, maxSalary, salaryType ) {

        this.id = id; 
        this.cityName = cityName; 
        this.jobSector = jobSector;
        this.jobType = jobType;
        this.minSalary = minSalary; 
        this.maxSalary = maxSalary; 
        this.salaryType = salaryType;
        this.jobTitle = jobTitle;

        if( jobSectorText != null ) this.jobSectorText = jobSectorText;
        if( jobTypeText   != null ) this.jobTypeText   = jobTypeText;
    },

    getAmountString: function( amount ) {   
        // TODO: what if there are pennies in the price?
        amount =  amount.replace( /\.\d+$/ ,'');

        var rgx = /(\d+)(\d{3})/;
        while (rgx.test(amount)) {
            amount = amount.replace(rgx, '$1' + ',' + '$2');
        }

        return amount;
    },

    getText: function( key, selectId ) {
        var select = $('#' + selectId).get(0);
        if(select) {
            var options = select.options;
            var i;
            for( i = 0; i < options.length; i++ ) {
                var option = options[i];
                if( option.value == key ) {
                    return option.innerHTML;
                    break;
                }
            }
        }
        return null;
    },

    getJobSectorText: function() {
        if( this.jobSectorText == null ) { 
            
            if( this.jobSector < 0 ) {
                this.jobSectorText = 'UNKNOWN';
            }
            else {
                this.jobSectorText = this.getText(this.jobSector, 'search_sector');
                if( this.jobSectorText  == null ) {
                    this.jobSectorText = 'UNKNOWN';
                }
            }
        }
        return this.jobSectorText;
    },

    getJobTypeText: function() {
        if( this.jobTypeText == null ) {
            this.jobTypeText = this.getText(this.jobType, 'search_type');
            if( this.jobTypeText  == null ) {
                this.jobTypeText = 'UNKNOWN';
            }
        }
        return this.jobTypeText;
    },

    formatSalary: function( num ) {
        var num = num.toString().replace(/\$|\,/g,'');

        if(isNaN(num)) {
            num = "0";
        }

        var sign = (num == (num = Math.abs(num)));
        var num = Math.floor(num*100+0.50000000001);
        var cents = num%100;

        num = Math.floor(num/100).toString();   

        if(cents > 0 && cents<10) {
            cents = "0" + cents;
        }

        for (var i = 0; i < Math.floor((num.length-(1+i))/3); i++) { 
            num = num.substring(0,num.length-(4*i+3))+','+
                    num.substring(num.length-(4*i+3));
        }

        var result = ((sign)? '':'-') + '£' + num; 
        if( cents > 0 ) { 
            result +=  '.' + cents;
        }

        return result;
    },

    getTooltipText: function() {
        var text = 
            "<B>" + this.jobTitle + "</B><BR>"; 

        var jsText = this.getJobSectorText();

        if( jsText != 'UNKNOWN' ) {
            text += "Job sector: " + this.getJobSectorText() + "<BR>";
        }

        text += "Job type: " + this.getJobTypeText() + "<BR>";

        if( this.minSalary > 0 || this.maxSalary > 0 ) {
            if( this.minSalary == this.maxSalary ) {
                text += this.formatSalary( this.minSalary );
            }
            else {
                text += this.formatSalary( this.minSalary ) + " - " + this.formatSalary( this.maxSalary )
            }

            text += ' ';

            switch (this.salaryType) {
                case 0: text += 'per annum'; break;
                case 1: text += 'pro rata'; break;
                case 2: text += 'per week'; break;
                case 3: text += 'per day'; break;
                case 4: text += 'per hour'; break;
                case 5: text += 'per month'; break;
            }
        }
        else {
            text += "salary not specified";
        }
        return text;
    }
}


var Jobs24Branch = Class.create();

Jobs24Branch.prototype = {

    name: null,
    
    initialize: function( name ) {
        this.name = name; 
    },

    getTooltipText: function() {
        return this.name;
    }
}

// Poi object

var Jobs24Poi = Class.create();

Jobs24Poi.prototype = {

    type: null,
    name: '',
    phone: '',
    pos: null,

    initialize: function( type, name, lat, lng) {
        this.type = type;

        var nameParts = name.split('-');
        if( nameParts.length > 1 ) {
            this.phone = nameParts[1];
        }

        this.name = nameParts[0];

        this.pos = new GLatLng( lat, lng );
    }
}

var Jobs24PoiInfo = Class.create();

Jobs24PoiInfo.prototype = { 
    zoom: 1,
    poiType: null,
    show: false,

    initialize: function( poiType, zoom ) {
        this.poiType = poiType;
        this.zoom = zoom;
    }
}


var Jobs24JobCacheItem = Class.create();

Jobs24JobCacheItem.prototype = {

    lat: 0,
    lng: 0,
    count: 1,
    job: null,
    
    initialize: function( lat, lng, job ) { 
        this.lat = lat;
        this.lng = lng;
        this.job = job;
    }
}

// PoiControl

function Jobs24PoiControl( poiInfo, name, position, map ) {
    this.name = name;
    this.poiInfo = poiInfo;
    this.position = position;
    this.map = map;
    this.added = false;
}

Jobs24PoiControl.prototype = new GControl();

// Creates a one DIV for each of the buttons and places them in a container
// DIV which is returned as our control element. We add the control to
// to the map container and return the element for the map class to
// position properly.
Jobs24PoiControl.prototype.initialize = function(map) {
    if (!this.container) {

        var containerDiv = $.create("div").attr("class", "poiControl");
        var poiCheckBox = $.create("input").attr("type", 'checkbox').click($.bind(this, this.onClick));
        containerDiv.append(poiCheckBox);
        containerDiv.append($.create("span").text(this.name));

        this.checkbox = poiCheckBox.get(0);
        this.container = containerDiv.get(0);
    }

    map.getContainer().appendChild(this.container);
    return this.container;
}

Jobs24PoiControl.prototype.onClick = function() {
    this.poiInfo.show = this.checkbox.checked;
    Jobs24Map.showPoiData(this.poiInfo);
}

Jobs24PoiControl.prototype.add = function() {
    if( !this.added ) {
        this.map.addControl( this );
        this.added = true;
    }
}

Jobs24PoiControl.prototype.remove = function() {
    if( this.added ) {
        this.map.removeControl( this );
        this.added = false;
    }
}

// By default, the control will appear in the top left corner of the
// map with 7 pixels of padding.
Jobs24PoiControl.prototype.getDefaultPosition = function() {
  return this.position;
}

function jobs24NoResultsControl() {
    var containerDiv = $.create("div").attr("id", "gradBack");
    containerDiv.append($.create("p").text("Sorry, we don't hold any jobs matching your exact requirements."));
    containerDiv.append($.create("p").text("Please try a new search."));

    this.container = containerDiv.get(0);
}

jobs24NoResultsControl.prototype = new GControl();

jobs24NoResultsControl.prototype.initialize = function( map ) { 
     map.getContainer().appendChild( this.container );    
     return this.container;
}

jobs24NoResultsControl.prototype.getDefaultPosition = function() {
    return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(100, 100))    
}

function jobs24MapLoadingControl() {}

jobs24MapLoadingControl.prototype = new GControl();

jobs24MapLoadingControl.prototype.initialize = function(map)
{
    if (!this.container)
    {
        var containerDiv = $.create("div").css("display", "none").attr("class", "contentBox").attr("id", "mapLoading");
        var top = $.create("div").attr("class", "Top");
        var middle = $.create("div").attr("class", "Middle");
        var bottom = $.create("div").attr("class", "Bottom");
        containerDiv.append(middle);
        middle.append(top);
        top.append(bottom);

        var image = $.create("img").attr("src", "/include/images/rotating_arrow.gif");
        bottom.append(image);

        var span = $.create("span").attr("id", "mapLoadingSpan").text("Loading locations...");
        bottom.append(span);

        this.container = containerDiv.get(0);
    }

    map.getContainer().appendChild(this.container);
    return this.container;
}


// By default, the control will appear in the top left corner of the
// map with 7 pixels of padding.
jobs24MapLoadingControl.prototype.getDefaultPosition = function() {
    return new GControlPosition(G_ANCHOR_BOTTOM_LEFT, new GSize(8, 35));
}