function ExtMarker(opts) {
    var infoWindow = null;
    var ib = null;
    this.id = opts.id;
    
    this.getInfoWindow = function() {
        return infoWindow;
    }
    
    this.setInfoWindow = function(iw) {
        infoWindow = iw;
    }
    
    this.showInfoWindow = function() {
        if (infoWindow !== null) infoWindow.open(this.getMap(), this);
    }
    
    this.hideInfoWindow = function() {
        if (infoWindow !== null) infoWindow.close();
    }
    
    this.showLabel = function() {
        if(ib !== null) ib.show();
    }
    
    this.hideLabel = function() {
        if(ib !== null) ib.hide();
    }
    
    if (opts.icon !== false) {
        google.maps.Marker.call(this, opts);
        
        if(opts.onclick !== undefined) {
            google.maps.event.addListener(this, 'click', opts.onclick);
        }
        if (opts.infoWindow !== undefined) {
            infoWindow = new google.maps.InfoWindow(opts.infoWindow);
            
            if(opts.onclick === undefined) {
                google.maps.event.addListener(this, 'click', function() {
                    infoWindow.open(this.getMap(), this);
                });
            }
        }
    }
    if(opts.label !== undefined) {
        var boxText = document.createElement("div");
        boxText.style.cssText = opts.label.boxTextStyle !== undefined ? opts.label.boxTextStyle : "border-radius: 0 3px 3px 3px; -moz-border-radius: 0 3px 3px 3px; font-size: 12px; border: 1px solid black; margin-top: 8px; background: yellow; padding: 3px;";
        if(opts.label.onclick !== undefined) {
            boxText.onclick = opts.label.onclick;
        }
        boxText.innerHTML = opts.label.content;
        opts.label.content = boxText;
	var defOptions = {
            standardBox: true,
            disableAutoPan: false,
            maxWidth: 0,
            pixelOffset: new google.maps.Size(0, 0),
            zIndex: null,
            boxStyle: { 
                backgroundImage: "url(" + (opts.label.arrowImage === undefined ? 'images/tipbox.gif' : opts.label.arrowImage + ")"),
                backgroundRepeat: "no-repeat",
		opacity: 0.9,
		width: "200px"
            },
            closeBoxMargin: "10px 2px 2px 2px",
            closeBoxURL: (opts.label.closeBtn !== undefined && opts.label.closeBtn === false) ? "" : "http://www.google.com/intl/en_us/mapfiles/close.gif",
            infoBoxClearance: new google.maps.Size(1, 1),
            isHidden: false,
            pane: "floatPane",
            enableEventPropagation: false
	};
        var options = $.extend({}, defOptions, opts.label);

        if(opts.icon === false) {
            options.position = opts.position;
        }
        ib = new InfoBox(options);
        if(opts.icon !== false) {
            ib.open(this.getMap(), this);
        }
        else {
             ib.open(opts.map);
             this.getPosition = function() {
                 return ib.getPosition();
            }
        }
    }
}
ExtMarker.prototype = google.maps.Marker.prototype;
ExtMarker.prototype.constructor = ExtMarker;

