/* --------------------------------------------------------------------------------
 *
 * Open Asset Import Library (ASSIMP) (http://assimp.sourceforge.net)
 * Assimp2Java bridge 
 *
 * Copyright (c) 2006-2009, ASSIMP Development Team
 * All rights reserved. See the LICENSE file for more information.
 *
 * --------------------------------------------------------------------------------
 */

#include "jbridge_pch.h"
using namespace Assimp;

// include the header files generated by javah
#include "assimp_Importer.h"
#include <list>

// used as error return code
#define AI_JNI_ERROR_RETURN 0xffffffff

////////////////////////////////////////////////////////////////////////////////////
/* typedef for a jassimp context, used to identify the Importer object which
 * belongs to a particular java Importer.
 */
////////////////////////////////////////////////////////////////////////////////////

typedef uint64_t JASSIMP_CONTEXT;

#ifdef JASSIMP_DEBUG_CHECKS
	typedef std::list< JASSIMP_CONTEXT > ImporterContextList;
	static ImporterContextList g_listActiveContexts;

// ------------------------------------------------------------------------------------------------
/* DEBUG: Check the validity of a particular context.
*/
bool jValidateContext (JASSIMP_CONTEXT context)
{
	ImporterContextList::const_iterator t =  std::find( g_listActiveContexts.begin(),g_listActiveContexts.end(),context);
	if (t != g_listActiveContexts.end() {
		return true;
	}

	DefaultLogger::get()->error("[jnibridge] Invalid context");
	return false;
}

// ------------------------------------------------------------------------------------------------
/* DEBUG: Check the validity of a particular scene
*/
bool jValidateScene (const aiScene* scene)
{
	if (!scene)	{
		DefaultLogger::get()->error("[jnibridge] There's not asset");
		return false;
	}
	return true;
}
#endif // ! ASSIMP_DEBUG

// ------------------------------------------------------------------------------------------------
/* Get the #Assimp::Importer for a particular JASSIMP_CONTEXT
*/
Assimp::Importer* jGetValidImporterScenePair (JASSIMP_CONTEXT jvmcontext)
{
#ifdef JASSIMP_DEBUG_CHECKS
	if (!jValidateContext((JASSIMP_CONTEXT)jvmcontext)) {
		return NULL;
	}
#endif // ! ASSIMP_DEBUG

	// get the importer instance from the context
	Assimp::Importer* pcImp = (Assimp::Importer*)jvmcontext;

#ifdef DEBUG
	if (!jValidateScene(pcImp->GetScene())) {
		return NULL;
	}
#endif // ! ASSIMP_DEBUG
	return pcImp;
}

// ------------------------------------------------------------------------------------------------
/*
* Class:     assimp_Importer
* Method:    _NativeInitContext
* Signature: ()I
*/
JNIEXPORT jlong JNICALL Java_assimp_Importer__1NativeInitContext
(JNIEnv * jvmenv, jobject jvmthis, jint version)	{
	// 2^64-1 indicates error
	JASSIMP_CONTEXT context = 0xffffffffffffffffL;

	if (version != assimp_Importer_ABI_VERSION) {
		return context;
	}

	// create a new Importer instance
	Assimp::Importer* pcImp = new Assimp::Importer();
	context = (JASSIMP_CONTEXT)(uintptr_t)pcImp;

#ifdef JASSIMP_DEBUG_CHECKS
	g_listActiveContexts.push_back(context);
#endif // ! ASSIMP_DEBUG

	// need to setup the logger ... or did we already initialize it?
	JNILogDispatcher* pcLogger;
	if (DefaultLogger::isNullLogger())	{
		pcLogger = new JNILogDispatcher();
		DefaultLogger::set (pcLogger);
	}
	else	{
		JNILogDispatcher* pcLogger = ( JNILogDispatcher* )DefaultLogger::get();
		pcLogger->AddRef();
	}

	// setup the JNI environment  
	if(!JNIEnvironment::Get()->AttachToCurrentThread(jvmenv)) {
		return 0xffffffffffffffffL;
	}

	// return our native context handle to the caller
	return context;
}

// ------------------------------------------------------------------------------------------------
/*
* Class:     assimp_Importer
* Method:    _NativeFreeContext
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_assimp_Importer__1NativeFreeContext
(JNIEnv * jvmenv, jobject jvmthis, jlong jvmcontext)
{

#ifdef JASSIMP_DEBUG_CHECKS
	if (!jValidateContext((JASSIMP_CONTEXT)jvmcontext)) {
		return AI_JNI_ERROR_RETURN;
	}
#endif // ! ASSIMP_DEBUG

	// delete the underlying Importer instance
	Assimp::Importer* pcImp = (Assimp::Importer*)jvmcontext;
	delete pcImp;

#ifdef JASSIMP_DEBUG_CHECKS
	g_listActiveContexts.remove(jvmcontext);
#endif // ! ASSIMP_DEBUG

	JNIEnvironment::Get()->DetachFromCurrentThread();
	return 0;
}

// ------------------------------------------------------------------------------------------------
/*
* Class:     assimp_Importer
* Method:    _NativeLoad
* Signature: (Ljava/lang/String;II)I
*/
JNIEXPORT jint JNICALL Java_assimp_Importer__1NativeLoad
(JNIEnv *jvmenv, jobject jvmthis, jstring jvmpath, jint jvmflags, jlong jvmcontext)
{
	jint iRet = 0;

#ifdef JASSIMP_DEBUG_CHECKS
	if (!jValidateContext((JASSIMP_CONTEXT)jvmcontext)) {
		return AI_JNI_ERROR_RETURN;
	}
#endif // ! ASSIMP_DEBUG

	// get the path from the jstring
	const char* szPath = JNU_GetStringNativeChars(jvmenv,jvmpath);
	if (!szPath)	{
		DefaultLogger::get()->error("[jnibridge] Unable to get path string from the java vm");
		return AI_JNI_ERROR_RETURN;
	}
	// get the importer instance from the context
	Assimp::Importer* pcImp = (Assimp::Importer*)jvmcontext;
	const aiScene* pcOut;

	// and load the file. The aiScene object itself remains accessible via Importer.GetScene().
	if(!(pcOut = pcImp->ReadFile(std::string(szPath),(unsigned int)jvmflags)))	{

		DefaultLogger::get()->error("[jnibridge] Unable to load asset");
		free((void*)szPath);
		return AI_JNI_ERROR_RETURN;
	}

	free((void*)szPath);

	// allocate a new assimp.Scene object to be returned by the importer
	jobject jScene;
	if(!(jScene = jvmenv->AllocObject(AIJ_GET_HANDLE(assimp.Importer.Class))))	{

		DefaultLogger::get()->error("[jnibridge] Unable to allocate output scene");
		return AI_JNI_ERROR_RETURN;
	}

	// fill the assimp.Scene instance with our data ...
	JNIEnvironment::Get()->assimp.Scene.Fill(jScene,pcOut);

	// ...  and store it in the Importer instance
	jvmenv->SetObjectField(jvmthis,AIJ_GET_HANDLE(assimp.Importer.scene),jScene);

	// .. and finally we don't need th scene anymore
	pcImp->FreeScene();
	return iRet;
}

// ------------------------------------------------------------------------------------------------
/*
* Class:     assimp_Importer
* Method:    _NativeSetPropertyInt
* Signature: (Ljava/lang/String;IJ)I
*/
JNIEXPORT jint JNICALL Java_assimp_Importer__1NativeSetPropertyInt
(JNIEnv * jvmenv, jobject _this, jstring name, jint value, jlong jvmcontext)
{
#ifdef JASSIMP_DEBUG_CHECKS
	if (!jValidateContext((JASSIMP_CONTEXT)jvmcontext)) {
		return AI_JNI_ERROR_RETURN;
	}
#endif // ! ASSIMP_DEBUG

	Assimp::Importer* pcImp = (Assimp::Importer*)jvmcontext;
	const char* sz = JNU_GetStringNativeChars(jvmenv,name);

	// set the property
	pcImp->SetPropertyInteger(sz,(int)value,NULL);
	free((void*)sz);
	return 0;
}


// ------------------------------------------------------------------------------------------------
/*
* Class:     assimp_Importer
* Method:    _NativeSetPropertyFloat
* Signature: (Ljava/lang/String;FJ)I
*/
JNIEXPORT jint JNICALL Java_assimp_Importer__1NativeSetPropertyFloat
(JNIEnv * jvmenv, jobject _this, jstring name, jfloat value, jlong jvmcontext)
{
#ifdef JASSIMP_DEBUG_CHECKS
	if (!jValidateContext((JASSIMP_CONTEXT)jvmcontext)) {
		return AI_JNI_ERROR_RETURN;
	}
#endif // ! ASSIMP_DEBUG

	Assimp::Importer* pcImp = (Assimp::Importer*)jvmcontext;
	const char* sz = JNU_GetStringNativeChars(jvmenv,name);

	// set the property
	pcImp->SetPropertyFloat(sz,(float)value,NULL);
	free((void*)sz);
	return 0;
}

// ------------------------------------------------------------------------------------------------
/*
* Class:     assimp_Importer
* Method:    _NativeSetPropertyString
* Signature: (Ljava/lang/String;Ljava/lang/String;J)I
*/
JNIEXPORT jint JNICALL Java_assimp_Importer__1NativeSetPropertyString
(JNIEnv * jvmenv, jobject _this, jstring name, jstring value, jlong jvmcontext)
{
#ifdef JASSIMP_DEBUG_CHECKS
	if (!jValidateContext((JASSIMP_CONTEXT)jvmcontext)) {
		return AI_JNI_ERROR_RETURN;
	}
#endif // ! ASSIMP_DEBUG

	Assimp::Importer* pcImp = (Assimp::Importer*)jvmcontext;
	const char* sz = JNU_GetStringNativeChars(jvmenv,name);
	const char* sc = JNU_GetStringNativeChars(jvmenv,value);

	// set the property
	pcImp->SetPropertyString(sz,sc,NULL);
	free((void*)sz);
	free((void*)sc);
	return 0;
}