/**
* jquery.googlemap v1.1.2
*
* Copyright David Hong 2009
* http://davidhong.id.au/jquery/google/maps/
*
* Simplified Google Maps API integrated into jQuery
* 
**/

(function($) {
    // fireEvent(opts, fn, self, arg)
    //     opts:    (json) jQuery options for this plugin
    //     fn:      (function) function to run
    //     self:    (object) this
    //     arg:     (object) argument to feed to function (fn)
    //
    // note: fn should always return true on successful runs, otherwise return
    //       false
    function fireEvent(opts, fn, self, arg) {
        if ($.isFunction(fn)) {
            try {
                if (opts.debug) {
                    try {
                        console.log(fn + " called with arguments (" + arg + ")");
                    } catch (e) {
                        alert(fn + " called with arguments (" + arg + ")");
                    }
                }
                return fn.call(self, arg);
            } catch (error) {
                if (opts.debug) {
                    var errorMessage = "Error calling googlemaps." + fn + ": " + error;
                    try {
                        console.error(errorMessage);
                    } catch (e) {
                        alert(errorMessage);
                    }
                } else {
                    throw error;
                }
                return false;
            }
        }
        return true;
    }

    var current = null;

    function Googlemap(root, conf) {
        // current instance
        var self = this;
        if (!current) {
            current = self;
        }

        // internal variables
        var map;
        var geo;
        var bounds;
        var markers;
        var processed;
        var failed;
        //var index = 0;
        var geocodeDelay = 50;

        // configuration (comments show default values)
        var latitude = conf.latitude;    // -35      initial latitude
        var longitude = conf.longitude;   // 150      initial longitude
        var zoom = conf.zoom;        // 4        this is the marker zoom
        var controls = conf.controls;    // true
        var labels = conf.labels;      // true
        var maptype = conf.maptype      // "street" map type options are: "street", "hybrid", "satellite"
        var draggable = conf.draggable;   // false    are the markers draggable?
        var html = conf.html;        // null
        var anchor = conf.anchor;      // null
        var addresses = conf.addresses;   // null     array of addresses as key and markers as values (null for no markers)
        var icons = conf.icons;       // null     array of images to use instead of the markers
        var debug = conf.debug;       // false

        // methods
        $.extend(self, {
            // plugin specific
            getVersion: function() { return [1, 0, 0]; },
            getRoot: function() { return root; },

            // google maps specific
            getMap: function() { return map; },
            getGeo: function() { return geo; },
            getAddresses: function() { return addresses; },
            getBounds: function() { return bounds; },
            //getIndex: function() { return index; },
            getMarkers: function() { return markers; },

            // api
            isBrowserCompatible: function() {
                if ($.isFunction(GBrowserIsCompatible))
                    return GBrowserIsCompatible();

                return false;
            },
            initialise: function() {
                self.trace("initialising: " + this);
                if (self.isBrowserCompatible()) {
                    self.trace("browser is compatible");
                    self.trace("root is: " + root);
                    self.trace("google map loading into element: " + document.getElementById($(root)[0].id));
                    map = map || new GMap2(document.getElementById($(root)[0].id));
                    geo = geo || new GClientGeocoder();
                    bounds = bounds || new GLatLngBounds();
                    markers = markers || new Array();
                    
                    self.trace("internal variables declared successfully");

                    GEvent.addListener(map, "load", function() {
                        self.trace("google map loaded!");
                    });

                    // set the map center and default zoom
                    //map.setCenter(new GLatLng(latitude, longitude), zoom);

                    // set the map type
                    if (maptype == "hybrid") map.setMapType(G_HYBRID_MAP);
                    else if (maptype == "satellite") map.setMapType(G_SATELLITE_MAP);
                    else if (maptype == "terrain") { map.setMapType(G_PHYSICAL_MAP); map.addMapType(G_PHYSICAL_MAP); }
                    else map.setMapType(G_NORMAL_MAP);

                    
                    // mark addresses on the map
                    if (addresses) {
                        if (addresses.length > 0) {
                            // HACK: only works on html elements                                                    
                            self.markAddress(0);
                            /*
                            while (i < addresses.length) {
                                // determine if its an address or latlong
                                addresses[i] = $(addresses[i]).text().trim();
                                self.trace(addresses[i]);
                                if (/^([-]*\d+(\.\d+)?)::([-]*\d+(\.\d+)?)/i.test(addresses[i]))  {
                                    // parse latlong and do what ever geocode does with the marker
                                    geo = (geo == null) ? new GClientGeocoder() : geo;
                                    failed = (failed == null) ? new Array() : failed;
                                    processed = (processed == null) ? new Array() : processed;
                                    var lat = addresses[i].split("::")[0];
                                    var lon = addresses[i].split("::")[1];
                                    self.addPlaceholder(i++, geo, failed, processed, lat, lon);
                                } else {                                                                      
                                    self.geocode(i++,1);
                                }
                            }
                            */
                        }
                    }

                    // add controls
                    if (controls) {
                        map.addControl(new GSmallMapControl());
                        map.addControl(new GMapTypeControl());
                    }
                }
            },
            
            markAddress: function(i) {               
                self.trace(Date());
                if (i < addresses.length) {
                    // determine if its an address or latlong
                    self.trace(addresses[i]);                    
                    addresses[i] = $(addresses[i]).text().trim();
                    if (/^([-]*\d+(\.\d+)?)::([-]*\d+(\.\d+)?)/i.test(addresses[i]))  {
                        // parse latlong and do what ever geocode does with the marker
                        geo = (geo == null) ? new GClientGeocoder() : geo;
                        failed = (failed == null) ? new Array() : failed;
                        processed = (processed == null) ? new Array() : processed;
                        var lat = addresses[i].split("::")[0];
                        var lon = addresses[i].split("::")[1];
                        self.trace("lat-lon found " + addresses[i]);
                        self.addPlaceholder(i, geo, failed, processed, lat, lon);
                        self.markAddress(++i);
                    } else {
                        //wait for the response to get back plus the geocodeDelay in ms                             
                        self.geocode(i);                                                              
                        setTimeout(function() {
                            self.markAddress(++i);
                        }, geocodeDelay);
                    }
                }
            },


            addPlaceholder: function(index, geo, failed, processed, lat, lon) {
            
                // TODO: extra from here for latlong addresses
                var point = new GLatLng(lat, lon, true);

                // extend bounds
                bounds = bounds || new GLatLngBounds();
                bounds.extend(point); self.trace("bounds extended");

                // marker
                var marker = self.createMarker(index, point);
                self.trace(marker); self.trace("marker created");

                // marker events
                GEvent.addListener(marker, "click", function() {
                    //map.setCenter(marker.getLatLng(), map.getBoundsZoomLevel(bounds) + 1);
                    map.setCenter(marker.getLatLng(), 15);
                    if (html) {
                        if (html[index]) {
                            marker.openInfoWindowHtml($(html[index]).html());
                        } else {
                            self.trace(index + ": " + addresses[index]);
                        }
                    }
                });

                // external link events
                if (anchor) {
                    if (anchor[index]) {
                        GEvent.addDomListener(anchor[index], "click", function() {
                            //map.setCenter(marker.getLatLng(), map.getBoundsZoomLevel(bounds) + 1);
                            map.setCenter(marker.getLatLng(), 15);
                            if (html) {
                                if (html[index]) {
                                    marker.openInfoWindowHtml($(html[index]).html());
                                } else {
                                    self.trace(index + ": " + addresses[index]);
                                }
                            }
                        });
                    }
                }

                // add marker to array and display
                markers[index] = marker;
                processed.push(index);
                
              
               
            
            },


            // geocode(index, count)
            geocode: function(index, count) {
                geo = (geo == null) ? new GClientGeocoder() : geo;
                failed = (failed == null) ? new Array() : failed;
                processed = (processed == null) ? new Array() : processed;
                
                var doCheck = false;
                if (addresses && index >= 0) {
                    self.trace("processing address: [" + addresses[index] + "] (" + index + ")");
                    markers = markers || new Array();
                    self.trace("attempt no: " + count );
                    // safer way of geocoding - avoids G_GEO_TOO_MANY_QUERIES
                    geo.getLocations(addresses[index], function(response) {
                        var statuscode = response.Status.code;
                        
                        if (statuscode == G_GEO_SUCCESS) {
                            // success!
                            self.trace(response.Placemark);
                            
                            self.addPlaceholder(index, geo, failed, processed, response.Placemark[0].Point.coordinates[1], response.Placemark[0].Point.coordinates[0]);

                            
                            /*
                            // TODO: extra from here for latlong addresses
                            var point = new GLatLng(response.Placemark[0].Point.coordinates[1], response.Placemark[0].Point.coordinates[0], true);

                            // extend bounds
                            bounds = bounds || new GLatLngBounds();
                            bounds.extend(point); self.trace("bounds extended");

                            // marker
                            var marker = self.createMarker(index, point);
                            self.trace(marker); self.trace("marker created");

                            // marker events
                            GEvent.addListener(marker, "click", function() {
                                map.setCenter(marker.getLatLng(), map.getBoundsZoomLevel(bounds) + 1);
                                if (html) {
                                    if (html[index]) {
                                        marker.openInfoWindowHtml($(html[index]).html());
                                    } else {
                                        self.trace(index + ": " + addresses[index]);
                                    }
                                }
                            });

                            // external link events
                            if (anchor) {
                                if (anchor[index]) {
                                    GEvent.addDomListener(anchor[index], "click", function() {
                                        map.setCenter(marker.getLatLng(), map.getBoundsZoomLevel(bounds) + 1);
                                        if (html) {
                                            if (html[index]) {
                                                marker.openInfoWindowHtml($(html[index]).html());
                                            } else {
                                                self.trace(index + ": " + addresses[index]);
                                            }
                                        }
                                    });
                                }
                            }

                            // add marker to array and display
                            markers[index] = marker;
                            processed.push(index);

                            if (processed.length + failed.length == addresses.length) {
                                self.displayMarkers();
                            }
                            //map.addOverlay(marker);

                            // onMarkerLoaded
                            if (fireEvent(conf, self.onMarkerLoaded, self, index) === false) {
                                return self;
                            }
                            
                            */
                            doCheck = true;
                        } else {
                            if (statuscode == G_GEO_TOO_MANY_QUERIES) {
                                // retry again after a short while as long as count < retryCount
                                /*
                                if (count < 3) {
                                    var delay = 6000;
                                    self.trace("index " + index + " will begin retry in " + delay + "ms")
                                    setTimeout(function() {
                                        self.geocode(index,count++);
                                    }, delay);
                                } else {
                                */
                                    self.trace("G_GEO_TOO_MANY_QUERIES for index -> " + index )
                                    failed.push(index);
                                    markers[index] = null;
                                    doCheck = true;
                                //}
                            } else {
                                if (statuscode == G_GEO_SERVER_ERROR) {
                                     //if (count < 3) {
                                     //   self.trace("retrying for 500 error: " + addresses[index]);
                                     //    setTimeout(function() {
                                     //      self.geocode(index,count++);
                                     //   }, 1000);
                                     //}
                                     //else{                                     
                                      failed.push(index);
                                      markers[index] = null;
                                      doCheck = true;
                                     //}
                                }
                                else {
                                    self.trace("unknown error code: " + statuscode + " - " + addresses[index]);
                                    failed.push(index);
                                    markers[index] = null;
                                    doCheck = true;
                                }
                            }
                        }
                        
                        if (doCheck) {
                            self.trace("processed.length - " + processed.length);
                            self.trace("failed.length - " + failed.length);
                            self.trace("addresses.length - " + addresses.length);
                            
                            if (processed.length + failed.length == addresses.length) {
                                self.displayMarkers();
                            }
                            //map.addOverlay(marker);

                            // onMarkerLoaded
                            if (fireEvent(conf, self.onMarkerLoaded, self, index) === false) {
                                return self;
                            }                   
                        }
                        else {
                            self.trace("dont check for " + addresses[index] + " ( " + index + " )");
                        }
                    });
                }
            },

            displayMarkers: function() {
                self.trace("displaying markers...");
                if (map && markers && markers.length > 0) {
                    var i = 0;
                    for (i = 0; i < markers.length; i++) {
                        if (markers[i]) {
                            map.addOverlay(markers[i]);
                        }
                    }
                }
            },

            // onMarkerLoaded(index)
            //     internal function : DO NOT MODIFY
            onMarkerLoaded: function(index) {
                // set map bounds and zoom level to optimal level so all marker can fit
                return self.optimiseZoomLevel(index);
            },

            // optimiseZoomLevel()
            optimiseZoomLevel: function(index) {
                try {
                    self.trace("trying to optimise zoom level");
                    if (bounds && markers.length > 0) {
                        map.setCenter(bounds.getCenter());
                        self.trace("optimal zoom: " + map.getBoundsZoomLevel(bounds));
                        map.setZoom(map.getBoundsZoomLevel(bounds));
                    }

                    return true;
                } catch (e) {
                    self.trace(e);
                    return false;
                }
            },

            // createMarker(index, point)
            //     index:    (number) index of the marker (also used to generate a letter)
            //     point:    (GLatLng) latitude and longitude of the marker
            createMarker: function(index, point) {
                if (labels) {
                    self.trace("creating lettered markers");
                    return createLetteredMarker(index, point);
                } else if (icons) {
                    self.trace("creating icon markers. image: " + icons[index]);
                    return self.createIconMarker(index, point);
                } else {
                    self.trace("creating generic marker");
                    return self.createGenericMarker(index, point);
                }
            },

            // createIconMarker(index, point): see createMarker
            createIconMarker: function(index, point) {
                var baseIcon = new GIcon();
                var image = new Image();
                baseIcon.image = icons[index]; image.src = icons[index];
                baseIcon.iconSize = new GSize(50, 50);
                baseIcon.iconAnchor = new GPoint(25, 50);
                baseIcon.infoWindowAnchor = new GPoint(15, 15);

                var markerOptions = {
                    icon: baseIcon,
                    bouncy: true
                };

                var marker = new GMarker(point, markerOptions);
                return marker;
            },

            // createGenericMarker(index, point): see createMarker
            createGenericMarker: function(index, point) {
                var baseIcon = new GIcon(G_DEFAULT_ICON);
                baseIcon.shadow = "http://www.google.com/mapfiles/shadow50.png";
                baseIcon.iconSize = new GSize(20, 34);
                baseIcon.shadowSize = new GSize(37, 34);
                baseIcon.iconAnchor = new GPoint(9, 34);
                baseIcon.infoWindowAnchor = new GPoint(9, 2);

                var marker = new GMarker(point);
                return marker;
            },

            // createLetteredMarker(index, point): see createMarker
            createLetteredMarker: function(index, point) {
                var baseIcon = new GIcon(G_DEFAULT_ICON);
                baseIcon.shadow = "http://www.google.com/mapfiles/shadow50.png";
                baseIcon.iconSize = new GSize(20, 34);
                baseIcon.shadowSize = new GSize(37, 34);
                baseIcon.iconAnchor = new GPoint(9, 34);
                baseIcon.infoWindowAnchor = new GPoint(9, 2);

                // lettered marker which starts at "A" and wraps at "Z"
                var range = "Z".charCodeAt(0) - "A".charCodeAt(0) + 1;
                var letter = String.fromCharCode("A".charCodeAt(0) + (index % range));
                var iconType = new GIcon(baseIcon);
                iconType.image = "http://www.google.com/mapfiles/marker" + letter + ".png";

                var markerOptions = {
                    icon: iconType,
                    bouncy: true
                };

                var marker = new GMarker(point, markerOptions);
                return marker;
            },

            // trace(arg, [args...]) : print everything in the arguments array
            trace: function() {
                if (!debug) return;

                var caller = arguments.caller || "self";
                for (var i = 0; i < arguments.length; i++) {
                    var argument = arguments[i]; // print object as it is
                    var line = argument;
                    try {
                        // Firefox, Safari, Opera
                        console.log(line);
                    } catch (error) {
                        // fails gracefully on IE, Chrome
                        alert(line);
                    }
                }
            }
        });

        function load() {
            self.initialise();
            return self;
        }

        load();
    }


    // jQuery plugin implementation
    jQuery.prototype.googlemap = function(conf) {
        // already constructed --> return API
        var api = this.eq(typeof conf == 'number' ? conf : 0).data("googlemap");
        if (api) { return api; }

        var opts = {
            latitude: -23,
            longitude: 133,
            zoom: 4,
            maptype: "street",
            labels: true,
            controls: true,
            draggable: false,
            html: null,
            anchor: null,
            addresses: null,
            icons: null,
            debug: false
        };

        $.extend(opts, conf);

        this.each(function() {
            var el = new Googlemap($(this), opts);
            $(this).data("googlemap", el);
        });

        return this;
    };

})(jQuery);

