﻿//any variables used
var map = null;
var mapAreaDiv;
var mapWidth;
var mapHeight;
var currentShapeIDs = [];
var currentZoomLevel;
var showHideIcons = true;
var viewAreaQueryParameters = [];
var viewAreaDescription = "";
var datasetType;
var pushpinLayer;
var shapeLayer;


//Thease 4 bounderies are used to limit the map to only show New Zealand.
var leftLongBoundery = 163.355712
var rightLongBoundrry = 180
var topLatBoundery = -33.715201
var bottomLatBoundery = -47.598755


//performed when loading the map
//this will bind the map to the appropriate div, set its default values and attach appropriate events to it.
//Bing maps interactive sdk can be found here: http://www.microsoft.com/maps/isdk/ajax/
function pageLoad() {
	if (typeof VEMap == 'undefined') {
		alert("Unfortunately, you may not have access to the Bing maps controls.\nThis may be due to a proxy not being set up correctly.\nTo test, please visit http://www.bing.com/maps/\n");
		return;
	}
    mapAreaDiv = $get(mapID);
    mapWidth = mapAreaDiv.clientWidth;
    mapHeight = mapAreaDiv.clientHeight;
    map = new VEMap(mapID);
    if (mapKey != "") {
    	map.AttachEvent("oncredentialserror", MyHandleCredentialsError);
       	map.AttachEvent("oncredentialsvalid", MyHandleCredentialsValid); 
    	map.SetCredentials(mapKey);
    }
    var latLong = $get(positionID).value.split(',');
    currentZoomLevel = $get(zoomLevelID).value;
    map.LoadMap(new VELatLong(latLong[0], latLong[1]), currentZoomLevel);
    map.SetMapMode(VEMapMode.Mode2D);
    map.AttachEvent("onchangeview", Map_OnChangeView);
    //only use inbuilt generalization if we don't generalize ourselves.
    if (simplificationOption == 0)
    	map.EnableShapeDisplayThreshold(true);
    else
    	map.EnableShapeDisplayThreshold(false);
    //map.HideDashboard();
    map.SetScaleBarDistanceUnit(VEDistanceUnit.Kilometers);
    map.Resize(mapWidth, mapHeight);

    shapeLayer = new VEShapeLayer();
    map.AddShapeLayer(shapeLayer);
    pushpinLayer = new VEShapeLayer();
    map.AddShapeLayer(pushpinLayer);
	
    //for ie:
    $addHandler(mapAreaDiv.parentNode, "onresize", Document_OnResize);
	//for ff:
    $addHandler(window, "resize", Document_OnResize);
    //    if (window.addEventListener)
//FF:
//        mapAreaDiv.parentNode.addEventListener("resize", Document_OnResize, false);
    //    else
//IE:
//        mapAreaDiv.parentNode.attachEvent("onresize", Document_OnResize);
    $get("RenderOptions").value = simplificationOption;
    $get("ViewArea").value = viewArea;
    $get("ViewAreaSelectContainer").style.display = enableViewAreaSelection ? "inline" : "none";
    $get("ViewAreaDisplayContainer").style.display = enableViewAreaSelection ? "none" : "inline";

    //here to fix the map size problem in IE6
    //we just need to force a map resize
    ResizeMap();
    ShowMapTips();
   }

function pageUnload() {
	if (typeof VEMap == 'undefined')
		map.Dispose();
}

function MyHandleCredentialsError() {
	//alert("The credentials are invalid.");
}

function MyHandleCredentialsValid() {
	//alert("The credentials are valid.");

}

//Fires whenever the window is resized, and will resize the map appropriately.
function Document_OnResize(e) {
	ResizeMap();
   }

   function ResizeMap() {
   	mapWidth = mapAreaDiv.parentNode.clientWidth - 20;
   	mapHeight = mapAreaDiv.parentNode.clientHeight - 80;
   	//alert("W: " + mapWidth + "; H: " + mapHeight);
   	map.Resize(mapWidth, mapHeight);
   }

//The lock is used to prevent calls from being made to the server before another call has finished.
var lock = false;


