(function($) {
    var map;
    var mapView = { type: null, zoom: null, lat: null, lng: null, coords: null };
    var markerArray = new Array();
    var totalMarkers = 0;
    var canFireMapMoveEvent = true;
    // -------------------------------------------------------
    // Settings
    // -------------------------------------------------------
    var currentSettings = {
        initLat: -34.9294304855664525, // sets the initial lattertude for the center of the map.
        initLng: 138.60162734985351, // sets the initial longitude for the center of the map.
        initZoom: 4, // sets the initial zoom level for the map.
        hasOverviewMap: true,

        hideMarkersOnLoad: false,

        iconBaseHref: "",
        iconDefaultImage: "iconr.png",
        iconDefaultShadow: "shadow.png",
        geoCountryCode: null,
        minGeoSearchZoom: 10,
        //callbacks
        markerSelected: null,
        afterMapMove: null
    };

    // jQuery extension function. A paramater object could be used to overwrite the default settings and load themap with a specific XML
    $.fn.gMapViewer = function(options, mapXML) {
        return this.each(function() {

            if (options) {
                $.extend(currentSettings, options);
            }

            if (GBrowserIsCompatible()) {
                map = new GMap2(this);
                map.setCenter(new GLatLng(currentSettings.initLat, currentSettings.initLng), currentSettings.initZoom);
                map.addControl(new GSmallMapControl());
                map.addControl(new GMenuMapTypeControl());

                if (currentSettings.hasOverviewMap) {
                    map.addControl(new GOverviewMapControl());
                }

                map.enableScrollWheelZoom();
                map.enableContinuousZoom();
                map.enableDoubleClickZoom();
                map.savePosition();

                //set unload function
                $("body").unload = GUnload;

                if (currentSettings.afterMapMove) {
                    GEvent.addListener(map, "moveend", function() {
                        if (canFireMapMoveEvent) {
                            currentSettings.afterMapMove(map.getCenter(), map.getBounds());
                        }
                    });

                }

                //load map from XML
                if (mapXML) {
                    var doc = GXml.parse(mapXML);
                    loadMap(doc);
                }
            }
        });
    }

    $.gMapViewerSettings = function(options) {
        if (options) {
            $.extend(currentSettings, options);
        }
    }

    $.gMapViewerGeoSearch = function(searchStr, callback) {
        if (searchStr.length != 0) {
            var geocoder = new GClientGeocoder();

            if (currentSettings.geoCountryCode != null && currentSettings.geoCountryCode.length != 0) {
                geocoder.setBaseCountryCode(currentSettings.geoCountryCode);
                searchStr = searchStr.replace("(^| )" + currentSettings.geoCountryCode.replace("\\", "") + "( |$)", "");
                searchStr += " " + currentSettings.geoCountryCode;
            }

            if (geocoder) {
                geocoder.getLatLng(
                  searchStr,
                  function(point) {
                      var bounds = null;
                      var latlng = null;

                      if (point) {
                          var zoom = map.getZoom();

                          if (currentSettings.minGeoSearchZoom && zoom < currentSettings.minGeoSearchZoom) {
                              zoom = currentSettings.minGeoSearchZoom;
                          }
                          map.setCenter(point, zoom);
                          bounds = map.getBounds();
                          latlng = map.getCenter();
                      }

                      if (callback) {
                          callback(latlng, bounds);
                      }
                  }
                );
            }
        }
    }

    $.gMapViewerPanTo = function(lat, lng, zoom) {
        map.setCenter(new GLatLng(lat, lng), zoom);
    };

    //Returns the map XML
    $.gMapViewerLoadPlacemarks = function(xmlDoc) {
        var doc = xmlDoc;

        map.clearOverlays();
        markerArray = new Array();
        loadMap(doc);
    };

    $.gMapViewerLoadPlacemarkArray = function(placemarkArray, callback) {
        map.clearOverlays();
        markerArray = placemarkArray;

        //get placemarkers
        var mapBounds = map.getBounds();
        var hasVisibleMarker = false;

        var newMarker;
        for (var i = 0; i < markerArray.length; i++) {

            newMarker = markerArray[i];
            if (newMarker) {
                map.addOverlay(newMarker);
                if (currentSettings.hideMarkersOnLoad) {
                    newMarker.hide();
                }
            }

            if (mapBounds.containsLatLng(newMarker.getLatLng())) {
                hasVisibleMarker = true;
            }
        }

        //If no markers are in viewing range then center map on first marker
        if (!hasVisibleMarker && markerArray.length != 0) {
            map.panTo(markerArray[0].getLatLng());
        }

        if (callback) {
            callback();
        }
    };

    $.gMapViewerShowPlacemark = function(index) {
        if (index < markerArray.length && markerArray[index]) {
            markerArray[index].show();
        }
    };

    $.gMapViewerHidePlacemark = function(index) {
        if (index < markerArray.length && markerArray[index]) {
            markerArray[index].hide();
        }
    };

    //Returns the map XML
    $.gMapViewerClearPlacemarks = function() {
        map.clearOverlays();
        markerArray = new Array();
    };

    //Returns the loaded placemarks as an array of GMarkers
    $.gMapViewerGetMarkers = function() {
        return markerArray;
    };



    //Reset zoom and center of map
    $.gMapViewerResetMap = function() {
        map.returnToSavedPosition();
    };

    $.gMapViewerSelectPlacemark = function(index) {
        SelectMarker(index);
    };

    /*** Exposed XML Util functions ***/
    $.gMapViewerSerializeNode = function(node) {
        return serializeNode(node);
    };

    $.gMapViewerXmlFormat = function(str) {
        return XmlFormat(str);
    }

    $.gMapViewerGetChildNodeValue = function(parentNode, nodeName, defaultValue) {
        return getChildNodeValue(parentNode, nodeName, defaultValue);
    }

    $.gMapViewerGetSingleNodeValue = function(parentNode, xPath, defaultValue) {
        return getSingleNodeValue(parentNode, xPath, defaultValue);
    }

    // -------------------------------------------------------
    // Private functions:
    // -------------------------------------------------------
    // Loads the map 
    // -------------------------------------------------------
    function loadMap(doc) {
        if (doc) {
            //load map settings
            var viewNode = doc.getElementsByTagName("View");

            if (viewNode.length != 0) {
                var mapTypeName = getChildNodeValue(viewNode[0], "MapType", "Map");
                var mapLat = getChildNodeValue(viewNode[0], "Latitude", 0);
                var mapLng = getChildNodeValue(viewNode[0], "Longitude", 0);
                var mapZoom = getChildNodeValue(viewNode[0], "Zoom", 0);

                if (mapLat != 0 && mapLng != 0) {
                    map.setCenter(new GLatLng(mapLat, mapLng));
                    map.setZoom(parseInt(mapZoom, 10));
                }

                if (mapTypeName != null) {
                    var mtArray = map.getMapTypes();
                    for (var i = 0; i < mtArray.length; i++) {
                        if (mtArray[i].getName() == mapTypeName) {
                            map.setMapType(mtArray[i]);
                            break;
                        }
                    }
                }
            }

            //get placemarkers
            var mapBounds = map.getBounds();
            var hasVisibleMarker = false;
            var markerNodes = doc.getElementsByTagName("Placemark");

            var newMarker;
            for (var i = 0; i < markerNodes.length; i++) {

                newMarker = GetMarkerData(markerNodes[i]);
                if (newMarker) {
                    map.addOverlay(newMarker);
                    if (currentSettings.hideMarkersOnLoad) {
                        newMarker.hide();
                    }
                }

                if (mapBounds.containsLatLng(newMarker.getLatLng())) {
                    hasVisibleMarker = true;
                }
            }

            //If no markers are in viewing range then center map on first marker
            if (!hasVisibleMarker && markerArray.length != 0) {
                map.panTo(markerArray[0].getLatLng());
            }
        }
    }

    // -------------------------------------------------------
    // Private Map functions:
    // -------------------------------------------------------

    function GetMarkerData(xmlNode) {
        //get placemarkers
        var lng = 0;
        var lat = 0;
        var title = "";
        var name = "";
        var descNode = null;
        var description = "";
        var iconURL = "";

        title = getChildNodeValue(xmlNode, "title", "");
        name = getChildNodeValue(xmlNode, "name", "");

        description = "";
        descNode = xmlNode.getElementsByTagName("description");
        lng = Trim(getChildNodeValue(xmlNode, "Longitude", "0"));
        lat = Trim(getChildNodeValue(xmlNode, "Latitude", "0"));
        iconURL = getSingleNodeValue(xmlNode, "Icon/href", "");

        if (descNode && descNode.length != 0) {
            description = serializeNode(descNode[0]);
            description = description.replace("<description>", "");
            description = description.replace("</description>", "");
            description = description.replace("<description/>", "");
        }

        if (name.length != 0 && name.toLowerCase() != title.toLowerCase()) {
            description = "<p>" + name + "</p>" + description
        }

        if (lat != 0 && lng != 0) {
            var index = markerArray.length;
            var newMarker = CreateMarker(new GLatLng(lat, lng), index, title, description, CreateIcon(iconURL));
            markerArray.push(newMarker);

            return newMarker;
        }

        return null;

    }

    // -------------------------------------------------------
    // Creates a GMarker object
    // -------------------------------------------------------
    function CreateMarker(latlng, idx, title, info, newIcon) {
        //var marker = new GMarker(latlng, {icon: newIcon});
        var marker = new GMarker(latlng);
        marker.Index = idx;
        marker.Title = title;

        GEvent.addListener(marker, "click", function() {
            canFireMapMoveEvent = false;
            marker.openInfoWindowHtml("<div class='infoWindow'><strong>" + title + "</strong><div>" + info + "</div></div>", { maxWidth: 250 });

            if (currentSettings.markerSelected) {
                currentSettings.markerSelected(idx);
            }
        });

        GEvent.addListener(marker, "infowindowopen", function() {
            setTimeout(function() {
                canFireMapMoveEvent = true;
            },
                400);
        });

        return marker;
    }

    // -------------------------------------------------------
    // Selects a marker and opens it's info window
    // -------------------------------------------------------
    function SelectMarker(idx) {
        if (markerArray && markerArray[idx]) {
            map.panTo(markerArray[idx].getLatLng());
            GEvent.trigger(markerArray[idx], "click");
        }
    }

    // -------------------------------------------------------
    // Creates a GIcon with the specified image
    // -------------------------------------------------------
    function CreateIcon(iconURL) {
        var icon = new GIcon();
        icon.image = currentSettings.iconBaseHref + iconURL;
        icon.shadow = currentSettings.iconBaseHref + currentSettings.iconDefaultShadow;
        icon.iconSize = new GSize(20.0, 34.0);
        icon.shadowSize = new GSize(38.0, 34.0);
        icon.iconAnchor = new GPoint(10, 34.0);
        icon.infoWindowAnchor = new GPoint(10.0, 0);

        return icon;
    }

    // -------------------------------------------------------
    // Helper functions
    // -------------------------------------------------------
    // Returns the value of the first node match for the given xPath.
    // If no value is found the default value is returned.
    // -------------------------------------------------------
    function getSingleNodeValue(parentNode, xPath, defaultValue) {
        var nodes;
        var xPathArray = xPath.split('/');
        var val = defaultValue;

        if (xPathArray.length > 1) {
            nodes = parentNode.getElementsByTagName(xPathArray[0]);

            if (nodes.length != 0) {
                val = getSingleNodeValue(nodes[0], xPathArray.slice(1).join('/'), defaultValue);
            }
        }
        else {
            val = getChildNodeValue(parentNode, xPath, defaultValue);

        }

        return val;
    }

    // -------------------------------------------------------
    // Returns the value of the child node.
    // If no value is found the default value is returned.
    // -------------------------------------------------------
    function getChildNodeValue(parentNode, nodeName, defaultValue) {
        var nodes = parentNode.getElementsByTagName(nodeName);
        if (nodes.length != 0) {
            return GXml.value(nodes[0]);
        }

        return defaultValue;
    }

    function Trim(textString) {
        return textString.replace(/(^ +)|( +$)/gi, "");
    }

    function XmlFormat(str) {
        var result = str;

        result = result.replace(/&/g, "&amp;");
        result = result.replace(/</g, "&lt;");
        result = result.replace(/>/g, "&gt;");
        result = result.replace(/'/g, "&apos;");
        result = result.replace(/"/g, "&quot;");

        return result;
    }

    function xml2Str(xmlNode) {
        try {
            // Gecko- and Webkit-based browsers (Firefox, Chrome), Opera
            return (new XMLSerializer()).serializeToString(xmlNode);
        }
        catch (e) {
            try {
                // Internet Explorer, including IE9 which seems to have a broken XMLSerializer().serializeToString(node).
                return xmlNode.xml;
            }
            catch (e) {
                //Other browsers without XML Serializer
                alert('Xmlserializer not supported');
            }
        }
        return false;
    }

    function serializeNode(node) {
        var xmlStr = xml2Str(node);
        if (xmlStr) {
            return xmlStr;
        } else {
            if (typeof printNode != 'undefined') {
                return printNode(node);
            }
            else {
                // might want to handle problem here
                return 'undefined';
            }
        }
    }
})(jQuery);

