
// Global variables
var outerLineColor = "#999999";
var outerFillColor = "#999999";
var outerLineWeight = 0;
var outerLineOpacity = .5;
var outerFillOpacity = .3;

// Eastern hemisphere is 0E to 180E.
// Western hemisphere is -180E to 0E.
// We use a 10 deg border at 0E to ensure each hemisphere maps correctly and does not wrap
var maxLat = 85;  // 85
var minLng = 5;  // 5
var maxLng = 180;  // 180
var worldWestHemSouthWestLat = -maxLat;
var worldWestHemSouthWestLng = -maxLng;
var worldWestHemNorthEastLat = maxLat;
var worldWestHemNorthEastLng= -minLng;
var worldEastHemSouthWestLat = -maxLat;
var worldEastHemSouthWestLng = minLng;
var worldEastHemNorthEastLat = maxLat;
var worldEastHemNorthEastLng= maxLng;


//
// Draw a polygon in a cut donut shape that covers the rest of the world outside
// of the given region of interest.
// It assumed that the region of interest is in the western hemisphere (0 to 180 E).
//
// regionOfInterest is of type GLatLngBounds.
//
/* Polygon method

      4 ---------------- 1, 5, 11
        |            / |
        |          /   |
        |  7-----6,10  |
        |   |   |      |
        |   |   |      |
        |  8-----9     |
        |              |
      3 ---------------- 2
*/



//
// Calculate the maximum northeast and southwest bounds surrounding both
// the map bounds and the region of interest polygon.
// roiPoly is of type Array(GLatLng).
// mapBounds is of type GLatLngBounds.
//
function _maxOuterRegion(roiPoly, mapBounds) {
    var minSWLat = mapBounds.getSouthWest().lat();
    var minSWLng = mapBounds.getSouthWest().lng();
    var maxNELat = mapBounds.getNorthEast().lat();
    var maxNELng = mapBounds.getNorthEast().lng();
    for (x in roiPoly) {
        if (roiPoly[x].lat() < minSWLat) {minSWLat = roiPoly[x].lat()}
        if (roiPoly[x].lng() < minSWLng) {minSWLng = roiPoly[x].lng()}
        if (roiPoly[x].lat() > maxNELat) {maxNELat = roiPoly[x].lat()}
        if (roiPoly[x].lng() > maxNELng) {maxNELng = roiPoly[x].lng()}
    }
    return new GLatLngBounds( new GLatLng(minSWLat,minSWLng), new GLatLng(maxNELat,maxNELng) );
}

//
// Calculate the maximum northeast and southwest bounds surrounding both
// the map bounds and the region of interest polygon.
// roiBounds is of type GLatLngBounds.
// mapBounds is of type GLatLngBounds.
//
function _maxOuterRegionByBounds(roiBounds, mapBounds) {
    var maxNorthEast = new GLatLng(	Math.max(roiBounds.getNorthEast().lat(),
    					    mapBounds.getNorthEast().lat()),
    					Math.max(roiBounds.getNorthEast().lng(),
    					    mapBounds.getNorthEast().lng()));
    var minSouthWest = new GLatLng(	Math.min(roiBounds.getSouthWest().lat(),
    					    mapBounds.getSouthWest().lat()),
    					Math.min(roiBounds.getSouthWest().lng(),
    					    mapBounds.getSouthWest().lng()));
    return new GLatLngBounds(minSouthWest, maxNorthEast);
}

//
// Calculate the vertex of a polygon that is closest to a given point.
// roiPoly is of type Array(GLatLng).
// refLatLng is of type GLatLng.
//
function _minDistToMap(roiPoly, refLatLng) {
    var mindist = 40075020;  // Approximate circumference of Earth
    var index = -1;
    for (x in roiPoly) {
        var d = refLatLng.distanceFrom(roiPoly[x]);
        if (d < mindist) {
            mindist = d;
            index = x;
        }
    }
    return [index, mindist];
}

