﻿


function SoMap(div, options) 
{
    // Save arguments
    this.div = div || "map";
    this.centreLat = options.centreLat ;
    this.centreLng = options.centreLng ;
    this.displayedMarkers = {};
    this.currentMarker = null;
    this.icons = {};
    
    // Marker type contstants
    this.mtCLUSTER = 0;
    this.mtLISTING = 1;
    this.tooltip = null;
    this.soSearch = null; //SoSearch
    
    this.markerImagePath = "/images/markers/listing/";
    
    // Self-reference for use in callbacks
    var me = this;
        
    this.createIcon = function(size, name)
    {
        // Assumes icons have the name type.png and type_shadow.png
        var icon = new GIcon();
        
        switch (size)
        {
            case "cluster":
                // sectionName will the cluster count
                icon.image = "/images/markers/cluster/" + name + ".png";
                icon.iconSize = new GSize(127.0, 55.0);
                icon.iconAnchor = new GPoint(58.0, 25.0);
                icon.infoWindowAnchor = new GPoint(0, 0);
                break;
            
            case "large":
                // if section name not passed we just point to eg a small.png othterwise we point to eg a music_small.png
                name = (name == null) ? "" : name + "_";
                icon.image = this.markerImagePath + name + "large.png";
                icon.shadow = this.markerImagePath + "large_shadow.png";
                icon.iconSize = new GSize(26.0, 40.0);
                icon.iconAnchor = new GPoint(13.0, 20.0);
                icon.infoWindowAnchor = new GPoint(33, 49);
                //GLog.write ("created large icon - ulr = " + icon.image);
                break;
            
            case "small":
                // if section name not passed we just point to eg a small.png othterwise we point to eg a music_small.png
                name = (name == null) ? "" : name + "_";
                icon.image = this.markerImagePath  + name + "small.png";
                icon.shadow = this.markerImagePath + "small_shadow.png";
                icon.iconSize = new GSize(16.0, 24.0);
                icon.iconAnchor = new GPoint(8.0, 12.0);
                icon.infoWindowAnchor = new GPoint(28, 35);
                break;
        }
        return icon;
    }
  
    
    if (GBrowserIsCompatible()) {
        // Be sure to cleanup circular references etc
        window.onunload = GUnload;
        
        this.map = new google.maps.Map2(document.getElementById(this.div));

        // Create the tooltip
        this.tooltip = document.createElement("div");
        document.getElementById("map").appendChild(this.tooltip);
        this.tooltip.style.visibility="hidden";
        this.tooltip.className="tooltip";
        
        
        
        // markerTypes is an js array included on the server
        this.tree = new SoTree("tree", markerTypes, {checkClick: function(node) { me.getData(); }} );
        
        //GEvent.addListener(this.map, "load", function() { me.getData(); });
        this.map.addControl(new GLargeMapControl());
        this.map.addControl(new GHierarchicalMapTypeControl()); //GMapTypeControl());
        this.map.addMapType(G_PHYSICAL_MAP);
        this.map.setCenter(new google.maps.LatLng( this.centreLat,  this.centreLng), 10);
        GEvent.addListener(this.map, "dragend", function() { me.dragEnd(); });
        GEvent.addListener(this.map, "moveend", function() { me.moveEnd();});
        GEvent.addListener(this.map, "zoomend", function() { me.getData(); });
        // when an infow window has updated refresh the map - we may have panned to an area unside the current bounds
        //GEvent.addDomListener(this.map, 'extinfowindowupdate', function() { me.getData();});
        GEvent.addDomListener(this.map, 'extinfowindowclose', function() { me.currentMarker = null; });
        
        // Create the default icons - the rest are created JIT as needed when we create the marker
        this.icons["large"] = this.createIcon("large", null);
        this.icons["small"] = this.createIcon("small", null); 
        
        // todo - pass options
        this.soSearch = new SoSearch(this.map, {});
    }
};

SoMap.prototype.moveEnd = function()
{
    // if a marker has been clicked then an info window opeining may cause the map to move to an unpopulated area
    // so get data for the map withouut generating too much traffic
    if (this.currentMarker != null) this.dragEnd();
    
}

