/*******************************************************************************
 * 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
 ******************************************************************************/


#include <jni.h>

#include "cgal.h"
#include "shotCgal.h"
#include "global.h"
#include "types.h"
#include "Storage.h"


#define CLASS                               Java_edu_duke_donaldLab_jdshot_disco_cgal_Circle
#define Circle_init                         CLASSFN( CLASS, init )
#define Circle_cleanup                      CLASSFN( CLASS, cleanup )
#define Circle_equals                       CLASSFN( CLASS, equals )
#define Circle_getCenter                    CLASSFN( CLASS, getCenter )
#define Circle_getSquaredRadius             CLASSFN( CLASS, getSquaredRadius )
#define Circle_nativeGetOrientation         CLASSFN( CLASS, nativeGetOrientation )
#define Circle_nativeGetSide                CLASSFN( CLASS, nativeGetSide )
#define Circle_isOnBoundedSideFace          CLASSFN( CLASS, isOnBoundedSide__Ledu_duke_donaldLab_jdshot_disco_cgal_Face_2 )
#define Circle_isOnUnboundedSideFace        CLASSFN( CLASS, isOnUnboundedSide__Ledu_duke_donaldLab_jdshot_disco_cgal_Face_2 )
#define Circle_isOnBoundedSideVertex        CLASSFN( CLASS, isOnBoundedSide__Ledu_duke_donaldLab_jdshot_disco_cgal_Vertex_2 )
#define Circle_isOnUnboundedSideVertex      CLASSFN( CLASS, isOnUnboundedSide__Ledu_duke_donaldLab_jdshot_disco_cgal_Vertex_2 )


typedef struct _CirclePair
{
	Circle_2 circle;
	Circular_k_root::Circle_2 circleRoot;

	_CirclePair( )
	{
		// do nothing
		// we just need a no-arg constructor
	}

	_CirclePair( Circle_2 _circle )
	{
		circle = _circle;
		circleRoot = Circular_k_root::Circle_2(
			Circular_k_root::Point_2( circle.center().x(), circle.center().y() ),
			circle.squared_radius()
		);
	}

} CirclePair;

static Storage<CirclePair> g_circles( CGALCLASS( "Circle" ), true );


void circlesCleanup( JNIEnv *jvm )
{
	g_circles.cleanupAll( jvm );
}

Circle_2 *getCircle( JNIEnv *jvm, jobject self )
{
	return &g_circles.get( jvm, self )->circle;
}

Circular_k_root::Circle_2 *getCircleRoot( JNIEnv *jvm, jobject self )
{
	return &g_circles.get( jvm, self )->circleRoot;
}

jobject newCircle( JNIEnv *jvm, const Circle_2 &circle )
{
	return g_circles.addNew( jvm, new CirclePair( circle ) );
}

JNIEXPORT void JNICALL Circle_init( JNIEnv *jvm, jobject self, jdouble x, jdouble y, jdouble squaredRadius, jint orientationId )
{
	START_SIGNAL_HANDLING
	{
		// convert orientation types
		CGAL::Orientation nativeOrientation = orientationId == 0 ? CGAL::CLOCKWISE : CGAL::COUNTERCLOCKWISE;

		// build and store the circles
		Circle_2 nativeCircle = Circle_2( Point_2( x, y ), squaredRadius, nativeOrientation );
		CirclePair *pPair = new CirclePair( nativeCircle );
		g_circles.add( jvm, self, pPair );
	}
	STOP_SIGNAL_HANDLING
}

JNIEXPORT void JNICALL Circle_cleanup( JNIEnv *jvm, jclass c, jint id )
{
	START_SIGNAL_HANDLING
	{
		if( !g_circles.cleanup( id ) )
		{
			throwException( jvm, "Circle cleanup failure!" );
		}
	}
	STOP_SIGNAL_HANDLING
}

JNIEXPORT jboolean JNICALL Circle_equals( JNIEnv *jvm, jobject self, jobject other )
{
	START_SIGNAL_HANDLING
	{
		// check arguments
		if( other == NULL )
		{
			throwIllegalArgumentException( jvm, "other must not be null." );
		}

		return *getCircle( jvm, self ) == *getCircle( jvm, other );
	}
	STOP_SIGNAL_HANDLING
	return false;
}

JNIEXPORT jobject JNICALL Circle_getCenter( JNIEnv *jvm, jobject self )
{
	START_SIGNAL_HANDLING
	{
		Circle_2 *pCircle = getCircle( jvm, self );
		return newVector2(
			jvm,
			to_double( pCircle->center().x() ),
			to_double( pCircle->center().y() )
		);
	}
	STOP_SIGNAL_HANDLING
	return NULL;
}

