/*
 
* Copyright (c) 2016 Metron, Inc.
 
* All rights reserved.
 
*
 
* Redistribution and use in source and binary forms, with or without
 
* modification, are permitted provided that the following conditions are met:
 
*
     
* Redistributions of source code must retain the above copyright
 
*
       
notice, this list of conditions and the following disclaimer.
 
*
     
* Redistributions in binary form must reproduce the above copyright
 
*
       
notice, this list of conditions and the following disclaimer in the
 
*
       
documentation and/or other materials provided with the distribution.
 
*
     
* Neither the name of Metron, Inc. nor the
 
*
       
names of its contributors may be used to endorse or promote products
 
*
       
derived from this software without specific prior written permission.
 
*
 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 
* DISCLAIMED. IN NO EVENT SHALL METRON, INC. BE LIABLE FOR ANY
 
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
*/
package com.metsci.glimpse.charts.shoreline;

import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.metsci.glimpse.util.geo.LatLonGeo;

/**
 
* Representation of land (shoreline).
 
*
 
* Note: LandShape is typically created from a LandFile by calling the toShape() method.
  
This
 
* shape is often cached using a LandManager class which allows the filename to be specified
 
* as a parameter and then loads the file to create the LandShape in a static initializer.
  
An
 
* example is shown below:
 
* <code>
 
* public class SampleLandManager <br>
 
* { <br>
 
*
     
private static final Params PARAMS = SampleLandManager.getParams(); <br>
 
*
     
private static final String landFile = PARAMS.getValue(new StringParam("landFile", "")); <br>
 
*
     
private static final LandShape landShape; <br>
 
*
     
static <br>
 
*
     
{ <br>
 
*
        
if (!landFile.isEmpty()) <br>
 
*
        
{ <br>
 
*
           
landShape = (new NgdcFile(new File(landFilename))).toShape(); <br>
 
*
        
} <br>
 
*
        
else <br>
 
*
        
{ <br>
 
*
           
landShape = null; <br>
 
*
        
} <br>
 
*
     
} <br>
 
*
     
public static LandShape getLandShape() <br>
 
*
     
{ <br>
 
*
        
return landShape; <br>
 
*
     
} <br>
 
* } <br>
 
* </code>
 
*/

public class LandShape
{
    
public static interface VertexConverter
    
{
        
void toXY( double lat, double lon, Point2D.Double xy );
    
}

    
private final List<LandSegment> segments;
    
private final LandVertex swCorner;
    
private final LandVertex neCorner;
    
private final LandBox box;

    
private final Shape suShape;
    
private final VertexConverter suConverter;

    
private final boolean invertFill;

    
public LandShape( List<LandSegment> segments, LandBox box )
    
{
        
assert segments != null;

        
this.box = box;
        
this.segments = Collections.unmodifiableList( new ArrayList<LandSegment>( segments ) );
        
this.swCorner = new LandVertex( box.southLat, box.westLon );
        
this.neCorner = new LandVertex( box.northLat, box.eastLon );
        
this.suConverter = new VertexConverter( )
        
{
            
public void toXY( double lat, double lon, Point2D.Double xy )
            
{
                
xy.x = ( swCorner.getDistanceX_SU( lon ) );
                
xy.y = ( swCorner.getDistanceY_SU( lat ) );
            
}
        
};

        
Shape rawSuShape = getRawFillShape( suConverter );
        
this.invertFill = ( rawSuShape.contains( 0, 0 ) != box.isSwCornerLand );
        
this.suShape = ( invertFill ? invert( rawSuShape, suConverter ) : rawSuShape );
    
}

    
public boolean isLand( double latDeg, double lonDeg )
    
{
        
Point2D.Double xy = new Point2D.Double( );
        
suConverter.toXY( latDeg, lonDeg, xy );
        
return suShape.contains( xy.getX( ), xy.getY( ) );
    
}

    
public Shape getStrokeShape( VertexConverter converter )
    
{
        
Path2D stroke = new Path2D.Double( );
        
Point2D.Double xy = new Point2D.Double( );

        
for ( LandSegment segment : segments )
        
{
            
LandVertex vertex0 = segment.vertices.get( 0 );
            
converter.toXY( vertex0.lat, vertex0.lon, xy );
            
stroke.moveTo( xy.getX( ), xy.getY( ) );

            
for ( int i = 1; i < segment.vertices.size( ); i++ )
            
{
                
LandVertex vertex = segment.vertices.get( i );
                
converter.toXY( vertex.lat, vertex.lon, xy );
                
stroke.lineTo( xy.getX( ), xy.getY( ) );
            
}
        
}
        
return stroke;
    
}

    
public Shape getFillShape( VertexConverter converter )
    
{
        
Shape fill = getRawFillShape( converter );
        
return ( invertFill ? invert( fill, converter ) : fill );
    
}

    
private Shape getRawFillShape( VertexConverter converter )
    
{
        
Path2D fill = new Path2D.Double( Path2D.WIND_EVEN_ODD );
        
Point2D.Double xy = new Point2D.Double( );

        
for ( LandSegment segment : segments )
        
{
            
if ( !segment.isFillable ) continue;

            
LandVertex vertex0 = segment.vertices.get( 0 );
            
converter.toXY( vertex0.lat, vertex0.lon, xy );
            
fill.moveTo( xy.getX( ), xy.getY( ) );

            
for ( int i = 1; i < segment.vertices.size( ); i++ )
            
{
                
LandVertex vertex = segment.vertices.get( i );
                
converter.toXY( vertex.lat, vertex.lon, xy );
                
fill.lineTo( xy.getX( ), xy.getY( ) );
            
}

            
for ( LandVertex ghostVertex : segment.ghostVertices )
            
{
                
converter.toXY( ghostVertex.lat, ghostVertex.lon, xy );
                
fill.lineTo( xy.getX( ), xy.getY( ) );
            
}

            
converter.toXY( vertex0.lat, vertex0.lon, xy );
            
fill.lineTo( xy.getX( ), xy.getY( ) );
        
}
        
return fill;
    
}

    
private Shape invert( Shape shape, VertexConverter converter )
    
{
        
Point2D.Double sw = new Point2D.Double( );
        
converter.toXY( swCorner.lat, swCorner.lon, sw );

        
Point2D.Double ne = new Point2D.Double( );
        
converter.toXY( neCorner.lat, neCorner.lon, ne );

        
double x = Math.min( sw.getX( ), ne.getX( ) );
        
double y = Math.min( sw.getY( ), ne.getY( ) );
        
double w = Math.abs( sw.getX( ) - ne.getX( ) );
        
double h = Math.abs( sw.getY( ) - ne.getY( ) );

        
Area inverted = new Area( new Rectangle2D.Double( x, y, w, h ) );
        
inverted.subtract( new Area( shape ) );
        
return inverted;
    
}

    
public LatLonGeo getSwCorner( )
    
{
        
return LatLonGeo.fromDeg( swCorner.lat, swCorner.lon );
    
}

    
public LatLonGeo getNeCorner( )
    
{
        
return LatLonGeo.fromDeg( neCorner.lat, neCorner.lon );
    
}

    
public List<LandSegment> getSegments( )
    
{
        
return segments;
    
}

    
public LandBox getLandBox( )
    
{
        
return box;
    
}
}