SoMap.prototype.dragEnd = function()
{
    // To avoid refreshing the data too frequently when the user nudges the map, only do so if the movement
    // is greater than MIN_MOVEMENT_PERCENTAGE percent.
    var MIN_MOVEMENT_PERCENTAGE = 6;
    
	var bnds = this.map.getBounds();
	var latMovement = bnds.getSouthWest().lat() - this.lastBounds.getSouthWest().lat() ;
	var previousLatSpan = this.lastBounds.getNorthEast().lat() - this.lastBounds.getSouthWest().lat() ;
	
	var lngMovement = bnds.getSouthWest().lng() - this.lastBounds.getSouthWest().lng() ;
	var previousLngSpan = this.lastBounds.getNorthEast().lng() - this.lastBounds.getSouthWest().lng() ;
	
	var verticalPercentMovement = Math.abs( latMovement / previousLatSpan) * 100;
	var horizalPercentMovement    = Math.abs( lngMovement / previousLngSpan) * 100;
	
	if (verticalPercentMovement < MIN_MOVEMENT_PERCENTAGE && 
	    horizalPercentMovement < MIN_MOVEMENT_PERCENTAGE) 
	{
		return;
	}
	else
	{
	    this.getData();
	}
};

SoMap.prototype.showTooltip = function(marker, gmarker)
{
    this.tooltip.innerHTML = marker.nm;
	var point= this.map.getCurrentMapType().getProjection().fromLatLngToPixel(this.map.getBounds().getSouthWest(),this.map.getZoom());
	var offset= this.map.getCurrentMapType().getProjection().fromLatLngToPixel(gmarker.getPoint(),this.map.getZoom());
	var anchor= gmarker.getIcon().iconAnchor;
	var width= gmarker.getIcon().iconSize.width;
	var pos = new GControlPosition(G_ANCHOR_BOTTOM_LEFT, new GSize(offset.x - point.x - anchor.x + width,- offset.y + point.y +anchor.y)); 
	pos.apply(this.tooltip);
	this.tooltip.style.visibility="visible";  
};


SoMap.prototype.getData = function()
{
    var me = this;
    
    // Get a delimited string describing the sections ans subsections to fetch
    var treeData = this.tree.getSelections();
    
    // Get the ne and sw bounds of the map
    var bounds = this.map.getBounds();
    var ne = bounds.getSouthWest().toUrlValue();
    var sw = bounds.getNorthEast().toUrlValue();
    
    // Get zoom level
    var zoom = this.map.getZoom();
    
    // Save settings to interogate in next ajax call
    this.lastZoom = zoom;
    this.lastBounds = bounds;
    
    // Construct the url for the async request
    var url = "/map/markers?ne=" + ne + "&sw=" + sw + "&zoom=" + zoom + "&treedata=" + treeData;
    
    //alert (this.map.getCenter());

    this.displayMarkers = function(markers)
    {
        var bounds = me.map.getBounds();

        // loop though each of the points in memory and remove those that arent going to be shown.
        // This is to remove flashing if for example we chose to remove all markers using clearOverlays.
        if (markers)
        {
            for (k in me.displayedMarkers)
            {
                var mk = me.displayedMarkers[k];
                
                if ((mk.type == this.mtCLUSTER) ||
                    (!bounds.containsLatLng(mk.getLatLng()) || !markers[k])) 
                {   
                    me.map.removeOverlay(mk);
                    delete me.displayedMarkers[k];
                }
            }

            // loop though each of the new points and create markers that don't exist
            for (k in markers) {
                
                var m = markers[k];
                var latlng = new GLatLng(m.lat, m.lng);
                
                // skip it if the marker already exists or is not in the viewable area.
                if (!me.displayedMarkers[k] && bounds.containsLatLng(latlng)) {
                    me.displayedMarkers[k]= me.createMarker(m, latlng);
                    me.map.addOverlay(me.displayedMarkers[k]);
                }                  
            }
        };
    };
    

    this.createMarker = function(marker, latlng)
    {
        var icon, zIndexProcessFunction;
        
        if (marker.t == -1)
        {
            // Its a custom listing search marker - one that wasnt on the map until we search for and grabbed it from the server
            icon = this.createIcon((marker.e) ? "large" : "small", null);
        }
        else if (marker.t == 0)
        {
            // Its a clustered marker - marker.c ontains the count of the listings inside it
            icon = this.createIcon("cluster", marker.c);
        }
        else
        {
            //title = marker.nm; // now using custom tooltip
            zIndexProcessFunction = null;
            
            // look up the icon to use 
            // first get the section
            var iconName ;
            var section = markerTypes[marker.s.toString()];
            if (section != "undefined")
            {
                var size = (marker.e) ? "large" : "small";
                iconName = section.sn + "_" + size;
                icon = this.icons[iconName];
                
                if (icon == null)
                {
                    // Create the icon JIT
                    icon = this.createIcon(size, section.sn);
                    this.icons[iconName] = icon;
                }
            }
        }
        
        zIndexProcessFunction = (marker.e || marker.t == -1) ? me.setEnhancedListingZIndex : null;
        
        var gmarker = new GMarker(latlng, {icon: icon, zIndexProcess: zIndexProcessFunction });
        
        GEvent.addListener(gmarker, "click", function() { me.markerClick(marker, gmarker); } );
        
        if (marker.t !=0)
        {
            // Add tooltip listeners
            GEvent.addListener(gmarker, "mouseover", function() {me.showTooltip(marker, gmarker); });        
            GEvent.addListener(gmarker, "mouseout", function()  {me.tooltip.style.visibility="hidden"; });   
        }
        
        return gmarker;
    };
    
    // Get the maker data
    $.ajax({
            type:      "GET",
            url:       url, 
            dataType:  "json",
            success:   this.displayMarkers,
            error:     function (XMLHttpRequest, textStatus, errorThrown) 
                       {
                         alert("Sorry, an error prevented us from displaying the data you requested.\nPlease try again later.");
                       }
    });
};


