(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});
        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 serializeNode (node) {
		if (typeof XMLSerializer != 'undefined') {
			return new XMLSerializer().serializeToString(node);
		}
		else if (typeof node.xml != 'undefined') {
			return node.xml;
		}
		else if (typeof printNode != 'undefined') {
			return printNode(node);
		}
		else {
			// might want to handle problem here
			return 'undefined';
		}
	}
})(jQuery);