//Fires whenever the map is panned or zoomed. This will determine wether a full or partial update of the shapes is required, refreshes the map.
function Map_OnChangeView(e) {
	if (lock)
		return;
	else
		lock = true;
    var zoomDif = e.zoomLevel - currentZoomLevel;
    currentZoomLevel = e.zoomLevel;

    var outsideBoundery = CheckAndSetBoundery();

    //we do a partial update if we dont change zoomlevel,or if we zoom in our out one level and do not cross the zoomgroup boundary.
    //otherwise we do a full update. These boundaries also align with the crossover points when selecting the viewarea is disabled, and is required to send back the new shapes.
    //The lowest zoomlevel is excluded when no simplification is done on the server, as the shapes returned will be the same. (simplificationOption == 0)
    //if the difference in zoomlevels is more than one, the map might have entered an unpredicatable state, so we do a full refresh of it.

    if (outsideBoundery == false) {
        if ((zoomDif <= 1 && zoomDif >= -1) 
			&& ((zoomDif == 0)
				|| (zoomDif == 1 && e.zoomLevel != 8 && e.zoomLevel != 11 && (e.zoomLevel != 14 || simplificationOption == 0))
				|| (zoomDif == -1 && e.zoomLevel != 7 && e.zoomLevel != 10 && (e.zoomLevel != 13 || simplificationOption == 0)))
			)
            RefreshMap(false);
        else
            RefreshMap(true);
    }
}

//This function checks to see whether New Zealand is still in the current camera view.
//If NZ is outside, move view to the nearest boundery.
function CheckAndSetBoundery() {
    var view = map.GetMapView();
    var latLong = $get(positionID).value.split(',');
    var currentCenterLat = view.TopLeftLatLong.Latitude - (view.TopLeftLatLong.Latitude - view.BottomRightLatLong.Latitude) / 2;
    var currentCenterLong = view.TopLeftLatLong.Longitude + (view.BottomRightLatLong.Longitude - view.TopLeftLatLong.Longitude) / 2;

    //We always calculate the offset as a positive number.
    var offset;
    
    //outside to the left
    if (view.BottomRightLatLong.Longitude < leftLongBoundery) {
        offset = leftLongBoundery - view.BottomRightLatLong.Longitude + (view.BottomRightLatLong.Longitude - view.TopLeftLatLong.Longitude) / 2
        map.SetCenter(new VELatLong(currentCenterLat, currentCenterLong + offset));
        return true;
    }
    //outside to the top
    else if (view.BottomRightLatLong.Latitude > topLatBoundery) {
        offset = view.BottomRightLatLong.Latitude - topLatBoundery + (view.TopLeftLatLong.Latitude - view.BottomRightLatLong.Latitude) / 2;
        map.SetCenter(new VELatLong(currentCenterLat - offset, currentCenterLong));
        return true;
    }
    //outside to the bottom
    else if (view.TopLeftLatLong.Latitude < bottomLatBoundery) {
        offset = bottomLatBoundery - view.TopLeftLatLong.Latitude + (view.TopLeftLatLong.Latitude - view.BottomRightLatLong.Latitude) / 2;
        map.SetCenter(new VELatLong(currentCenterLat + offset, currentCenterLong));
        return true;
    }
    //zoom level is less than 5, seeing too much from the map, only New Zealand is allowed.
    else if (currentZoomLevel < 5) {
        var latLong = $get(positionID).value.split(',');
        map.SetCenterAndZoom(new VELatLong(latLong[0], latLong[1]), 5);
        return true;
    } else {
        return false;
    }
}

//This will update the variables for an update of the shapes, and update the map.
function RefreshMap(Complete) {
	
    //only load the shapes if there is parameters for the query, otherwise just do the request with empty parameters to log the movement.
	if (viewAreaQueryParameters == null || viewAreaQueryParameters.length == 0) {
		GetMapDataFromServer("", "");
		return;
	}
	SetWait(true, Complete);
	CloseMapTips();
    simplificationOption = $get("RenderOptions").value;
    viewArea = GetViewArea();
    UpdateParameterDisplay();
    var center = map.GetCenter();
    $get(positionID).value = center.Latitude + ',' + center.Longitude;
    $get(zoomLevelID).value = map.GetZoomLevel();
    //Use the inbuilt simplification if no server side simplification is performed.
    map.EnableShapeDisplayThreshold(simplificationOption == 3);
    var mode = map.GetMapMode();
    //we only support 2d maps, as we can't properly test the 3d option.
    if (mode == VEMapMode.Mode2D) {
        if (simplificationOption == 1 || Complete) {
            currentShapeIDs = null;
            currentShapeIDs = [];
                        
            pushpinLayer.DeleteAllShapes();
            shapeLayer.DeleteAllShapes();
            //map.DeleteAllShapeLayers();
        }
        LoadShapeFile();
    }
}


