/*******************************************************************************
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 * 
 * Contact Info:
 * 	Bruce Donald
 * 	Duke University
 * 	Department of Computer Science
 * 	Levine Science Research Center (LSRC)
 * 	Durham
 * 	NC 27708-0129 
 * 	USA
 * 	brd@cs.duke.edu
 * 
 * Copyright (C) 2011 Jeffrey W. Martin and Bruce R. Donald
 * 
 * <signature of Bruce Donald>, April 2011
 * Bruce Donald, Professor of Computer Science
 ******************************************************************************/


package edu.duke.donaldLab.jdshot.test.disco;

import java.util.ArrayList;
import java.util.Iterator;

import edu.duke.donaldLab.jdshot.disco.cgal.Arrangement;
import edu.duke.donaldLab.jdshot.disco.cgal.Box;
import edu.duke.donaldLab.jdshot.disco.cgal.Circle;
import edu.duke.donaldLab.jdshot.disco.cgal.Face;
import edu.duke.donaldLab.jdshot.disco.cgal.FaceIterator;
import edu.duke.donaldLab.jdshot.disco.cgal.Halfedge;
import edu.duke.donaldLab.jdshot.disco.cgal.HalfedgeIterator;
import edu.duke.donaldLab.jdshot.disco.cgal.ShotCgal;
import edu.duke.donaldLab.jdshot.disco.cgal.Vertex;
import edu.duke.donaldLab.jdshot.test.ExtendedTestCase;
import edu.duke.donaldLab.share.geom.Vector2;

public class TestArrangement extends ExtendedTestCase
{
	public void testArrangement( )
	{
		Arrangement arrangement = new Arrangement();
		Circle circle = new Circle( new Vector2( 0.0, 0.0 ), 1.0 );
		arrangement.insert( circle );
		assertEquals( 2, arrangement.getNumFaces() );
	}
	
	public void testFaceIterator( )
	{
		Arrangement arrangement = new Arrangement();
		Circle circle = new Circle( new Vector2( 0.0, 0.0 ), 1.0 );
		arrangement.insert( circle );
		FaceIterator iter = new FaceIterator( arrangement );
		assertTrue( iter.hasNext() );
		Face face1 = iter.next();
		assertTrue( iter.hasNext() );
		Face face2 = iter.next();
		assertFalse( iter.hasNext() );
		
		// check the faces - exactly one should be unbounded
		assertTrue( face1.isUnbounded() ^ face2.isUnbounded() );
	}
	
	public void testFaces( )
	{
		Arrangement arrangement = new Arrangement();
		Circle circle = new Circle( new Vector2( 0.0, 0.0 ), 1.0 );
		arrangement.insert( circle );
		for( Face face : arrangement )
		{
			if( !face.isUnbounded() )
			{
				assertTrue( face.getNumHalfedges() > 0 );
				
				assertNotNull( face.getBoundingBox() );
			}
		}
	}
	
	public void testHalfedgeIterator( )
	{
		Arrangement arrangement = new Arrangement();
		Circle circle = new Circle( new Vector2( 0.0, 0.0 ), 1.0 );
		arrangement.insert( circle );
		for( Face face : arrangement )
		{
			if( face.isUnbounded() )
			{
				continue;
			}
			
			HalfedgeIterator iter = new HalfedgeIterator( face );
			assertTrue( iter.hasNext() );
			assertNotNull( iter.next() );
			assertTrue( iter.hasNext() );
			assertNotNull( iter.next() );
			assertFalse( iter.hasNext() );
		}
	}
	
	public void testTransientHalfedgeIterator( )
	{
		Arrangement arrangement = new Arrangement();
		Circle circle = new Circle( new Vector2( 0.0, 0.0 ), 1.0 );
		arrangement.insert( circle );
		for( Face face : arrangement )
		{
			if( face.isUnbounded() )
			{
				continue;
			}
			
			HalfedgeIterator iter = new HalfedgeIterator( face, true );
			assertTrue( iter.hasNext() );
			assertNotNull( iter.next() );
			assertTrue( iter.hasNext() );
			assertNotNull( iter.next() );
			assertFalse( iter.hasNext() );
		}
	}
	