//
// Deduce the direction of a polygon (the order of the vertices), clockwise or anti-colockwise
// ROIPOLY is of type Array(GLatLng).
// INDEXxx is of type integer.
//
// ROIPOLY must contain at least points. If at least three points are different then the returned
// value indicates if the order of the vertices are clockwise (1) or anti-clockwise(0) for a polygon
// plotted on a map and viewed on a screen. If there are three points in roiPoly and they are not
// all different then the result is undefined and the returned value is -1.
// INDEXxx is the array index to roiPoly for the point closest to a reference point, such as the
// north-east corner of the map. For example,
//    mapNorthEast.distanceFrom( roiPoly[indexNE] ) <= mapNorthEast.distanceFrom( roiPoly[x] )
//
function _isClockwise(roiPoly, indexNE, indexNW, indexSE) {
    var x;
    for (x=indexNE+1; x<roiPoly.length(); x++) {
        if (roiPoly[indexNW].equals( roiPoly[x] )) {return 0} // Anit-clockwise
        if (roiPoly[indexSE].equals( roiPoly[x] )) {return 1} // Clockwise
    }
    for (x=0; x<indexNE; x++) {
        if (roiPoly[indexNW].equals( roiPoly[x] )) {return 0} // Anit-clockwise
        if (roiPoly[indexSE].equals( roiPoly[x] )) {return 1} // Clockwise
    }
}