//Update the div displaying the parameters.
function UpdateParameterDisplay() {
    var parameterList = document.createElement("ul");
    parameterList.setAttribute("class", "bulletedList");
    for(var i = 0; i < viewAreaQueryParameters.length; i++) {
        var parameterListItem = document.createElement("li");
        parameterListItem.innerText = viewAreaQueryParameters[i].KeyText + ": " + viewAreaQueryParameters[i].ValueText;
        if (viewAreaQueryParameters[i].Key == "DatasetType" && parameterList.hasChildNodes()) {
            parameterList.insertBefore(parameterListItem, parameterList.firstChild);
            datasetType = viewAreaQueryParameters[i].ValueText;
        }
        else
            parameterList.appendChild(parameterListItem);
    }
    var parameterListItem = document.createElement("li");
    var viewAreaText = "View area: " + viewAreaDescription
    parameterListItem.innerText = viewAreaText;
    parameterList.appendChild(parameterListItem);
    var queryDetails = $get('QueryDetails');
    queryDetails.innerHTML = "";
    queryDetails.appendChild(parameterList);
    $get('ViewAreaDisplay').innerHTML = viewAreaText;
}

//return the current viewarea.
function GetViewArea() {
    if (enableViewAreaSelection) {
        return $get("ViewArea").value;
    }
    else {
        var viewAreaSelect = $get("ViewArea");
        var zoomLevel = map.GetZoomLevel();
        if (zoomLevel < 8) {
            viewAreaDescription = viewAreaSelect[0].text;
            //$get("ViewAreaDisplay").innerText = viewAreaSelect[0].text;
            return viewAreaSelect[0].value;
        }
        if (zoomLevel > 10) {
            viewAreaDescription = viewAreaSelect[2].text;
            //$get("ViewAreaDisplay").innerText = viewAreaSelect[2].text;
            return $get("ViewArea")[2].value;
        }
        viewAreaDescription = viewAreaSelect[1].text;
        //$get("ViewAreaDisplay").innerText = viewAreaSelect[1].text;
        return $get("ViewArea")[1].value;
    }
}

//Prepares the parameters for the callback, and calls the method generated on the server.
function LoadShapeFile() {
    var ShapeFileRequest = new Object();
    var view = map.GetMapView();

    ShapeFileRequest.TopLeft = view.TopLeftLatLong;
    ShapeFileRequest.TopRight = view.TopRightLatLong;
    ShapeFileRequest.BottomLeft = view.BottomLeftLatLong;
    ShapeFileRequest.BottomRight = view.BottomRightLatLong;

    ShapeFileRequest.MapWidth = mapWidth;
    ShapeFileRequest.MapHeight = mapHeight;
    ShapeFileRequest.ZoomLevel = map.GetZoomLevel();
    ShapeFileRequest.CurrentShapes = currentShapeIDs;

    ShapeFileRequest.ViewArea = viewArea;
    ShapeFileRequest.SimplificationOption = simplificationOption;

    ShapeFileRequest.ViewAreaQueryParameters = viewAreaQueryParameters;

    GetMapDataFromServer(JSON.stringify(ShapeFileRequest), "");
}

//Receive the data from the server.
function ReceiveMapData(arg, context) {
	if (arg != "") {
		jsonLayer = arg;
		if (arg != null)
			AddLayer(jsonLayer);
	}
	SetWait(false);
	lock = false;
}

//Handle any errors raised by the server.
function ReceiveMapError(error) {
    alert('An error has occurred. Please try another dataset combination, or contact the administrator for help.');
    SetWait(false);
    lock = false;
}

