/**
* @package 'Route Groups for Google Maps'
* @copyright Copyright (C) 2007 Mark Sanford. All rights reserved.
* @license http://www.gnu.org/copyleft/gpl.html GNU/GPL, see LICENSE.php
* See COPYRIGHT.txt for copyright notices and details.
*/

/* All methods are 'private' unless commented otherwise */

/* public */
// element = textual id of <div> of google map
// scriptsbase = url of scripts directory
// opt = object containing optional properties: region, wheelzoom
function RideMap(element, scriptbase, opts) {
	/*
    	routes: array of route objects
    	routes[0].id;
    	routes[0].marker;
    	routes[0].status;	0=unloaded, 1=load pending, 2=loaded
    	routes[0].bounds		loaded as needed
    	routes[0].line		loaded as needed
    	routes[0].infoHTML	loaded as needed
	*/
    this.routes = null;
    this.currentindex = -1;
    this.scriptbase = scriptbase;
    this.zoomResponse = null;
    this.opts = opts?opts:{};
    this.element = document.getElementById(element);
	this.getroute = this.scriptbase + '/getroutes.php?';
    
    this.element.style.backgroundColor = '#DDDDDD';
    
    if (!GBrowserIsCompatible()) return;
    
	grurl = this.scriptbase + "/getroutes.php";
	if (this.opts.region) {
		grurl += '?region=' + this.opts.region;
	} else if (this.opts.tag) {
		grurl += '?tag=' + this.opts.tag;
	}

	ajaxSend(grurl, this, this.onRouteListLoaded);

}

// Callback when we've fetched all basic route info. Populate markers.
RideMap.prototype.onRouteListLoaded = function(responseText) {

    var oObject;
    if (responseText.length == 0 || !(oObject = responseText.parseJSON()) ) {
        this.makeMap(new GLatLngBounds(new GLatLng(-89, -179), new GLatLng(89, 179)));
        return;
    }

    this.routes = new Array();

    if (this.opts.region) {
        b = this.opts.region.split(',');
    } else {
        var c = oObject.bounds;
        b = [ c['south'], c['west'], c['north'], c['east'] ];
    }

    this.makeMap(new GLatLngBounds(new GLatLng(parseFloat(b[0]), parseFloat(b[1])),
    new GLatLng(parseFloat(b[2]), parseFloat(b[3]))));

    var ra = oObject.routes;
    for (var i = 0; i<ra.length; i++)
    {
        this.routes[i] = new Object();
        this.routes[i].id = ra[i]['ID'];
        this.routes[i].marker = null;
        this.routes[i].status = 0;
        this.routes[i].line = null;
        this.routes[i].infoHTML = null;
        this.routes[i].bounds = null;
    }

    if (this.zoomResponse) {
        this.onZoomRouteLoaded(this.zoomResponse);
        this.zoomResponse = null;
    }

    this.createMarkers(ra);

}


// Create and configure Google Map
RideMap.prototype.makeMap = function(bounds) {
    this.map = new GMap2(this.element);
    this.map.setCenter(bounds.getCenter(), this.map.getBoundsZoomLevel(bounds));
    this.map.setMapType(G_NORMAL_MAP);
    this.map.addControl(new GLargeMapControl());
    this.map.addControl(new GMapTypeControl());

    if ( ! (this.opts.wheelzoom == 'n') ) {
        // prevent wheel events in map from scrolling browser
        this.map.enableScrollWheelZoom();
        GEvent.addDomListener(this.map.getContainer(), "DOMMouseScroll", rm_wheelevent);
        this.map.getContainer().onmousewheel = rm_wheelevent; 
    }

    GEvent.bind(this.map, "click", this, this.onMapClick);
    
    // make the tooltip
    this.tooltip = document.createElement("div");
    this.tooltip.style.visibility="hidden";
    this.element.appendChild(this.tooltip);

    MTSUtil.KeyModMonitor.monitorON();
    
}

// Create the markers from the fetched data.  Break up into several little
// actions using a timer callback for better responsiveness.
RideMap.prototype.createMarkers = function(ra) {
    var rm = this;
    var i = 0;
    function cm() {
        var ct = 0;
        while (i < ra.length && ct < 20)
        {
            var r = ra[i];
            var markerPt = r['marker_pos'].split(',');
            var m = new GMarker( new GLatLng(parseFloat(markerPt[0]), parseFloat(markerPt[1])) );
            m.tooltip = '<div class="rm_tooltip">'+r['caption']+'</div>';
            m.mymap = rm;
            GEvent.bind(m, "mouseover", m, m.onMarkerMouseOver);
            GEvent.bind(m, "mouseout", m, m.onMarkerMouseOut);
            GEvent.bind(m, "dblclick", m, m.onDblClick);
            rm.map.addOverlay(m);
            rm.routes[i].marker = m;
            m.routeindex = i;
            ct++; i++;
        }
        if (i < ra.length) {
            setTimeout(cm, 5);
        }
    }
    cm();
}