	public void testHalfedges( )
	{
		Arrangement arrangement = new Arrangement();
		Circle circle = new Circle( new Vector2( 0.0, 0.0 ), 1.0 );
		arrangement.insert( circle );
		for( Face face : arrangement )
		{
			if( face.isUnbounded() )
			{
				continue;
			}
			
			assertEquals( 2, face.getNumHalfedges() );
			ArrayList<Halfedge> halfedges = new ArrayList<Halfedge>( face.getNumHalfedges() );
			for( Halfedge halfedge : face )
			{
				halfedges.add( halfedge );
				
				// check the circle
				Circle halfedgeCircle = halfedge.getCircle();
				assertNotNull( halfedgeCircle );
				assertTrue( halfedgeCircle.equals( circle ) );
				
				// make sure we have vertices
				assertNotNull( halfedge.getSource() );
				assertNotNull( halfedge.getTarget() );
				
				// make sure we have a bounding box
				assertNotNull( halfedge.getBoundingBox() );
			}
			
			// make sure the vertices are linked together correctly
			assertTrue( halfedges.get( 0 ).getSource().equals( halfedges.get( 1 ).getTarget() ) );
			assertTrue( halfedges.get( 1 ).getSource().equals( halfedges.get( 0 ).getTarget() ) );
		}
	}
	
	public void testTransientHalfedges( )
	{
		Arrangement arrangement = new Arrangement();
		Circle circle = new Circle( new Vector2( 0.0, 0.0 ), 1.0 );
		arrangement.insert( circle );
		for( Face face : arrangement )
		{
			if( face.isUnbounded() )
			{
				continue;
			}
			
			assertEquals( 2, face.getNumHalfedges() );
			ArrayList<Halfedge> halfedges = new ArrayList<Halfedge>( face.getNumHalfedges() );
			for( Halfedge halfedge : face.transientHalfedges() )
			{
				halfedges.add( halfedge );
				
				// check the circle
				Circle halfedgeCircle = halfedge.getCircle();
				assertNotNull( halfedgeCircle );
				assertTrue( halfedgeCircle.equals( circle ) );
				
				// make sure we have vertices
				assertNotNull( halfedge.getSource() );
				assertNotNull( halfedge.getTarget() );
			}
		}
	}
	
	public void testCircle( )
	{
		// check construction and accessors
		Circle circle = new Circle( new Vector2( 4.0, 5.0 ), 4.0, Circle.Orientation.Counterclockwise );
		assertEquals( new Vector2( 4.0, 5.0 ), circle.getCenter() );
		assertEquals( 4.0, circle.getSquaredRadius() );
		assertEquals( Circle.Orientation.Counterclockwise, circle.getOrientation() );
	}
	
	public void testCirclePredicateSide( )
	{
		// build a test arrangement
		Arrangement arrangement = new Arrangement();
		Circle circle1 = new Circle( new Vector2( 0.0, 0.0 ), 1.0 );
		Circle circle2 = new Circle( new Vector2( 1.0, 0.0 ), 1.0 );
		arrangement.insert( circle1 );
		arrangement.insert( circle2 );
		
		// find interesting vertices
		Vertex bounded1 = findVertexAt( arrangement, new Vector2( 0.0, 0.0 ) );
		assertNotNull( bounded1 );
		assertEquals( Circle.Side.BoundedSide, circle1.getSide( bounded1 ) );
		
		Vertex unbounded1 = findVertexAt( arrangement, new Vector2( 2.0, 0.0 ) );
		assertNotNull( unbounded1 );
		assertEquals( Circle.Side.UnboundedSide, circle1.getSide( unbounded1 ) );
		
		Vertex boundary1 = findVertexAt( arrangement, new Vector2( 1.0, 0.0 ) );
		assertNotNull( boundary1 );
		assertEquals( Circle.Side.Boundary, circle1.getSide( boundary1 ) );
	}
	
	public void testCleanupArrangements( )
	throws Exception
	{
		try
		{
			ShotCgal.cleanup();
			ShotCgal.init();
			
			// allocate some objects
			for( int i=0; i<100; i++ )
			{
				new Arrangement();
			}
			
			// try to garbage collect
			System.gc();
			Thread.sleep( 200 );
			
			assertEquals( 100, ShotCgal.cleanupUnreferenced() );
		}
		finally
		{
			ShotCgal.cleanup();
		}
	}
	
	public void testCleanupCircles( )
	throws Exception
	{
		try
		{
			ShotCgal.cleanup();
			ShotCgal.init();
			
			// allocate some objects and discard the references
			for( int i=0; i<100; i++ )
			{
				new Circle( new Vector2( 0.0, 0.0 ), 1.0 );
			}
			
			// try to garbage collect
			System.gc();
			Thread.sleep( 200 );
			
			assertEquals( 100, ShotCgal.cleanupUnreferenced() );
		}
		finally
		{
			ShotCgal.cleanup();
		}
	}
	