JNIEXPORT jdouble JNICALL Circle_getSquaredRadius( JNIEnv *jvm, jobject self )
{
	Circle_2 *pCircle = getCircle( jvm, self );
	return to_double( pCircle->squared_radius() );
}

int getOrientationId( CGAL::Orientation orientation )
{
	switch( orientation )
	{
		case CGAL::CLOCKWISE: return 0;
		case CGAL::COUNTERCLOCKWISE: return 1;
		default: return -1;
	}
}

JNIEXPORT jint JNICALL Circle_nativeGetOrientation( JNIEnv *jvm, jobject self )
{
	START_SIGNAL_HANDLING
	{
		Circle_2 *pCircle = getCircle( jvm, self );
		return getOrientationId( pCircle->orientation() );
	}
	STOP_SIGNAL_HANDLING
	return -1;
}

int getSideId( CGAL::Bounded_side side )
{
	switch( side )
	{
		case CGAL::ON_BOUNDED_SIDE: return 0;
		case CGAL::ON_UNBOUNDED_SIDE: return 1;
		case CGAL::ON_BOUNDARY: return 2;
	}
	return -1;
}

JNIEXPORT jint JNICALL Circle_nativeGetSide( JNIEnv *jvm, jobject self, jobject vertex )
{
	START_SIGNAL_HANDLING
	{
		// check arguments
		if( vertex == NULL )
		{
			throwIllegalArgumentException( jvm, "vertex must not be null." );
		}

		Circular_k_root::Circle_2 *pCircleRoot = getCircleRoot( jvm, self );

		// convert the vertex to a root point
		Arrangement::Point_2 point = getVertex( jvm, vertex )->point();
		Circular_k_root::Point_2 pointRoot = Circular_k_root::Point_2( point.x(), point.y() );

		// evaluate the predicate
		return getSideId( pCircleRoot->bounded_side( pointRoot ) );
	}
	STOP_SIGNAL_HANDLING
	return -1;
}

JNIEXPORT jboolean JNICALL Circle_isOnBoundedSideFace( JNIEnv *jvm, jobject self, jobject faceObj )
{
	START_SIGNAL_HANDLING
	{
		const Arrangement::Face *pFace = getFace( jvm, faceObj );
		CirclePair *pPair = g_circles.get( jvm, self );

		// for each halfedge along the face boundary...
		Arrangement::Ccb_halfedge_const_circulator circ = pFace->outer_ccb();
		Arrangement::Ccb_halfedge_const_circulator circStart = circ;
		do
		{
			Arrangement::Halfedge halfedge = *circ;

			// get the source vertex of the edge
			Arrangement::Point_2 point = (*halfedge.source()).point();
			Circular_k_root::Point_2 pointRoot = Circular_k_root::Point_2( point.x(), point.y() );

			switch( pPair->circleRoot.bounded_side( pointRoot ) )
			{
				case CGAL::ON_UNBOUNDED_SIDE:
					return false;
				case CGAL::ON_BOUNDED_SIDE:
					return true;
				default:
				break;
			}
		}
		while( ++circ != circStart );

		// all vertices are on the boundary
		return true;
	}
	STOP_SIGNAL_HANDLING
	return false;
}

JNIEXPORT jboolean JNICALL Circle_isOnUnboundedSideFace( JNIEnv *jvm, jobject self, jobject faceObj )
{
	START_SIGNAL_HANDLING
	{
		return !Circle_isOnBoundedSideFace( jvm, self, faceObj );
	}
	STOP_SIGNAL_HANDLING
	return false;
}

JNIEXPORT jboolean JNICALL Circle_isOnBoundedSideVertex( JNIEnv *jvm, jobject self, jobject vertexObj )
{
	START_SIGNAL_HANDLING
	{
		CirclePair *pPair = g_circles.get( jvm, self );

		// convert the vertex to a root point
		Arrangement::Point_2 point = getVertex( jvm, vertexObj )->point();
		Circular_k_root::Point_2 pointRoot = Circular_k_root::Point_2( point.x(), point.y() );

		return pPair->circleRoot.has_on_bounded_side( pointRoot );
	}
	STOP_SIGNAL_HANDLING
	return false;
}

JNIEXPORT jboolean JNICALL Circle_isOnUnboundedSideVertex( JNIEnv *jvm, jobject self, jobject vertexObj )
{
	START_SIGNAL_HANDLING
	{
		CirclePair *pPair = g_circles.get( jvm, self );

		// convert the vertex to a root point
		Arrangement::Point_2 point = getVertex( jvm, vertexObj )->point();
		Circular_k_root::Point_2 pointRoot = Circular_k_root::Point_2( point.x(), point.y() );

		return pPair->circleRoot.has_on_unbounded_side( pointRoot );
	}
	STOP_SIGNAL_HANDLING
	return false;
}