SoMap.prototype.setEnhancedListingZIndex = function(marker, b)
{
    var zIndexAddition = 100;
    return GOverlay.getZIndex(marker.getPoint().lat()) + zIndexAddition * 1000000;
};

SoMap.prototype.setClusterZIndex = function(marker, b)
{
    var zIndexAddition = 10;
    return GOverlay.getZIndex(marker.getPoint().lat()) + zIndexAddition * 1000000;
};

SoMap.prototype.markerClick = function(marker, gmarker)
{
    this.map.closeExtInfoWindow();
    this.currentMarker = gmarker;
    
    if (marker.t == this.mtCLUSTER)
    {
        this.map.setCenter(gmarker.getLatLng());
        this.map.setZoom(this.map.getZoom() + 1);
    }
    else
    {
        // 
        
        
        // Get the infowindow html
        gmarker.openExtInfoWindow(
                              this.map,
                              "sowindow",
                              "<div style='text-align:center; height:50px;padding-top:20px;'><img alt='Loading...' src= '/images/ajax-big.gif' /></div>",
                              {
                                ajaxUrl: "/map/infowindow?content_id=" + marker.cid, 
                                beakOffset: 25,
                                paddingX:90,
                                paddingY:10
                              }
                            );              
    }
};

// data i the result of an automplete lookup
// if undefined no match was found.
// otherwise data[0] is thename and data[1] is the id
SoMap.prototype.findAndDisplayListing = function(data)
{
    var me = this;
    
    if (!data) 
    {
        // entry is not in autocomplete list so do a glocalsearch
        this.soSearch.findAll();
    }
    else
    {
        var cid = data[1];
        // Look up and show listing
        //if (console) console.info("lookup listing..");
        var marker = this.displayedMarkers[cid];
        if (marker)
        {
            GEvent.trigger(marker, "click");
        }
        else
        {
            $.ajax( 
                    {
                      url:      "/map/search?contentitemid=" + cid,
                      dataType:  "json",
                      error:   function (XMLHttpRequest, textStatus, errorThrown) { alert("Sorry, we are unable to service your request at the moment."); },
                      success: function (result, textStatus)  
                      {
                         var latlng = new GLatLng(result.lat, result.lng);
                         me.map.panTo(latlng);
                         // Test to see if we now have the marker on the map as a result of a movend ajax request to server
                         // However its unlikley as that request is prob still going on right now
                         var testagainmarker = me.displayedMarkers[cid];
                         if (testagainmarker)
                         {
                            GEvent.trigger(testagainmarker, "click");
                         }
                         else
                         {
                             var gmarker = me.createMarker(result, latlng )
                             me.map.addOverlay(gmarker);
                             me.map.panTo(gmarker.getLatLng());
                             GEvent.trigger(gmarker, "click");
                         }
                         //GEvent.addListener(gmarker, 'click', function(){  gmarker.openInfoWindowHtml(marker.html);});
                      }
                });
        }

    }
}