function ExtMap(mapDiv, opts) {
    google.maps.Map.call(this, mapDiv, opts);
    var directionsService;
    var renderer;
    var geocoder;
    var markers = Array();
    var panelElem = opts.panel || null;
    var mapOpts = opts;
    var map = this;
    var labelBoxTextStyle = opts.labelBoxTextStyle;
    var labelArrowImage = opts.labelArrowImage;

    this.markerFromPostcode = function (opts) {
        if(geocoder == undefined) geocoder = new google.maps.Geocoder();
        if(opts.postcode != undefined) {
            var address = opts.postcode + ', ' + (opts.country == undefined ? 'UK' : opts.country);
            var map = this;
            geocoder.geocode( {'address': address}, function(results, status) {
                if (status == google.maps.GeocoderStatus.OK) {
                    opts.position =  results[0].geometry.location;
                    map.addMarker(opts);
                }
            });
        }
    }
    this.showAllInfoWindows = function() {
        for(i in markers) {
            markers[i].showInfoWindow();
        }
    }
    this.hideAllInfoWindows = function() {
        for(i in markers) {
            markers[i].hideInfoWindow();
        }
    }
    this.hideAllLabels = function() {
        for(i in markers) {
            markers[i].hideLabel();
        }
    }
    this.showAllLabels = function() {
        for(i in markers) {
            markers[i].showLabel();
        }
    }
    this.addMarker = function(opts) {
        if(labelBoxTextStyle !== undefined && opts.label !== undefined && opts.label.boxTextStyle === undefined) opts.label.boxTextStyle = labelBoxTextStyle;
        if(labelArrowImage !== undefined && opts.label !== undefined && opts.label.arrowImage === undefined) opts.label.arrowImage = labelArrowImage;
        if(opts.postcode !== undefined && opts.position == undefined) {
            this.markerFromPostcode(opts);
            return null;
        }
        if (opts.lat !== undefined && opts.lng !== undefined)
            opts.position = new google.maps.LatLng(opts.lat, opts.lng);
        if(opts.icon == undefined && mapOpts.defaultIcon != undefined)
            opts.icon = mapOpts.defaultIcon;
        opts.map = this;
        var marker = new ExtMarker(opts);
        markers.push(marker);
        return marker;
    }
    this.getMarkerById = function(id) {
        for(i in markers) {
            if(markers[i].id == id)
                return markers[i];
        }
        return null;
    }
    this.route = function(start, end, options) {
        if(directionsService == undefined) {
            directionsService = new google.maps.DirectionsService();
        }
        if(renderer == undefined) {
            renderer = new google.maps.DirectionsRenderer();
            renderer.setMap(this);
            renderer.setPanel(panelElem);
        }
        var origin, destination;
        if(start.postcode != undefined) {
            origin = start.postcode + ', ' + (start.country == undefined ? 'UK' : start.country);
        }
        else {
            originMarker = this.getMarkerById(start);
            if (originMarker == null) return;
            origin = originMarker.getPosition();
        }
        if(end.postcode !== undefined) {
            destination = end.postcode + ', ' + (end.country == undefined ? 'UK' : end.country);
        }
        else {
            destMarker = this.getMarkerById(end);
            if (destMarker == null) return;
            destination = destMarker.getPosition();
        }
        var request = {
            origin: origin, 
            destination: destination,
            travelMode: google.maps.DirectionsTravelMode.DRIVING
        };
        this.routeRespHandler = function(response, status) { 
            if (status == google.maps.DirectionsStatus.OK) {
                if(options === undefined) {
                    renderer.setMap(this);
                    renderer.setPanel(panelElem);
                }
                else if(options.draw === false)
                    renderer.setMap(null);
                if(options !== undefined && options.panel !== undefined){
                    renderer.setPanel($(options.panel).get(0));}
                renderer.setDirections(response);
            }
        }
        directionsService.route(request, this.routeRespHandler);
    }
    this.moveTo = function(arg) {
        var argType = typeof(arg)
        if (argType == 'number')  {
            marker = this.getMarkerById(arg);
            if (marker == null) return;
            this.panTo(marker.getPosition());
        }
        else if(argType === 'object' && arg.lat !== undefined && arg.lng !== undefined) {
            this.panTo(new google.maps.LatLng(arg.lat, arg.lng));
        }
    }
    this.streetview = function(arg) {
        var sv = this.getStreetView();
        if (arg.lat !== undefined && arg.lng !== undefined) {
            latlng = new google.maps.LatLng(arg.lat, arg.lng);
            sv.setPosition(latlng);
        }
        else if (typeof(arg) == 'number') {
            var m = this.getMarkerById(arg);
            if(m !== null) sv.setPosition(m.getPosition());
        }
        sv.setVisible(true);
        return sv;
    }
    this.centerView = function() {
        var minLng = 180;
        var maxLng = -180;
        var minLat = 90;
        var maxLat = -90;
        for (var i in markers) {
            var mPos = markers[i].getPosition();
            var mLng = mPos.lng();
            var mLat = mPos.lat();
            if(mLng < minLng) minLng = mLng;
            if(mLng > maxLng) maxLng = mLng;
            if(mLat < minLat) minLat = mLat;
            if(mLat > maxLat) maxLat = mLat;
        }
        var swCorner = new google.maps.LatLng(minLat, minLng);
        var neCorner = new google.maps.LatLng(maxLat, maxLng);
        var bounds = new google.maps.LatLngBounds(swCorner, neCorner);
        this.fitBounds(bounds);
    }
}
ExtMap.prototype = google.maps.Map.prototype;
ExtMap.prototype.constructor = ExtMap;