//
// The main function.
// Create a set of polygons that, when filled, cover the region between the map or world bounds
// and the region-of-interest polygon. That is, create an inverse mask from polygons.
//
// The issue is complicated by gmap's wrapping of polygon's around 180,-180 longitude.
// Future features will include arbitrary polygons, and handling of polygons that span 180,-180.
//
// Present assumptions:
// 1. The polygon is rectanglular.
// 2. The polygon is defined anti-clockwise.
// 3. The polygon is wholly contained within the eastern hemisphere of the world.
//
function drawOuterRegion(inputPolygon) {

    // Define the outer bounds of the resultant polygon(s).
    // These may be:
    //    1. The current map view.
    //    2. The maximum bounds that include the map and the polygon.
    //    3. The world as eastern and western hemispheres.
    //
    // 1. Map only (as variables for the corners)
    //var mapBounds = map.getBounds();
    //var mapSouthWest = mapBounds.getSouthWest();
    //var mapNorthEast = mapBounds.getNorthEast();
    //var mapNorthWest = new GLatLng(mapNorthEast.lat(), mapSouthWest.lng());
    //var mapSouthEast = new GLatLng(mapSouthWest.lat(), mapNorthEast.lng());
    // 
    // 2. Maximum of map and polygon (as variables for the corners)
    //var mapBounds = maxOuterRegion(inputPolygon, map.getBounds());
    //var mapSouthWest = mapBounds.getSouthWest();
    //var mapNorthEast = mapBounds.getNorthEast();
    //var mapNorthWest = new GLatLng(mapNorthEast.lat(), mapSouthWest.lng());
    //var mapSouthEast = new GLatLng(mapSouthWest.lat(), mapNorthEast.lng());

    // 3. Eastern and western hemispheres (as CLOCKWISE polygons)
    var hemisEast = new Array();
    hemisEast.push( new GLatLng(worldEastHemNorthEastLat, worldEastHemNorthEastLng) );
    hemisEast.push( new GLatLng(worldEastHemSouthWestLat, worldEastHemNorthEastLng) );
    hemisEast.push( new GLatLng(worldEastHemSouthWestLat, worldEastHemSouthWestLng) );
    hemisEast.push( new GLatLng(worldEastHemNorthEastLat, worldEastHemSouthWestLng) );
    hemisEast.push( new GLatLng(worldEastHemNorthEastLat, worldEastHemNorthEastLng) );
    var hemisWest = new Array();
    hemisWest.push( new GLatLng(worldWestHemNorthEastLat, worldWestHemNorthEastLng) );
    hemisWest.push( new GLatLng(worldWestHemSouthWestLat, worldWestHemNorthEastLng) );
    hemisWest.push( new GLatLng(worldWestHemSouthWestLat, worldWestHemSouthWestLng) );
    hemisWest.push( new GLatLng(worldWestHemNorthEastLat, worldWestHemSouthWestLng) );
    hemisWest.push( new GLatLng(worldWestHemNorthEastLat, worldWestHemNorthEastLng) );
    var hemisGreenwich = new Array();
    hemisGreenwich.push( new GLatLng(worldEastHemNorthEastLat, worldEastHemSouthWestLng) );
    hemisGreenwich.push( new GLatLng(worldEastHemSouthWestLat, worldEastHemSouthWestLng) );
    hemisGreenwich.push( new GLatLng(worldWestHemSouthWestLat, worldWestHemNorthEastLng) );
    hemisGreenwich.push( new GLatLng(worldWestHemNorthEastLat, worldWestHemNorthEastLng) );
    hemisGreenwich.push( new GLatLng(worldEastHemNorthEastLat, worldEastHemSouthWestLng) );

    /* The generic version would involve a number of careful steps. We need to:
        1. Know the direction of the polygon, especially when the polygon is contained
           wholly within the map domain, ie the 'cut donut'. If the path that defines the
           resultant polygon contains a cross-over during the 'cut' then Chrome will fill the
           inner polygon. To avoid this we need to ensure that the outer and inner polygons
           are defined in opposite directions. That is, one clockwise and the other anti-clockwise.
           The direction of a polygon can be reversed simply by reversing the order of the vertices.
        2. Know where the polygon spans 180,-180 and correctly cut and rejoin the polygon as
           many times as required to finish with a set of eastern polygon(s) and western polygon(s).

       Proposed algorithm:
        1. Step around the polygon (direction doesnt matter) building polygon(s) for the eastern
           and western hemispheres.
           If the first point is in the eastern hemisphere then add each point, including the first,
           to eastPoly.
           If the polygon changes hemispheres then:
               a. find the latitude at the point crossing 180,-180 longitude.
               b. add the crossing point (180 lng) to eastPoly.
               c. start westPoly with the crossing point (-180 lng).
               d. continuing adding polygon point to westPoly until another change of hemisphere.
               e. find the crossing point and this to each of westPoly and eastPoly.
           At completion of the polygon, ensure the tail joins the head for both eastPoly and westPoly.
        2. If the polygon crossed 180,-180 then this will give two polygons, eastPoly and westPoly.
           The edges of these polygons will contain segments of 180,-180 longitude but this should be
           ok when plotting. If not, will need to be smarter about building these polygons.
        3. If the polygon does not cross 180,-180 then eastPoly and westPoly will be equivalent to
           the polygon and the other will be empty.
        4. Determine the closest point in eastPoly and westPoly to mapNorthEast, mapNorthWest and
           mapSouthEast. Use,
               [indexNE,dist] = _minDistToMap(roiPoly, mapNorthEast);
               [indexNW,dist] = _minDistToMap(roiPoly, mapNorthWest);
               [indexSE,dist] = _minDistToMap(roiPoly, mapSouthEast);
        5. Determine the direction of eastPoly and/or westPoly, and change to anti-clockwise if
           required. Use,
               var clock = _isClockwise(roiPoly, indexNE, indexNW, indexSW);
               if (clock == 1) {
                   var r2 = Array();
                   for (i=0; i<eastPoly.length(); i++) {
                      r2[i] = eastPoly[-1-i];
                   }
                   roiPoly = r2;
               }  elseif (clock != 0) {
                   // Error message
               }
        6. Re-order eastPoly and/or westPoly to start/end at indexNE.
        7. Define the output polygons to be a concatenation of the map or hemisphere boundary and
           eastPoly or westPoly, respectively.
    */

    /* The following is a quick solution that:
        1. Uses the eastern and western hemispheres.
        2. Assumes the polygon is contained wholly within the eastern hemisphere only.
        3. Assumes the polygon is either defined as GLatLngBounds or an anti-clockwise polygon.
    */

    // Test if roiPolygon is of type GLatLngBounds.
    // If so, convert it to an anti-clockwise polygon.
    var roiPolygon = new Array();
    var x;
    if (inputPolygon instanceof GLatLngBounds) {
        var inputSouthWest = inputPolygon.getSouthWest();
        var inputNorthEast = inputPolygon.getNorthEast();
        roiPolygon.push( inputNorthEast );
        roiPolygon.push( new GLatLng(inputNorthEast.lat(), inputSouthWest.lng()) );
        roiPolygon.push( inputSouthWest );
        roiPolygon.push( new GLatLng(inputSouthWest.lat(), inputNorthEast.lng()) );
        roiPolygon.push( inputNorthEast );
    } else {
        // Assume its a polygon
        for (x in inputPolygon) {
            roiPolygon.push(inputPolygon[x]);
        }
    }


    // Add roiBounds
    var currPoly = hemisEast;
    for (x in roiPolygon) {
        currPoly.push( roiPolygon[x] );
    }
    currPoly.push( currPoly[0] );

    var polys = new Array();
    polys.push(currPoly);
    polys.push(hemisWest);
    polys.push(hemisGreenwich);

    for (p in polys) {
        polys[p] = new GPolygon(polys[p], outerLineColor, outerLineWeight, outerLineOpacity,
                                 outerFillColor, outerFillOpacity, {clickable : false});
    }

    return polys;
}



