function GMapShape() { }
GMapShape.prototype = new google.maps.Overlay();

GMapShape.prototype.initialize = function(map)
{
    this.map_ = map;

    if (! document.createElementNS)
    {
        this.unsupported_ = true;
        return;
    }

    this.svg_ = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    this.svg_.style.position = 'absolute';

    this.g_ = document.createElementNS("http://www.w3.org/2000/svg", "g");
    this.g_.setAttribute("class", this.type());
    this.svg_.appendChild(this.g_);

    this.initShape();

    this.map_.getPane(G_MAP_MAP_PANE).appendChild(this.svg_);

    this.redraw(true);
};

GMapShape.prototype.initShape = function()
{
    throw new Error("GMapShape.initShape() must be implemented.");
};

GMapShape.prototype.remove = function()
{
    if (! this.unsupported_)
        this.svg_.parentNode.removeChild(this.svg_);
};

GMapShape.prototype.copy = function()
{
    throw new Error("GMapShape.copy() must be implemented.");
};

GMapShape.prototype.getBounds = function()
{
    throw new Error("GMapShape.getBounds() must be implemented.");
};

GMapShape.prototype.type = function()
{
    throw new Error("GMapShape.type() must be implemented.");
};

GMapShape.prototype.getCentre = function()
{
    var bounds = this.getBounds();
    var ne = bounds.getNorthEast();
    var sw = bounds.getSouthWest();
    return new google.maps.LatLng((ne.lat() + sw.lat())/ 2,
                       (ne.lng() + sw.lng()) / 2);
};

// Get the approximate scale of the map in pixels per metre
GMapShape.prototype.getScale = function()
{
    var mb = this.map_.getBounds();
    var ne = new GeoPoint(mb.getNorthEast().lat(), mb.getNorthEast().lng());
    var sw = new GeoPoint(mb.getSouthWest().lat(), mb.getSouthWest().lng());
    var realDistance = ne.distanceTo(sw);

    var sz = this.map_.getSize();
    var pixelDistance = Math.sqrt(sz.width * sz.width + sz.height * sz.height);

    return pixelDistance / (realDistance ? realDistance : 1);
};

GMapShape.prototype.redraw = function(force) {
    // We only need to redraw if the coordinate system has changed
    if (!force || !document.createElementNS) return;

    var bounds = this.getBounds();
    var ne = bounds.getNorthEast();
    var sw = bounds.getSouthWest();
    var nw = new google.maps.LatLng(ne.lat(), sw.lng());
    this.origin_ = this.map_.fromLatLngToDivPixel(nw);
    this.origin_.x -= 100;
    this.origin_.y -= 100;
    var width = this.map_.fromLatLngToDivPixel(ne).x - this.origin_.x + 100;
    var height = this.map_.fromLatLngToDivPixel(sw).y - this.origin_.y + 100;

    this.svg_.style.left = this.origin_.x + 'px';
    this.svg_.style.top = this.origin_.y + 'px';
    this.svg_.style.width = width + 'px';
    this.svg_.style.height = height + 'px';

    this.redrawShape();
};

// The SVG canvas does not draw beyond a certain distance in Firefox, and it
// does not draw in negative coordinates on either axis.  However, Google
// Maps uses negative coordinates.  Therefore, the SVG canvas has to be moved
// relative to the Google Maps coordinate system.  This method takes any
// offset into account.
GMapShape.prototype.fromLatLngToDivPixel = function(point)
{
    var pt = this.map_.fromLatLngToDivPixel(point);
    return new GPoint(pt.x - this.origin_.x, pt.y - this.origin_.y);
};

GMapShape.prototype.redrawShape = function()
{
    throw new Error("GMapShape.redrawShape() must be implemented.");
};


function GMapPoint(centre)
{
    this.centre_ = centre;
};
GMapPoint.prototype = new GMapShape();

GMapPoint.prototype.initShape = function()
{
    this.dot_ = document.createElementNS("http://www.w3.org/2000/svg", "circle");
    this.dot_.setAttribute('r', '2px');
    this.dot_.setAttribute('class', 'dot');
    this.g_.appendChild(this.dot_);
};

GMapPoint.prototype.copy = function()
{
    return new GMapPoint(this.centre_);
};

GMapPoint.prototype.type = function()
{
    return "GMapPoint";
};

GMapPoint.prototype.setGMapPoint = function(pt)
{
    this.centre_ = pt;
    this.redraw(true);
};

GMapPoint.prototype.getBounds = function()
{
    return new google.maps.LatLngBounds(this.centre_, this.centre_);
};

GMapPoint.prototype.redrawShape = function()
{
    var cpx = this.fromLatLngToDivPixel(this.centre_);
    this.dot_.setAttribute("cx", cpx.x + "px");
    this.dot_.setAttribute("cy", cpx.y + "px");
};


function GMapUncertainty(centre, unc)
{
    GMapPoint.apply(this, [centre]);
    this.uncertainty_ = unc;
};
GMapUncertainty.prototype = new GMapPoint();

GMapUncertainty.prototype.initShape = function()
{
    this.unc_= document.createElementNS("http://www.w3.org/2000/svg", "circle");
    this.unc_.setAttribute('class', 'uncertainty');
    this.g_.appendChild(this.unc_);
    GMapPoint.prototype.initShape.apply(this, []);
};

GMapUncertainty.prototype.getBounds = function()
{
    var incr = 180*Math.acos(1-Math.pow(this.uncertainty_/6378137, 2)/2)/Math.PI;
    var sw = new google.maps.LatLng(this.centre_.lat() - incr, this.centre_.lng() - incr, false);
    var ne = new google.maps.LatLng(this.centre_.lat() + incr, this.centre_.lng() + incr, false);
    return new google.maps.LatLngBounds(sw,  ne);
};