//Add the new set of returned shapes to the map.
function AddLayer(jsonLayer) {    
    var layer = JSON.parse(jsonLayer);
    var shapes = [];
    var pins = [];
    var shapeCount = layer.ShapeList.length;
    if (shapeCount == 0)
    	return;
	//determine which method to call to extract the vertices from the shapes. This will be different depending on wether the vertices have been LZE encoded on the server side.
	var ExtractionMethod = layer.ShapeList[0].CompressedVertices ? GetVELatLongArrayFromCompressed : GetVELatLongArray;
	
	var lineColor = new VEColor(73, 45, 23, 0.8);
	var i = shapeCount;
	var FormatDescriptionMethod;
	//A different method to format the description may have been specified. If not, use the default.
	if (typeof OverrideFormatDescription == 'function')
		FormatDescriptionMethod = OverrideFormatDescription
	else
		FormatDescriptionMethod = DefaultFormatDescription;
    while (i--) {
    	var veShape = new VEShape(VEShapeType.Polygon, ExtractionMethod(layer.ShapeList[i]));
    	veShape.SetFillColor(new VEColor(layer.ShapeList[i].ShapeFillColor.Red, layer.ShapeList[i].ShapeFillColor.Green, layer.ShapeList[i].ShapeFillColor.Blue, layer.ShapeList[i].ShapeFillColor.Alpha / 255));
    	veShape.SetLineColor(lineColor);
    	veShape.SetLineWidth(1);
    	veShape.HideIcon();
    	//only primary shapes need a pin. The primary shape is determined server side.
    	//if (showHideIcons && layer.ShapeList[i].IsPrimaryShape) {
    	if (layer.ShapeList[i].IsPrimaryShape) {
    		vePushPin = new VEShape(VEShapeType.Pushpin, new VELatLong(layer.ShapeList[i].PinPosition.Latitude, layer.ShapeList[i].PinPosition.Longitude));
            vePushPin.SetCustomIcon("Images/MapPin.gif");
//            vePushPin.ShowIcon();
            vePushPin.SetTitle("<h4 style=\"font-size:1.1em;\">" + layer.ShapeList[i].ViewAreaName + "</h4>");
            vePushPin.SetDescription(FormatDescriptionMethod(layer, i));
            pins.push(vePushPin);
        }
        //Add the shape to the array of shapes, and it's id to the array of shapeID's.
        shapes.push(veShape);
        currentShapeIDs.push(layer.ShapeList[i].ShapeId);
    }

    // Add the array of shapes to the shape layer. Bulk adding it to the shapelayer rather than directly to the map is faster, according to the SDK.
    shapeLayer.AddShape(shapes);
    //the layer needs to be hidden and shown to bypass a bug causing the shapes not to appear.
    shapeLayer.Hide();
    shapeLayer.Show();
    pushpinLayer.AddShape(pins);
   }

	//The default function to use when formatting the description used for primary shapes.
   function DefaultFormatDescription(layer, i) {
   	var AddThousandsSeperatorMethod = AddThousandsSeperatorToNumber;
	var selectionDensity;
	if (isNaN(layer.ShapeList[i].AreaSelection))
		selectionDensity = layer.ShapeList[i].AreaSelection
	else
		selectionDensity = Math.round((layer.ShapeList[i].AreaSelection / Math.sqrt(Math.pow(layer.ShapeList[i].ViewAreaSize, 2))) * 10) / 10;
	var quickstatsLink = quickstatsBaseLink + layer.ShapeList[i].QuickStatsId;  
	if (datasetType.toLowerCase() != "individuals")
		quickstatsLink += "&tab=" + datasetType;
	return "<div class=\"DescriptionContents\">"
		+ "Target market: " + AddThousandsSeperatorMethod(layer.ShapeList[i].AreaSelection) + "<br/>"
		+ "Total " + datasetType.toLowerCase() + " in area: " + AddThousandsSeperatorMethod(layer.ShapeList[i].AreaTotal) + "<br/>"
		+ "Market total in NZ: " + AddThousandsSeperatorMethod(layer.SelectedNZPopulation) + "<br/>"
		+ "Total " + datasetType.toLowerCase() + " in NZ: " + AddThousandsSeperatorMethod(layer.TotalNZPopulation) + "<br/>"
		+ "Target market density: " + AddThousandsSeperatorMethod(selectionDensity) + " per km²<br/>" //we calculate the square root of the square because that will make any negative numbers positive, and 0 will become 1.
		+ "Area size: " + AddThousandsSeperatorMethod(Math.round(layer.ShapeList[i].ViewAreaSize)) + " km²</div>"
		+ "<div class=\"DescriptionFootnote\">Symbols:<br/>"
		+ "C confidential<br/>"
		+ "S suppressed<br/>"
		+ ".. figure not available</div>"
		+ "<h4><a class=\"normalLink\" style=\"font-size: 1em;\" href=\"javascript:OpenQuickStatsWindow('" + quickstatsLink + "')\">Click here to view QuickStats</a></h4>";
}

