﻿/* Functionality to display google map complete with markers.
 * Written by Andy Wardley  abw@wardley.org for Completely Retail.
 * October-November 2008
 */


var DEFAULT = {
    info_width:   300,
    inset_width:  150,
    inset_height: 120,
    icons: {
        SC:     '/images/markers/SC.png',
        RW:     '/images/markers/RW.png',
        HS:     '/images/markers/HS.png',
        DEPT:   '/images/markers/DEPT.png',
        RAG:    '/images/markers/RAG.png',
        CAR:    '/images/markers/CAR.png',
        SM:     '/images/markers/SM.png',
        DIY:    '/images/markers/DIY.png',
        '101SC':  '/images/markers/101SC.png',
        '101RW':  '/images/markers/101RW.png',
        '102SC':  '/images/markers/102SC.png',
        '103RW':  '/images/markers/103RW.png',
        '103SC':  '/images/markers/103SC.png',
        '103HS':  '/images/markers/103HS.png'
    },
    shadows: {
        SC:     '/images/markers/RWSC_shadow.png',
        RW:     '/images/markers/RWSC_shadow.png',
        HS:     '/images/markers/HS_shadow.png',
        DEPT:   '/images/markers/BL_shadow.png',
        RAG:    '/images/markers/BL_shadow.png',
        CAR:    '/images/markers/BL_shadow.png',
        SM:     '/images/markers/BL_shadow.png',
        DIY:    '/images/markers/BL_shadow.png',
        '101SC':  '/images/markers/BL_shadow.png',
        '101RW':  '/images/markers/BL_shadow.png',
        '102SC':  '/images/markers/BL_shadow.png',
        '103RW':  '/images/markers/BL_shadow.png',
        '103SC':  '/images/markers/BL_shadow.png',
        '103HS':  '/images/markers/BL_shadow.png'
    }
};


function show_google_map(options) {
    return new CRMap(options);
}

function CRMap() {
    this.init.apply(this, arguments);
}

proto = CRMap.prototype;

proto.log = function() {
//    if (console && console.log)
//       console.log.apply(console, arguments);
};

proto.init = function(config) {
    var elemname  = config.element || 'map';
    this.element  = document.getElementById(elemname);
    if (! this.element) {
        alert("The '" + elemname + "' element is not defined in the document");
        return;
    }

    this.map        = new GMap2(this.element);
    this.icon       = config.icon;
    this.shadow     = config.shadow;
    this.icons      = config.icons      || DEFAULT.icons;
    this.shadows    = config.shadows    || DEFAULT.shadows;
    this.info_width = config.info_width || DEFAULT.info_width;
    this.autoload   = config.autoload   || { };
    this.group      = config.group      || { };
    this.branding   = parseInt(config.branding) || 0;
    this.markers    = [ ];
    this.ids        = { };
		
		/* ATTEMPTING TO ADD MARKER MANAGER TO THIS CLASS - WILL PROBABLY NOT WORK AS I HAVEN'T A CLUE WHAT I'M DOING IN HERE - JBB 
		this.mgr 				= new MarkerManager(this.map);*/
		
    if (config.controls) {
        this.log('Adding controls');
        this.map.addControl( new GLargeMapControl() );	
        this.map.addControl( new GMapTypeControl() );
        this.map.addControl( new GScaleControl() );
    }

    if (config.inset) {
        this.log('Adding inset');
        var width  = config.inset_width  || DEFAULT.inset_width;
        var height = config.inset_height || DEFAULT.inset_height;
        
        this.map.addControl(
            new GOverviewMapControl(),
            new GControlPosition(
                /* for some reason, BOTTOM_LEFT works on safari, even
                 * though it appears on the right in both safari and firefox!
                 */
                G_ANCHOR_BOTTOM_LEFT, 
                new GSize(width, height)
            )
        );
    }

    var latitude, longitude;
    var markers  = config.markers;
    var map_type = config.map_type || G_NORMAL_MAP;
    var zoom     = config.zoom;

    if (markers.length) {
        this.log('Computing centroid of markers');
        var min_lat  =  1000;
        var min_long =  1000;
        var max_lat  = -1000;
        var max_long = -1000;
        var marker, n, mid_lat, mid_long, zoom;

        // compute the midpoint of lat and long
        for (n in markers) {
            marker = markers[n];
            if (marker[0] < min_lat) {
                min_lat = marker[0];
            }
            if (marker[0] > max_lat) {
                max_lat = marker[0];
            }
            if (marker[1] < min_long) {
                min_long = marker[1];
            }
            if (marker[1] > max_long) {
                max_long = marker[1];
            }
        }
        
        if (! zoom) {
            var delta_lat  = max_lat - min_lat;
            var delta_long = max_long - min_long;
            var delta_max  = delta_lat > delta_long ? delta_lat : delta_long;
            this.log('delta max: %s', delta_max);
            // ad-hoc guestimate
            if (delta_max < 0.05)
                zoom = 12;
            else  if (delta_max < 0.1)
                zoom = 11;
            else if (delta_max < 0.31)
                zoom = 10;
            else if (delta_max < 0.75)
                zoom = 9;
            else if (delta_max < 1.25)
                zoom = 8;
            else if (delta_max < 2)
                zoom = 7;
            else if (delta_max < 4.7)
                zoom = 6;
            else 
                zoom = 5;
        }
        this.log("Latitude: %f - %f  (%f)", min_lat, max_lat, delta_lat);
        this.log("Longitude: %f - %f  (%f)", min_long, max_long, delta_long);
        latitude  = (min_lat + ((max_lat - min_lat) / 2 ));
        longitude = (min_long + ((max_long - min_long) / 2));
    }
    else {
        latitude  = 51.2603;
        longitude = -0.589397;
    }

    zoom = zoom || 8;

    this.log('Setting centre to %f,%f zoom:%d', latitude, longitude, zoom);
    this.map.setCenter(
        new GLatLng(config.latitude || latitude, config.longitude || longitude), 
        zoom, map_type
    );
    
    if (markers)
        this.add_markers(markers);
    
    if (this.autoload.listings || this.autoload.poi) {
        this.load_listings();
        GEvent.bind(this.map, "zoomend", this, this.load_listings);
        GEvent.bind(this.map, "moveend", this, this.load_listings);
    }
}