(function($) {
    
	$.fn.gMap = function(options) {
            var defaultOpts = {
                zoom : 7,
                mapTypeId: google.maps.MapTypeId.ROADMAP,
                defaultIcon : 'http://www.google.com/mapfiles/marker.png',
                markers : []
            };
            var settings = $.extend({}, defaultOpts, options);
            var markers = settings.markers;      
            
            if(settings.markersSrc !== undefined && settings.markersSrc.selector !== undefined) {
                $(settings.markersSrc.selector).each(function() {
                    eval('var object = ' + $(this).attr(settings.markersSrc.attr || 'rel'));                  
                    markers.push(object);
                });
            }
            
            if (settings.lat !== undefined && settings.lng !== undefined) {
                settings.center = new google.maps.LatLng(settings.lat, settings.lng);
            }
            else {
                var minLng = 180;
                var maxLng = -180;
                var minLat = 90;
                var maxLat = -90;
                for (var i in markers) {
                    var m = markers[i];
                    if(m.lat !== undefined && m.lng !== undefined) {
                        var mLng = m.lng;
                        var mLat = m.lat;
                        if(mLng < minLng) minLng = mLng;
                        if(mLng > maxLng) maxLng = mLng;
                        if(mLat < minLat) minLat = mLat;
                        if(mLat > maxLat) maxLat = mLat;
                    }
                }
                var centerLat = (minLat + maxLat) / 2;
                var centerLng = (minLng + maxLng) / 2;
                settings.center = new google.maps.LatLng(centerLat, centerLng);
            }
            if(settings.panel !== undefined) 
                settings.panel = $(settings.panel).get(0);
            
            var map = new ExtMap(this.get(0), settings);
            
            for (i in markers) {
                object = markers[i];
                if(settings.labelArrowImage !== undefined && object.label !== undefined && object.label.arrowImage === undefined)
                    object.label.arrowImage = settings.labelArrowImage;
                if(settings.labelBoxTextStyle !== undefined && object.label !== undefined && object.label.boxTextStyle === undefined)
                    object.label.boxTextStyle = settings.labelBoxTextStyle;  
                map.addMarker(object)
            }
            
            if(settings.markersSrc !== undefined && settings.markersSrc.selector !== undefined) {
                $(settings.markersSrc.selector).each(function() {
                    eval('var object = ' + $(this).attr(settings.markersSrc.attr || 'rel'));                  
                    if(settings.markersSrc.onclick !== undefined && map[settings.markersSrc.onclick] !== undefined) {
                        $(this).click(function() {
                            map[settings.markersSrc.onclick].call(map, object.id);
                            return false;
                        });
                    }
                });
            }
            return map;
	}
        
        $.fn.streetview = function(options) {
            if(options.lat !== undefined && options.lng !== undefined) {
                options.position = new google.maps.LatLng(options.lat, options.lng);
            }
            var panorama = new google.maps.StreetViewPanorama(this.get(0), options);
            panorama.setVisible(true);
            return panorama;
        }  
})(jQuery);