/* public */
RideMap.prototype.zoomToRouteLabel = function(label) {
    var grurl = this.scriptbase + "/getroutes.php?label=" + label;
    ajaxSend(grurl, this, this.onZoomRouteLoaded);
}

/* public */
RideMap.prototype.zoomToRouteID = function(id) {
    var grurl = this.getroute + "q=" + id;
    ajaxSend(grurl, this, this.onZoomRouteLoaded);
}

RideMap.prototype.zoomToBounds = function(bounds, backup) {
    var center = bounds.getCenter();
    var zoom = this.map.getBoundsZoomLevel(bounds) + (backup ? -1 : 0);
    this.map.setCenter(center, zoom);
}

// Callback when zooming a particular route(s)
RideMap.prototype.onZoomRouteLoaded = function(responseText) {
    if (this.routes == null) { 
        // oops, markers not loaded.  Stash away and do later.
        this.zoomResponse = responseText;
        return;
    }
    var loaded = this.loadRoute(responseText, true);
    if (loaded[0] != undefined) {
        this.map.closeInfoWindow();
        var bounds = this.routes[loaded[0]].bounds;
        for (var i=1; i<loaded.length; i++) {
            bounds.extend(this.routes[loaded[i]].bounds.getSouthWest());
            bounds.extend(this.routes[loaded[i]].bounds.getNorthEast());
        }
        this.zoomToBounds(bounds);
    }
}

RideMap.prototype.unloadRoute = function(idx) {
    if (this.routes[idx].line)
    this.map.removeOverlay(this.routes[idx].line);
    this.routes[idx].line = null;
    this.routes[idx].infoHTML = null;
    this.routes[idx].bounds = null;
    this.routes[idx].status = 0;
}

RideMap.prototype.unloadAllButCurrent = function() {
    for (var idx in this.routes) {
        if (idx == this.currentindex) continue;
        if (this.routes[idx].status > 0)
        this.unloadRoute(idx);
    }
}

RideMap.prototype.onMapClick = function(overlay,  point) {
    if (overlay)
    {
        if ( (typeof overlay.routeindex == "undefined")) // not a marker
        return;

        this.currentindex = overlay.routeindex;
        var r = this.routes[this.currentindex];

        // If we've already loaded this route, just show the infowindow
        switch (r.status) {
        case 0:
            r.status = 1;
            this.map.openInfoWindowHtml(overlay.getPoint(), "<p>Loading...<p>");
            var grurl = this.getroute + "q=" + r.id;
            ajaxSend(grurl, overlay, overlay.onRouteLoaded);
            // fall through
        case 1:
            if (!MTSUtil.KeyModMonitor.ctrlKey)
            this.unloadAllButCurrent();
            // do nothing.. we already have a load pending
            break;
        case 2:
            if (MTSUtil.KeyModMonitor.ctrlKey) {
                this.unloadRoute(this.currentindex);
            } else {
                this.openInfoWnd(overlay.getPoint());
            }
            break;
        }
    }
}

// Call back when a route is loaded after clicking marker
GMarker.prototype.onRouteLoaded = function(responseText)
{
    var loaded = this.mymap.loadRoute(responseText);
    for (idx in loaded) {
        if (loaded[idx] == this.mymap.currentindex)
        this.mymap.openInfoWnd(this.getPoint());
    }
}


RideMap.prototype.openInfoWnd = function(point)
{
    this.map.openInfoWindowHtml(point, this.routes[this.currentindex].infoHTML);
    var mag = document.getElementById("rm_mag");
    if (mag)
    GEvent.bindDom(mag,  "click",  this,  this.onZoom);
}

RideMap.prototype.onZoom = function(point)
{
    this.map.closeInfoWindow();
    var bounds = this.routes[this.currentindex].bounds;
    this.zoomToBounds(bounds, true);
}

// from a route ID, lookup it's index in this.routes
// Return -1 if not found
RideMap.prototype.lookupIndex = function(id) {
    var routeindex = -1;
    if (id != undefined && id != Number.NaN && id != -1) {
        for (i = this.routes.length; i--; ) {
            if (this.routes[i].id == id)
            routeindex = i;
        }
    }
    return routeindex;
}