proto.add_data = function(data) {
//    this.log('Adding %s data', data.length);
    var markers = [];

    for (var n in data) {
        var item = data[n];
        var lat  = item.latitude;
        var lng  = item.longitude;
        if (parseFloat(lat) != 0 && parseFloat(lng) != 0)
            markers.push([lat,lng,item]);
    }
    this.add_markers(markers);
}

proto.add_markers = function(markers) {
    this.log('Adding %s markers', markers.length);

    for (var n in markers) {
        var item    = markers[n];
        var lat     = item[0];
        var lng     = item[1];
        var opts    = item[2];

        if (opts && opts.id && this.ids[opts.id])
            continue;       /* skip duplicates */
        
        if (parseFloat(lat) == 0 && parseFloat(lng) == 0)
            continue;

        var group = opts.group;
        if (group) {
            if (opts.branding_id && parseInt(opts.branding_id) != 0) {
                /* branded results are shown with branded icons if this is
                 * an unbranded map, or if it's a branded map which has the
                 * same branding as the marker
                 */
                if ((! this.branding) || (this.branding && this.branding == opts.branding_id)) {
                    group = opts.branding_id + opts.group;
                    opts.branded = 1;
                }
            }
        }
        else {
            group = 'default';
        }
        opts.group = group;

        var icon    = new GIcon(G_DEFAULT_ICON);
        var image   = opts.icon   || this.icons[group]   || this.icon;
        var shadow  = opts.shadow || this.shadows[group] || this.shadow;

//        this.log('%s marker at %f,%f', group || '<anon>', lat,lng);
        
        if (image) {
            icon.image    = image;
            icon.iconSize = new GSize(25, 25);
        }

        if (shadow) {
            icon.shadow     = shadow;
            icon.shadowSize = new GSize(25, 25);
        }

        if (image || shadow) {
            icon.iconAnchor = new GPoint(0, 25);
            icon.imageMap   = [0,0,25,0,25,25,0,25];
        }
        
        this.log('%s marker at %f,%f  image:%s  shadow:%s', group || '<anon>', lat,lng,image,shadow);
    
        // moved this into a separate method to force a new marker reference
        // to be created.  I'm not sure if it's me or JS, but marker appeared
        // to be getting re-used when the marker was created inline???
        var marker = this.new_marker(
            lat, lng,
            (image || shadow) ? { icon: icon } : { }, 
            opts
        );

        if (opts.id)
            this.ids[opts.id] = marker;
        
        // add marker to any group it defines
        var group;
        if (opts.group) {
//          this.log('adding marker to %s group', opts.group);
            group = this.group[opts.group];
            if (! group)
                group = this.group[opts.group] = { visible: 1, markers: [ ] };
            if (! group.markers)
                group.markers = [ ];
            group.markers.push(marker);
        }

        this.markers.push(marker);

        /* display markers that aren't in groups, or are in visible groups */
        if ((group && group.visible) || ! group)
            this.map.addOverlay(marker);

    }
};

proto.new_marker = function(lat, lng, opts, info) {
    var text   = info ? this.marker_info(info) : '';
    var marker = new GMarker( 
        new GLatLng(lat, lng),  
        opts
    );

    var that = this;
    GEvent.addListener(marker, "click", function() {
        marker.openInfoWindowHtml(text, { maxWidth: this.info_width }); // 
        if (info.callback) {
            info.callback(this, lat, lng, info, marker);
        }
    });
    
    return marker;
};