	public void testCleanupFaceIterators( )
	throws Exception
	{
		try
		{
			ShotCgal.cleanup();
			ShotCgal.init();
			
			// keep strong references to these objects
			Arrangement arrangement = new Arrangement();
			
			// allocate some objects and discard the references
			for( int i=0; i<100; i++ )
			{
				new FaceIterator( arrangement );
			}
			
			// try to garbage collect
			System.gc();
			Thread.sleep( 200 );
			
			assertEquals( 100, ShotCgal.cleanupUnreferenced() );
		}
		finally
		{
			ShotCgal.cleanup();
		}
	}
	
	public void testCleanupHalfedgeIterators( )
	throws Exception
	{
		try
		{
			ShotCgal.cleanup();
			ShotCgal.init();
			
			// keep strong references to these objects
			Arrangement arrangement = new Arrangement();
			Circle circle = new Circle( new Vector2( 0.0, 0.0 ), 1.0 );
			arrangement.insert( circle );
			Iterator<Face> iter = arrangement.iterator();
			@SuppressWarnings( "unused" )
			Face unboundedFace = iter.next();
			Face face = iter.next();
			
			// allocate some objects and discard the references
			for( int i=0; i<100; i++ )
			{
				new HalfedgeIterator( face );
			}
			
			// try to garbage collect
			System.gc();
			Thread.sleep( 200 );
			
			assertEquals( 100, ShotCgal.cleanupUnreferenced() );
		}
		finally
		{
			ShotCgal.cleanup();
		}
	}
	
	public void testCleanupBoxes( )
	throws Exception
	{
		try
		{
			ShotCgal.cleanup();
			ShotCgal.init();
			
			// allocate some objects and discard the references
			for( int i=0; i<100; i++ )
			{
				new Box( 0.0, 0.0, 0.0, 0.0 );
			}
			
			// try to garbage collect
			System.gc();
			Thread.sleep( 200 );
			
			assertEquals( 100, ShotCgal.cleanupUnreferenced() );
		}
		finally
		{
			ShotCgal.cleanup();
		}
	}
	
	public void testCleanupNonConstructables( )
	throws Exception
	{
		try
		{
			ShotCgal.cleanup();
			ShotCgal.init();
			
			// keep strong references to these objects
			Arrangement arrangement = new Arrangement();
			Circle circle = new Circle( new Vector2( 0.0, 0.0 ), 1.0 );
			arrangement.insert( circle );
			
			// allocate and lose references to some objects
			/* NOTE:
				Use explicit iterators here to avoid weird leftover references (?)
				to iterators from the for ( ... ) syntax that are preventing
				garbage collection somehow.
			*/
			FaceIterator iterFace = new FaceIterator( arrangement );
			while( iterFace.hasNext() )
			{
				Face face = iterFace.next();
				
				if( face.isUnbounded() )
				{
					continue;
				}
				
				HalfedgeIterator iterHalfedge = new HalfedgeIterator( face );
				while( iterHalfedge.hasNext() )
				{
					Halfedge halfedge = iterHalfedge.next();
					
					halfedge.getCircle();
					halfedge.getSource();
					halfedge.getTarget();
				}
			}
			iterFace = null;
			
			// try to garbage collect
			System.gc();
			Thread.sleep( 200 );
			
			/* should have
				2 faces
				2 halfedges
				2 circles
				4 vertices
				1 face iterator
				1 halfedge iterator
			*/
			assertEquals( 2 + 2 + 2 + 4 + 1 + 1, ShotCgal.cleanupUnreferenced() );
		}
		finally
		{
			ShotCgal.cleanup();
		}
	}
	
	private Vertex findVertexAt( Arrangement arrangement, Vector2 point )
	{
		for( Face face : arrangement )
		{
			// skip the unbounded face
			if( face.isUnbounded() )
			{
				continue;
			}
			
			for( Halfedge halfedge : face )
			{
				Vertex source = halfedge.getSource();
				if( source.toPoint().equals( point ) )
				{
					return source;
				}
				
				Vertex target = halfedge.getTarget();
				if( target.toPoint().equals( point ) )
				{
					return target;
				}
			}
		}
		
		return null;
	}
}