SoMap.prototype.ShowDialog = function(dialogId)
{
    var me = this;
    
    function showDialog()
    {
        // First hide all other dialogs
        $("div.dialog").hide();
        
        $("div#" + dialogId + ".dialog, div.dialog-overlay").slideDown("fast", function() { 
            $("div#" + dialogId + " form").validate({ 
                        submitHandler: function(form) { me.deliver("feedbackdialog_form"); } 
                        })
            });
  
    }
    
    
    showDialog();
    
    return false;
    
};

SoMap.prototype.HideDialog = function()
{
    $("div.dialog, div.dialog-overlay").slideUp("fast");
}

SoMap.prototype.deliver = function(formId)
{
    var me = this;
    
function ScrollToElement(theElement){

  var selectedPosX = 0;
  var selectedPosY = 0;
              
  while(theElement != null){
    selectedPosX += theElement.offsetLeft;
    selectedPosY += theElement.offsetTop;
    theElement = theElement.offsetParent;
  }
                        		      
 window.scrollTo(selectedPosX,selectedPosY);

};
    $.ajax({
					url: '/map/delivery',
					data: $('#' + formId).serialize() + '&action=send',
					type: 'post',
					cache: false,
					dataType: 'html',
					success: function (result, textStatus) 
					    {
							alert("Thank you your email has been recieved");
							$("#feedbackdialog").hide();
							ScrollToElement(document.getElementById("map"));
					    },
					error: me.deliveryError
				});
}

SoMap.prototype.deliveryError = function()
{
    alert("Sorry, we couldn't send your mail - please try again later");
    me.HideDialog();
}

// Helper method for the underlying directions class
SoMap.prototype.loadDirectionsIfEnter = function(e)
{
    //if which property of event object is supported (NN4)
    var characterCode = (e && e.which) ? e.which : e.keyCode;
    if(characterCode == 13)
    {
        this.loadDirections();
        return false;
    }
    else { return true; }
}

// Helper method for the underlying directions class
SoMap.prototype.loadDirections = function()
{
    if (!this.directions) this.directions = new Directions(this.map, {});
    
    // Pass in the user entered start location to be geocoded
    this.directions.load( $("#directionsStartingLocation").attr("value"), this.currentMarker);
}


//
// Cookie helper class
//
Cookie = {

    setCookie: function (name, value, expiredays)
    {
        var exdate=new Date();
        exdate.setDate(exdate.getDate()+expiredays);
        document.cookie=name + "=" + escape(value) + ((expiredays==null) ? "" : ";expires="+exdate.toGMTString());
        
    },
    
    getCookie: function (name)
    {
       if (document.cookie.length > 0)
       {
         var nameEQ = name + "=";
	     var ca = document.cookie.split(';');
	     for(var i=0;i < ca.length;i++) 
	     {
		    var c = ca[i];
		    while (c.charAt(0)==' ') c = c.substring(1,c.length);
		    if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
	     }
	   }
	   return "";
     },
     
     eraseCookie: function(name) 
     {	
        Cookie.setCookie(name,"",-1); 
     }

};