proto.marker_info = function(info) {
		var thumbnail;
		if (info.thumb) {	
			thumbnail = this.elem('img', {'src':info.thumb,'alt':info.name,'style':'width:64px;height:52px;float:left;margin-right:10px;'} ); 
			if(info.link) { thumbnail = this.elem('a',{href:info.link}, thumbnail); }
		}

    var text = this.elem('h4', {'class':'name','style':'clear:none;float:left; width:200px;margin:0em;'}, info.name);
    if (info.address)
        text = text + this.elem('div', {'class':'address','style':'clear:none; float:left;font-size:1.1em; width:20em;'}, info.address);
    // hideous hack to only show links for branded markers (where the map
    // is branded) or show all markers when the map is unbranded
    if (info.link && (info.show || (! this.branding) || (info.branding_id && this.branding == info.branding_id)))
        text = text + this.elem('a', {'class':'link',href:info.link,'style':'clear:none; float:left;font-size:1.1em;'}, 'View ' + (info.subject_type || 'details'));

		var output = this.elem('div', {'class':'info','style':'width:30em;'},thumbnail + text + '<br clear="all">'); //
//    return this.elem('div', {'class':'details'}, output);
		return output;
};

proto.elem = function(etype, attrs, ebody) {
		if(etype == 'img') { return '<' + etype + this.elem_attrs(attrs) + '/>'; }
		else { return '<' + etype + this.elem_attrs(attrs) + '>' + ebody + '</' + etype + '>'; }
}

proto.elem_attrs = function(attrs) {
    var name, value;
    var chunks = [ ];
    for (name in attrs) {
        value = attrs[name];
        chunks.push(name + '="' + value + '"');
    }
    return chunks.length
        ? ' ' + chunks.join(' ')
        : '';
}

proto.check_group = function(checkbox,name) {
    if (checkbox.checked)
        this.show_group(name);
    else
        this.hide_group(name);
}

proto.hide_group = function(name) {
    var group = this.group[name];
    if (!group)  {
        this.group[name] = { visible: 0, markers: [ ] };
        this.log('Invalid marker group specified: %s', name);
        return;
    }
    for (n in group.markers) {
        this.map.removeOverlay(group.markers[n])
    }
    group.visible = 0;

    /* one group can be linked to one or more others, so if we turn
     * one or the other on or off, then the linked groups should also
     * turn on/off.  WARNING: this could result in an infinite loop
     * if there are circular references between groups.
     */
    if (group.link) {
        for (n in group.link) {
            this.hide_group(group.link[n]);
        }
    }

};

proto.show_group = function(name) {
    var group = this.group[name];
    if (!group)  {
        this.log('Invalid marker group specified: %s', name);
        return;
    }
    if (group.visible) {
        this.log('Group is already visible: %s', name);
        return;
    }
    for (n in group.markers) {
        this.map.addOverlay(group.markers[n]);
    }
    group.visible = 1;
    
    /* chase linked groups, as per hide_group() */
    if (group.link) {
        for (n in group.link) {
            this.show_group(group.link[n]);
        }
    }
};

proto.load_listings = function() {
    var bounds = this.map.getBounds();
    var ne     = bounds.getNorthEast();
    var sw     = bounds.getSouthWest();
    var north  = ne.lat();
    var east   = ne.lng();
    var south  = sw.lat();
    var west   = sw.lng();
    var self   = this;
//    this.log('Loading listings via AJAX');
    $('#bounds').html(
        'Latitude: N:' + north + ' to S:' + south + '<br>' + 
        'Longitude: E:' + east + ' to W:' + west
    );
/* REMOVED FOR CUSHMAN & WAKEFIELD - I THINK THIS ADDED IN THE POI STUFF OR SOMETHING! JBB
    $.getJSON(
        "/map/scan", 
        { n:        north, 
          s:        south, 
          e:        east, 
          w:        west, 
          json:     1, 
          listings: this.autoload.listings ? 1 : 0,
          poi:      this.autoload.poi ? 1 : 0 
        },
        function(data) { self.add_data(data) }
    );
*/
}

proto.map_update = function() {
    var bounds = this.map.getBounds();
    var ne     = bounds.getNorthEast();
    var sw     = bounds.getSouthWest();
    var north  = ne.lat();
    var east   = ne.lng();
    var south  = sw.lat();
    var west   = sw.lng();
    var self   = this;
    $('#bounds').html(
        'Latitude: N:' + north + ' to S:' + south + '<br>' + 
        'Longitude: E:' + east + ' to W:' + west
    );
    $.getJSON(
        "/map/poi", 
        { n: north, s: south, e: east, w: west, json:1 },
        function(data) { self.import_poi_data(data) }
    );
}



