/*******************************************************************************
 * 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_Arrangement
#define Arrangement_init                CLASSFN( CLASS, init )
#define Arrangement_cleanup             CLASSFN( CLASS, cleanup )
#define Arrangement_insert              CLASSFN( CLASS, insert )
#define Arrangement_removeEdge          CLASSFN( CLASS, removeEdge )
#define Arrangement_getNumFaces         CLASSFN( CLASS, getNumFaces )
#define Arrangement_insertAll           CLASSFN( CLASS, insertAll )
#define Arrangement_setObserver         CLASSFN( CLASS, setObserver )
#define Arrangement_getUnboundedFace    CLASSFN( CLASS, getUnboundedFace )


// Jeff: 2010-10-10 - this could be moved into separate H and CPP files, but I'm lazy
class ArrangementObserver : public CGAL::Arr_observer<Arrangement>
{
public:
	ArrangementObserver( JNIEnv *jvm, jobject listener, Arrangement *pArrangement )
	: CGAL::Arr_observer<Arrangement>( *pArrangement )
	{
		// save arguments
		m_listener = jvm->NewGlobalRef( listener );
		m_pArrangement = pArrangement;

		// save method ids
		m_jvm = jvm;
		jclass listenerClass = m_jvm->FindClass( CGALCLASS( "ArrangementObserver" ) );
		checkException( m_jvm );
		m_onInitId = m_jvm->GetMethodID( listenerClass, "onInit", "()V" );
		checkException( m_jvm );
		m_onBeforeSplitFaceId = m_jvm->GetMethodID( listenerClass, "onBeforeSplitFace", "(L" CGALCLASS( "Face" ) ";L" CGALCLASS( "Face" ) ";Z)V" );
		checkException( m_jvm );
		m_onAfterSplitFaceId = m_jvm->GetMethodID( listenerClass, "onAfterSplitFace", "(L" CGALCLASS( "Face" ) ";L" CGALCLASS( "Face" ) ";Z)V" );
		checkException( m_jvm );

		// bubble events
		m_jvm->CallVoidMethod( m_listener, m_onInitId );
		checkException( m_jvm );
	}

	void cleanup( JNIEnv *jvm )
	{
		// cleanup held references
		jvm->DeleteGlobalRef( m_listener );
	}

	virtual void before_split_face( Arrangement::Face_handle hFace, Arrangement::Halfedge_handle hHalfedge )
	{
		// bubble events
		m_jvm->CallVoidMethod(
			m_listener,
			m_onBeforeSplitFaceId,
			newFace( m_jvm, &*hFace ),
			newHalfedge( m_jvm, &*hHalfedge )
		);
		checkException( m_jvm );
	}

	virtual void after_split_face( Arrangement::Face_handle hOldFace, Arrangement::Face_handle hNewFace, bool isHole )
	{
		// bubble events
		m_jvm->CallVoidMethod(
			m_listener,
			m_onAfterSplitFaceId,
			newFace( m_jvm, &*hOldFace ),
			newFace( m_jvm, &*hNewFace ),
			isHole
		);
		checkException( m_jvm );
	}

private:
	jobject m_listener;
	Arrangement *m_pArrangement;

	JNIEnv *m_jvm;
	jmethodID m_onInitId;
	jmethodID m_onBeforeSplitFaceId;
	jmethodID m_onAfterSplitFaceId;
};

typedef struct _ArrangementEntry
{
	Arrangement *pArrangement;
	ArrangementObserver *pObserver;

	_ArrangementEntry( )
	{
		pArrangement = new Arrangement();
		pObserver = NULL;
	}

	~_ArrangementEntry( )
	{
		SAFE_DELETE( pArrangement );
		SAFE_DELETE( pObserver );
	}
} ArrangementEntry;

static Storage<ArrangementEntry> g_arrangements( CGALCLASS( "Arrangement" ), true );

void arrangementsCleanup( JNIEnv *jvm )
{
	g_arrangements.cleanupAll( jvm );
}

Arrangement *getArrangement( JNIEnv *jvm, jobject self )
{
	return g_arrangements.get( jvm, self )->pArrangement;
}

JNIEXPORT void JNICALL Arrangement_init( JNIEnv *jvm, jobject self )
{
	START_SIGNAL_HANDLING
	{
		g_arrangements.add( jvm, self, new ArrangementEntry() );
	}
	STOP_SIGNAL_HANDLING
}

JNIEXPORT void JNICALL Arrangement_cleanup( JNIEnv *jvm, jclass c, jint id )
{
	START_SIGNAL_HANDLING
	{
		g_arrangements.get( id )->pObserver->cleanup( jvm );
		if( !g_arrangements.cleanup( id ) )
		{
			throwException( jvm, "Arrangement cleanup failure!" );
		}
	}
	STOP_SIGNAL_HANDLING
}

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

		// add the curve to the arrangement
		Arrangement *pArrangement = getArrangement( jvm, self );
		Circle_2 *pCircle = getCircle( jvm, circle );
		insert( *pArrangement, *pCircle );
	}
	STOP_SIGNAL_HANDLING
}

JNIEXPORT void JNICALL Arrangement_insertAll( JNIEnv *jvm, jobject self, jobject circles )
{
	START_SIGNAL_HANDLING
	{
		// convert the circles list to a STL Vector

		// get the number of circles in the list
		jclass listClass = jvm->FindClass( "java/util/List" );
		checkException( jvm );
		jmethodID sizeId = jvm->GetMethodID( listClass, "size", "()I" );
		int numCircles = jvm->CallIntMethod( circles, sizeId );
		checkException( jvm );

		// allocate the vector
		std::vector<Circle_2> nativeCircles;
		nativeCircles.reserve( numCircles );

		// iterator over the circles in the list
		jclass iteratorClass = jvm->FindClass( "java/util/Iterator" );
		checkException( jvm );
		jmethodID iteratorId = jvm->GetMethodID( listClass, "iterator", "()Ljava/util/Iterator;" );
		jobject iter = jvm->CallObjectMethod( circles, iteratorId );
		checkException( jvm );

		jmethodID hasNextId = jvm->GetMethodID( iteratorClass, "hasNext", "()Z" );
		jmethodID nextId = jvm->GetMethodID( iteratorClass, "next", "()Ljava/lang/Object;" );

		// for each circle...
		while( jvm->CallBooleanMethod( iter, hasNextId ) == (jboolean)true )
		{
			checkException( jvm );
			jobject circle = jvm->CallObjectMethod( iter, nextId );
			checkException( jvm );
			if( circle == NULL )
			{
				throwException( jvm, "null circles are not allowed" );
			}

			// add it to the vector
			nativeCircles.push_back( *getCircle( jvm, circle ) );
		}

		// finally, build the arrangement using the sweep line algorithm
		Arrangement *pArrangement = getArrangement( jvm, self );
		insert( *pArrangement, nativeCircles.begin(), nativeCircles.end() );
	}
	STOP_SIGNAL_HANDLING
}

JNIEXPORT jint JNICALL Arrangement_getNumFaces( JNIEnv *jvm, jobject self )
{
	START_SIGNAL_HANDLING
	{
		Arrangement *pArrangement = getArrangement( jvm, self );
		return pArrangement->number_of_faces();
	}
	STOP_SIGNAL_HANDLING
	return 0;
}

JNIEXPORT void JNICALL Arrangement_setObserver( JNIEnv *jvm, jobject self, jobject listener )
{
	START_SIGNAL_HANDLING
	{
		ArrangementEntry *pEntry = g_arrangements.get( jvm, self );
		pEntry->pObserver = new ArrangementObserver( jvm, listener, pEntry->pArrangement );
	}
	STOP_SIGNAL_HANDLING
}

JNIEXPORT jobject JNICALL Arrangement_getUnboundedFace( JNIEnv *jvm, jobject self )
{
	START_SIGNAL_HANDLING
	{
		return newFace( jvm, &*getArrangement( jvm, self )->unbounded_face() );
	}
	STOP_SIGNAL_HANDLING
	return NULL;
}