//Gets an array of VELatLong object from an uncompressed shape.
function GetVELatLongArray(shape) {
    var veLatLongs = [];
    for (var i = 0; i < shape.Vertices.length; i++) {
    	veLatLongs.push(new VELatLong(shape.Vertices[i].Latitude, shape.Vertices[i].Longitude));
    }
    return veLatLongs;
}

//Gets an array of VELatLong objects from a shape with compressed vertices. The vertices are decoded first, before creating the array.
function GetVELatLongArrayFromCompressed(shape) {
    var len = shape.CompressedVertices.length;
    var index = 0;
    var array = [];
    var lat = 0;
    var lng = 0;
    try {
        while (index < len) {
            var b;
            var shift = 0;
            var result = 0;
            do {
            	b = shape.CompressedVertices.charCodeAt(index++) - 63;
                result |= (b & 0x1f) << shift;
                shift += 5;
            } while (b >= 0x20);
            var dlat = ((result & 1) ? ~(result >> 1) : (result >> 1));
            lat += dlat;

            shift = 0;
            result = 0;
            do {
            	b = shape.CompressedVertices.charCodeAt(index++) - 63;
                result |= (b & 0x1f) << shift;
                shift += 5;
            } while (b >= 0x20);
            var dlng = ((result & 1) ? ~(result >> 1) : (result >> 1));
            lng += dlng;

            array.push(new VELatLong((lat * 1e-5), (lng * 1e-5)));
        }
    } catch (ex) {
        //error in encoding.
    }
    return array;
}

//Shows or hides the waiting dialog and sets the cursor appropriately.
function SetWait(waiting, enableCover) {
    if (waiting) {
        if (enableCover)
            $get("ProgressUpdater").style.display = "block";
        SetCursor('wait');
    }
    else {
        $get("ProgressUpdater").style.display = "none";
        SetCursor('');
    }
}

//Sets the cursor as specified.
function SetCursor(cursorType) {
    if (cursorType) {
        document.body.style.cursor = cursorType;
        mapAreaDiv.parentNode.style.cursor = cursorType;
        mapAreaDiv.childNodes[0].style.cursor = cursorType;
    }
    else {
        document.body.style.cursor = '';
        mapAreaDiv.parentNode.style.cursor = '';
        mapAreaDiv.childNodes[0].style.cursor = '';
    }
}

//Appends 'New Zealand' to the search string if it does not already contain it. This is to prevent results from other countries returning.
function FindAddress() {
    var addressToFind = $get("AddressSearch").value;
    if (addressToFind.search(/new zealand/i) == -1) {
        addressToFind += ", New Zealand";
    }
    //map.Find(null,
    //        addressToFind);
    results = map.Find(null,
                          addressToFind,
                          null,
                          null,
                          0,
                          10,
                          true,
                          true,
                          true,
                          true,
                          VerifyResult);

}

//Because we append 'New Zealand' to the search string, it will almost always return at least one result. We're not really interested in this country wide ressult, so we display a message.
function VerifyResult(layer, resultsArray, places, hasMore, veErrorMessage) {
	if (places == null || (places.length == 1 && places[0].Name == "New Zealand")) {
		//RefreshMap(true);
		map.ShowMessage('No places in New Zealand could be found using your search text. Try adding the name of a city.');
	}
}

//Removes the default "Search" text from the search box, and sets the font.
function ClearDefaultText() {
    var searchTextBox = $get("AddressSearch");
    if (searchTextBox.value == "Search") {
        searchTextBox.value = "";
        searchTextBox.style.fontStyle = "normal";
    }

}

//Change the variable determining wether info pins are shown, and updates the map.
function ShowHideIcons(newShowHideIcons) {
	if (newShowHideIcons)
		pushpinLayer.Show();
	else
		pushpinLayer.Hide();
    //showHideIcons = newShowHideIcons;
    //RefreshMap(true);
}

function ShowMapTips() {
	$get("MapTips").style.display = "inline";
}

function CloseMapTips() {
	$get("MapTips").style.display = "none";
}

function ShowQueryDetails() {
	$get("QueryDetails").style.display = 'block';
}

function HideQueryDetails() {
	$get("QueryDetails").style.display = 'none';
}