// Load routes from XmlHttpRequest response
// return: an ARRAY of indexes of routes loaded.
// unconditional==true means load even it status is 0
RideMap.prototype.loadRoute = function(responseText, unconditional) {
    indexesLoaded = new Array();
    if (responseText.length == 0) return indexesLoaded;
    var oObject = responseText.parseJSON();
    for (var idx in oObject)
    {
        if (! oObject.hasOwnProperty(idx)) continue;

        var route = oObject[idx];
        
        var routeindex = this.lookupIndex(parseInt(route['ID'])); //!bottleneck?
        
        if (routeindex == -1) // we don't have a marker for this route!  Can't load!
        continue;
        
        if (!unconditional && this.routes[routeindex].status == 0) 
        continue;  // we've already been unloaded!
        
        this.routes[routeindex].status = 2;
        
        var encodedPolyline = new GPolyline.fromEncoded({
            color: route['color'],
            weight: 5,
			opacity: 0.7,
            points: route['encoded_polyline'],
            levels: route['encoded_levels'],
            zoomFactor: route['zoomfactor'],
            numLevels: route['numlevels']
        });

        this.routes[routeindex].line = encodedPolyline;
        this.routes[routeindex].infoHTML = this.makeInfoHtml(route);	
        
        // add the new line
        this.map.addOverlay(encodedPolyline);

        // figure out the bounds
        var sw = new GLatLng( route['bound_south'], route['bound_west']);
        var ne = new GLatLng(route['bound_north'], route['bound_east']);
        this.routes[routeindex].bounds = new GLatLngBounds(sw, ne);
        
        indexesLoaded[indexesLoaded.length] = routeindex;
    }

    return indexesLoaded;
}


RideMap.prototype.makeInfoHtml = function(record) {
    var html = 
        '<div class="rm_infodiv"><div class="rm_caption">CAPTION</div><div class="rm_picture">'
        + '<a href="LINK_URL" target="_blank"><img class="rm_img" src="PICTURE_URL" width="PICTURE_WIDTH" '
        + 'height="PICTURE_HEIGHT" /></a><div class="rm_description">DESCRIPTION</div>'
        + '<img id="rm_mag" src="SBASE/img/mg.png" width="20" height="20" /></div>';
    
    
    html = html.replace(/CAPTION/g, record['caption']);
    html = html.replace(/LINK_URL/g, record['link_url']);
    html = html.replace(/PICTURE_URL/g, record['picture_url']);
    html = html.replace(/PICTURE_WIDTH/g, record['picture_width']);
    html = html.replace(/PICTURE_HEIGHT/g, record['picture_height']);
    html = html.replace(/DESCRIPTION/g, record['description']);
    html = html.replace(/SBASE/g, this.scriptbase);

    return html;
}

/*
 * GMarker 'decorating' functions
 */

GMarker.prototype.onDblClick = function()
{
    this.mymap.zoomToRouteID(this.mymap.routes[this.routeindex].id);
}

// Callback when a marker is moused over
GMarker.prototype.onMarkerMouseOver = function()
{
    var map = this.mymap.map;
    var tooltip = this.mymap.tooltip;
    
    tooltip.innerHTML = this.tooltip;
    var point = map.getCurrentMapType().getProjection().fromLatLngToPixel
    (map.getBounds().getSouthWest(),map.getZoom());
    var offset = map.getCurrentMapType().getProjection().fromLatLngToPixel(this.getPoint(),map.getZoom());
    var anchor = this.getIcon().iconAnchor;
    var width = this.getIcon().iconSize.width;
    var pos = new GControlPosition(G_ANCHOR_BOTTOM_LEFT,
    new GSize(offset.x - point.x - anchor.x + width,- offset.y + point.y +anchor.y)); 
    pos.apply(tooltip);
    tooltip.style.visibility="visible";
}

GMarker.prototype.onMarkerMouseOut = function()
{
    this.mymap.tooltip.style.visibility="hidden";
}

/*
 * Helper functions
 */

// 'threadsafe' asynchronous XMLHTTPRequest code for multiple simltaneous fetches
// callback_function(resonseText) will be called with this of callback_object.
function ajaxSend(url, callback_object, callback_function) {

    function ajaxBindCallback(){
        if (ajaxRequest.readyState == 4) {
            if (ajaxRequest.status == 200) {
                ajaxCallback.call(ajaxObject, ajaxRequest.responseText);  
            } else {
                /* alert("There was a problem retrieving the xml data:\n" + ajaxRequest.status 
                                        + ":\t" + ajaxRequest.statusText + "\n" + ajaxRequest.responseText); */
            }
        }
    }
    // use a local variable to hold our request and callback until the inner function is called...
    var ajaxRequest = null;
    var ajaxCallback = callback_function;
    var ajaxObject = callback_object;

    ajaxRequest = GXmlHttp.create();
    ajaxRequest.onreadystatechange = ajaxBindCallback;
    ajaxRequest.open("GET", url, true);
    ajaxRequest.send(null);
    
}

function rm_wheelevent(e)
{
    if (!e){
        e = window.event
    }
    if (e.preventDefault){
        e.preventDefault()
    }
    e.returnValue = false;
}