GMapUncertainty.prototype.redrawShape = function()
{
    var cpx = this.fromLatLngToDivPixel(this.centre_);
    this.unc_.setAttribute("cx", cpx.x + "px");
    this.unc_.setAttribute("cy", cpx.y + "px");

    var size = this.getScale() * this.uncertainty_;
    this.unc_.setAttribute("r", size + "px");

    GMapPoint.prototype.redrawShape.apply(this, []);
};

function GMapEllipse(centre, maj, min, phi)
{
    GMapUncertainty.apply(this, [centre, maj]);
    this.maj_ = maj;
    this.min_ = min ? min : maj;
    this.phi_ = phi ? phi : 0;
};
GMapEllipse.prototype = new GMapUncertainty();

GMapEllipse.prototype.initShape = function()
{
    this.ellipse_= document.createElementNS("http://www.w3.org/2000/svg", "ellipse");
    this.g_.appendChild(this.ellipse_);
    GMapUncertainty.prototype.initShape.apply(this, []);
};

GMapEllipse.prototype.copy = function()
{
    return new GMapEllipse(this.centre_, this.maj_,
                           this.min_, this.phi_);
};

GMapEllipse.prototype.type = function()
{
    return "GMapEllipse";
};

GMapEllipse.prototype.redrawShape = function()
{
    GMapUncertainty.prototype.redrawShape.apply(this, []);

    var cpx = this.fromLatLngToDivPixel(this.centre_);
    this.g_.setAttribute("transform", "rotate(" + this.phi_ + "," + cpx.x + "," + cpx.y + ")");
    this.ellipse_.setAttribute("cx", cpx.x + "px");
    this.ellipse_.setAttribute("cy", cpx.y + "px");

    var scale = this.getScale();

    this.ellipse_.setAttribute("ry", (this.maj_ * scale) + "px");
    this.ellipse_.setAttribute("rx", (this.min_ * scale) + "px");
};

function GMapPolygon(points, centroid, uncertainty)
{
    GMapUncertainty.apply(this, [centroid, uncertainty]);
    this.points_ = points;
};
GMapPolygon.prototype = new GMapUncertainty();

GMapPolygon.prototype.initShape = function()
{
    this.path_ = document.createElementNS("http://www.w3.org/2000/svg", "path");
    this.g_.appendChild(this.path_);

    GMapUncertainty.prototype.initShape.apply(this, []);
};

GMapPolygon.prototype.copy = function()
{
    return new GMapPolygon(this.points_, this.focus_);
};

GMapPolygon.prototype.type = function()
{
    return "GMapPolygon";
};

GMapPolygon.prototype.redrawShape = function()
{
    GMapUncertainty.prototype.redrawShape.apply(this, []);

    var px = this.fromLatLngToDivPixel(this.points_[this.points_.length - 1]);
    var d = 'M ' + px.x + ',' + px.y;
    for (var i in this.points_)
    {
        i = parseInt(i); if (isNaN(i)) { continue; }

        px = this.fromLatLngToDivPixel(this.points_[i]);
        d += ' L ' + px.x + ',' + px.y;
    }
    d += ' z';
    this.path_.setAttribute("d", d);
};

function GMapArcBand(_anchor, _inner, _outer, _start, _opening,
		     _centroid, _uncertainty)
{
    GMapUncertainty.apply(this, [_centroid, _uncertainty]);
    this.anchor = _anchor;
    this.inner = _inner;
    this.outer = _outer;
    this.start = _start;
    this.opening = _opening;
};
GMapArcBand.prototype = new GMapUncertainty();

GMapArcBand.prototype.initShape = function()
{
    this.path = document.createElementNS("http://www.w3.org/2000/svg", "path");
    this.g_.appendChild(this.path);

    GMapUncertainty.prototype.initShape.apply(this, []);
};

GMapArcBand.prototype.copy = function()
{
    return new GMapArcBand(this.anchor, this.inner, this.outer, this.start, this.centroid_, this.opening_);
};

GMapArcBand.prototype.type = function()
{
    return "GMapArcBand";
};

GMapArcBand.prototype.redrawShape = function()
{
    GMapUncertainty.prototype.redrawShape.apply(this, []);

    var s = this.start * Math.PI / 180;
    var e = (this.start + this.opening) * Math.PI / 180;
    var ir = this.getScale() * this.inner;
    var or = this.getScale() * this.outer;

    var px = this.fromLatLngToDivPixel(this.anchor);
    var inner1x = px.x + ir * Math.sin(s);
    var inner1y = px.y - ir * Math.cos(s);
    var d = 'M ' + inner1x + ',' + inner1y;

    var inner2x = px.x + ir * Math.sin(e);
    var inner2y = px.y - ir * Math.cos(e);
    d += ' A ' + ir + ',' + ir + ',0,'
         + (this.opening > 180 ? 1 : 0) + ',1,'
         + inner2x + ',' + inner2y;


    var outer2x = px.x + or * Math.sin(e);
    var outer2y = px.y - or * Math.cos(e);
    d += ' L ' + outer2x + ',' + outer2y;

    var outer1x = px.x + or * Math.sin(s);
    var outer1y = px.y - or * Math.cos(s);
    d += ' A ' + or + ',' + or + ',0,'
         + (this.opening > 180 ? 1 : 0) + ',0,'
         + outer1x + ',' + outer1y;

    d += ' z';

    this.path.setAttribute("d", d);
};
