function createMarker(point, name, description) {
    var marker = new GMarker(point);
    GEvent.addListener(marker, "click", function() {
//        marker.openInfoWindowHtml("<h1>" + name + "</h1>" + description);
        marker.openInfoWindowHtml(description);
    })
    return marker;
}

function KMLNSResolver(prefix) {
    if(prefix == 'kml') return "http://earth.google.com/kml/2.0";
    return null;
}

//Represents a KML feed. Used internally by KMLHandler.
function KMLFeed(map, url) {
    this.map = map;
    this.url = url;
    this.overlays = new Array();
    this.request = undefined;

    this.handleKML = function(data, response) {
        //Delete existing overlays
        for(var i in this.overlays) {
            this.overlays[i].destroy();
            map.removeOverlay(this.overlays[i]);
        }
        this.overlays = new Array();

        //Populate with elements in the updated feed
        var doc = GXml.parse(data);

        var bounds = undefined;
        var document = doc.documentElement.getElementsByTagName("Document")[0];
        var folders = document.getElementsByTagName("Folder");
        for(var f = 0; f < folders.length; f++) {
            placemarks = folders[f].getElementsByTagName("Placemark");
            for(var i = 0; i < placemarks.length; i++) {
                var point = placemarks[i].getElementsByTagName("Point")[0];
                var coords = point.getElementsByTagName("coordinates")[0].childNodes[0].nodeValue;
                coords = coords.split(",");
                var name = placemarks[i].getElementsByTagName("name")[0].childNodes[0].nodeValue;
                var description = placemarks[i].getElementsByTagName("description")[0].childNodes[0].nodeValue;

                var point = new GLatLng(parseFloat(coords[1]), parseFloat(coords[0]));
//                var marker = new KMLMarker(point, name, description);
                var marker = createMarker(point, name, description);
                map.addOverlay(marker);
                if (!bounds) {
                    bounds = GBounds(point);
                } else {
                    bounds.extend(point);
                }
                this.overlays.push(marker);
            }
        }
        map.setCenter(new GLatLng((bounds.maxX-bounds.minX)/2, (bounds.maxY-bounds.minY)/2));
    }

    //Triggers whenever the map is moved or zoomed.
    //Also manually invoked when the feed is first created to initially populate
    //the feed.
    this.onMapChange = function() {
        //Trigger fetching the new map
        if(this.request != undefined) {
            this.request.abort();
        }

        url = this.url;
        bounds = map.getBounds();

        if(url.indexOf("?") == -1) {
            url = url + "?BBOX=" + bounds.minX + "," + bounds.minY + "," + bounds.maxX + "," + bounds.maxY;
        } else {
            url = url + "&BBOX=" + bounds.minX + "," + bounds.minY + "," + bounds.maxX + "," + bounds.maxY;
        }

        var _this = this;
        GDownloadUrl(url, _this.handleKML);
    }

    this.destroy = function() {
        GEvent.removeListener(this.moveendListener);

        for(var i in this.overlays) this.overlays[i].destroy();
    }

    //Fetch the feed for the first time
    this.onMapChange();

    //Add event handlers
    this.moveendListener = GEvent.bind(map, 'moveend', this, this.onMapChange);
}

function getChildrenByTagName(node,tag) {
    var l = new Array();
    for(var i = 0; i < node["chd"].length; i++) {
        var child = node["chd"][i];
        if (child["tag"] == tag) {
            l.push(child);
        }
    }
    return l;
}

//Represents a JSON feed. Used internally by KMLHandler.
function JSONFeed(map, url) {
    this.map = map;
    this.url = url;
    this.request = undefined;

    this.handleJSON = function(data, response) {
        //Populate with elements in the updated feed
        var doc = eval(data);

        var bounds = undefined;
        var document = doc[0]["chd"][0];
        var folders = getChildrenByTagName(document,"Folder");
        for(var f = 0; f < folders.length; f++) {
            placemarks = getChildrenByTagName(folders[f],"Placemark");
            for(var i = 0; i < placemarks.length; i++) {
                var point = getChildrenByTagName(placemarks[i],"Point")[0];
                var coords = getChildrenByTagName(point,"coordinates")[0]["val"];
                coords = coords.split(",");
                var name = getChildrenByTagName(placemarks[i],"name")[0]["val"];
                var description = getChildrenByTagName(placemarks[i],"description")[0]["val"];

                var point = new GLatLng(parseFloat(coords[1]), parseFloat(coords[0]));
//                var marker = new KMLMarker(point, name, description);
                var marker = createMarker(point, name, description);
                map.addOverlay(marker);
                if (bounds == undefined) {
                    bounds = GBounds(point);
                } else {
                    bounds.extend(point);
                }
            }
        }
        if (bounds != undefined) {
            map.setCenter(new GLatLng((bounds.maxX-bounds.minX)/2, (bounds.maxY-bounds.minY)/2));
        }
    }

    //Triggers whenever the map is moved or zoomed.
    //Also manually invoked when the feed is first created to initially populate
    //the feed.
    this.onMapChange = function() {
        //Trigger fetching the new map
        if(this.request != undefined) {
            this.request.abort();
        }

        url = this.url;
        bounds = map.getBounds();

        if(url.indexOf("?") == -1) {
            url = url + "?BBOX=" + bounds.minX + "," + bounds.minY + "," + bounds.maxX + "," + bounds.maxY;
        } else {
            url = url + "&BBOX=" + bounds.minX + "," + bounds.minY + "," + bounds.maxX + "," + bounds.maxY;
        }

        var _this = this;
        GDownloadUrl(url, _this.handleJSON);
    }

    this.destroy = function() {
        GEvent.removeListener(this.moveendListener);
    }

    //Fetch the feed for the first time
    this.onMapChange();

    //Add event handlers
    this.moveendListener = GEvent.bind(map, 'moveend', this, this.onMapChange);
}

//A KMLHandler handles (fetching, updating) KML feeds for a map
function KMLHandler(map) {
    this.map = map;
    this.feeds = [];

    this.addFeed = function(url) {
        //Add the feed to the feeds array
        this.feeds[url] = new KMLFeed(this.map, url);
    }

    this.addKML = function(url) {
        //Add the feed to the feeds array
        this.feeds[url] = new KMLFeed(this.map, url);
    }

    this.addJSON = function(url) {
        //Add the feed to the feeds array
        this.feeds[url] = new JSONFeed(this.map, url);
    }

    this.removeFeed = function(url) {
        //Remove the feed from the feeds array
        this.feeds[url].destroy();
        delete this.feeds[url];
    }
}