// Directions 
function Directions(map, opts)
{
    // Defaults
    this.directionsCmd = null;
    this.map = map;
    this.destinationMarker = null;
    var me = this;
    this.localSearch = null;
    this.postcodeRegExPattern =  "[A-PR-UWYZ][A-HK-Y0-9][A-HJKSTUW0-9]?[ABEHMNPRVWXY0-9]? {1,2}[0-9][ABD-HJLN-UW-Z]{2}";
    this.directionsContentDiv = opts.directionsContentDiv || "directions_content";
    // The following are jQuery selectors
    this.directionsDiv = opts.directionsDiv || "#directions";
    
    
    // GDirections callback - we successfully geocoded the user entered lookup and have some
    // directions to display...
    this.onGDirectionsLoad = function() 
    {   
        me.map.closeExtInfoWindow();
        $(me.directionsDiv).show();
    };

    // Handle direction errors 
    this.handleErrors = function ()
    {
       if (me.directions.getStatus().code == G_GEO_UNKNOWN_ADDRESS)
         alert("No corresponding geographic location could be found for this place name.\nTry being more specific - for example instead of \"Green Road\", use \"Green Road, Thorpe\".");
       else if (me.directions.getStatus().code == G_GEO_SERVER_ERROR)
         alert("A geocoding or directions request could not be successfully processed, yet the exact reason for the failure is not known.\n Error code: " + me.directions.getStatus().code);
       else if (me.directions.getStatus().code == G_GEO_MISSING_QUERY)
         alert("The HTTP q parameter was either missing or had no value. For geocoder requests, this means that an empty address was specified as input. For directions requests, this means that no query was specified in the input.\n Error code: " + me.directions.getStatus().code);
       else if (me.directions.getStatus().code == G_GEO_BAD_KEY)
         alert("The given key is either invalid or does not match the domain for which it was given. \n Error code: " + me.directions.getStatus().code);
       else if (me.directions.getStatus().code == G_GEO_BAD_REQUEST)
         alert("A directions request could not be successfully parsed.\n Error code: " + me.directions.getStatus().code);
       else alert("An unknown error occurred.");
       me.directions.clear();
    };
 
    // Initialise the GDirections
    this.directions = new GDirections(this.map, document.getElementById(this.directionsContentDiv));
    GEvent.addListener(me.directions, "load", me.onGDirectionsLoad);
    GEvent.addListener(me.directions, "error", me.handleErrors);
    
    // Load directions for a sepcific marker
    this.load = function(userEnteredStartPoint, destinationMarker)
    {
        this.destinationMarker = destinationMarker;
        this.directions.clear();
        
        // Do a google local search for the postcode if we have one
        // First test to see if we have a valid postcode - if we do, do a local search,
        var regexp = new RegExp(this.postcodeRegExPattern,"g");
        var s = userEnteredStartPoint.toUpperCase()
        var postcode =  regexp.exec(s);
        
        if (postcode == null)
        {
            
            // Its not a postcode so safer to do a standard google directions lookup
            // This is the destination - ie the property
            var propertyLatLng = destinationMarker.getLatLng();

            // format the cmad to send to google - save in case we need to use in error handling etc - always add UK to prevent spurious scope...
            this.directionsCmd = userEnteredStartPoint + ",UK to " + propertyLatLng.lat() + "," + propertyLatLng.lng();
            
            // save the cmd for use when/if we redraw
            this.directions.load(this.directionsCmd, {getPolyline:true, getSteps:true});
            
        }
        else
        {
            // The user entered a postcode - do a local search
            if (this.localSearch == null) 
            {
                this.localSearch = new GlocalSearch();
                this.localSearch.setSearchCompleteCallback(null, me.onLocalSearch);
            }
            this.localSearch.setCenterPoint(this.map);
            this.localSearch.execute(postcode);
        }
        regexp =null;
        
    };
    
    
    // Called when Local Search results are returned - ge the first hit and use in GDirections
    this.onLocalSearch = function() 
    {
    
        if (me.localSearch.results == null || me.localSearch.results == undefined) 
        {
            alert("No results found");
            return;
        }

        if (me.localSearch.length == 0) 
        {
            alert("No results found");
            return;
        }
        
        if (me.destinationMarker != null)
        {
            var listingLatLng = me.destinationMarker.getLatLng();
            var from = me.localSearch.results[0].lat + "," + me.localSearch.results[0].lng;
            var to = listingLatLng.lat() + "," + listingLatLng.lng();
            me.directionsCmd = from + " to " + to;
            me.directions.load(me.directionsCmd, {getPolyline:true, getSteps:true});
        }
    };
    
    
    this.destroy = function()
    {
        $(this.directionsDiv).hide();
        this.directions.clear();
    };
   
};