/*
 *			GPAC - Multimedia Framework C SDK
 *
 *				Authors: Jean Le Feuvre
 *				Copyright (c) Telecom ParisTech 2009-
 *					All rights reserved
 *
 *	Created by NGO Van Luyen, Ivica ARSOV / ARTEMIS / Telecom SudParis /Institut TELECOM on Oct, 2010
 *
 *  This file is part of GPAC / Wrapper
 *
 *  GPAC 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, or (at your option)
 *  any later version.
 *
 *  GPAC 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; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <jni.h>

#include <gpac/terminal.h>
#include <gpac/thread.h>
#include <gpac/options.h>
#include <gpac/modules/service.h>
#include <gpac/internal/terminal_dev.h>
#include <gpac/internal/compositor_dev.h>

#include "wrapper.h"
#include "wrapper_jni.c"

#include <math.h>
#include <android/log.h>

#define TAG "GPAC_WRAPPER"

#define LOGV(X, Y)  __android_log_print(ANDROID_LOG_VERBOSE, TAG, X, Y)
#define LOGD(X, Y)  __android_log_print(ANDROID_LOG_DEBUG, TAG, X, Y)
#define LOGE(X, Y)  __android_log_print(ANDROID_LOG_ERROR, TAG, X, Y)
#define LOGW(X, Y)  __android_log_print(ANDROID_LOG_WARN, TAG, X, Y)
#define LOGI(X, Y)  __android_log_print(ANDROID_LOG_INFO, TAG, X, Y)
#include <pthread.h>

static JavaVM* javaVM = NULL;

static pthread_key_t jni_thread_env_key = 0;

//these two are used by modumles - define them when using static module build
#ifdef GPAC_STATIC_MODULES

#ifdef __cplusplus
extern "C" {
#endif

JavaVM* GetJavaVM()
{
	return javaVM;
}

JNIEnv* GetEnv()
{
	JNIEnv* env = 0;
	if (javaVM) javaVM->GetEnv((void**)(&env), JNI_VERSION_1_2);
	return env;
}

#ifdef __cplusplus
}
#endif

#endif


/**
 * This method is called when a pthread is destroyed, so we can delete the JNI env
 */
static void jni_destroy_env_func(void * env) {
	LOGI("Destroying a thread with attached data, env=%p.\n", env);
	JavaEnvTh * jniEnv = (JavaEnvTh *) env;
	if (jniEnv) {
		/*jniEnv->env->DeleteLocalRef(&jniEnv->cbk_displayMessage);
		jniEnv->env->DeleteLocalRef(&jniEnv->cbk_onProgress);
		jniEnv->env->DeleteLocalRef(&jniEnv->cbk_showKeyboard);
		jniEnv->env->DeleteLocalRef(&jniEnv->cbk_setCaption);
		jniEnv->env->DeleteLocalRef(&jniEnv->cbk_onLog);*/
		memset(jniEnv, 0, sizeof(JavaEnvTh));
		gf_free(jniEnv);
	}
	pthread_setspecific(jni_thread_env_key, NULL);
	if (javaVM)
		javaVM->DetachCurrentThread();
}

static int jniRegisterNativeMethods(JNIEnv* env, const char* className,
                                    const JNINativeMethod* gMethods, int numMethods)
{
	int res;
	jclass clazz;
	clazz = env->FindClass(className);
	if (clazz == NULL) {
		LOGE("Native registration unable to find class '%s'\n", className);
		return -1;
	}
	LOGI("Registering %d methods...\n", numMethods );
	res = env->RegisterNatives(clazz, gMethods, numMethods);
	if (res < 0) {
		LOGE("RegisterNatives failed for '%s'\n", className);
		return res;
	}
	return 0;
}

static JNINativeMethod sMethods[] = {
	/* name, signature, funcPtr */

	{	"createInstance",
		"(Lcom/gpac/Osmo4/GpacCallback;IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J",
		(void*)&Java_com_gpac_Osmo4_GPACInstance_createInstance
	},
	{	"gpacdisconnect",
		"()V",
		(void*)Java_com_gpac_Osmo4_GPACInstance_gpacdisconnect
	},
	{	"gpacrender",
		"()V",
		(void*)Java_com_gpac_Osmo4_GPACInstance_gpacrender
	},
	{	"gpacresize",
		"(II)V",
		(void*)Java_com_gpac_Osmo4_GPACInstance_gpacresize
	},
	{	"gpacfree",
		"()V",
		(void*)Java_com_gpac_Osmo4_GPACInstance_gpacfree
	},
	{	"gpaceventkeypress",
		"(IIIII)V",
		(void*)Java_com_gpac_Osmo4_GPACInstance_gpaceventkeypress
	},
	{	"gpaceventmousedown",
		"(FF)V",
		(void*)Java_com_gpac_Osmo4_GPACInstance_gpaceventmousedown
	},
	{	"gpaceventmouseup",
		"(FF)V",
		(void*)Java_com_gpac_Osmo4_GPACInstance_gpaceventmouseup
	},
	{	"gpaceventmousemove",
		"(FF)V",
		(void*)Java_com_gpac_Osmo4_GPACInstance_gpaceventmousemove
	},
	{	"setGpacPreference",
		"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
		(void*)Java_com_gpac_Osmo4_GPACInstance_setGpacPreference
	},
	{	"setGpacLogs",
		"(Ljava/lang/String;)V",
		(void*)Java_com_gpac_Osmo4_GPACInstance_setGpacLogs
	},
	NULL
};


jint JNI_OnUnLoad(JavaVM* vm, void* reserved) {
	LOGI("Deleting library, vm=%p...\n", vm);
	if (pthread_key_delete(jni_thread_env_key)) {
		LOGW("Failed to delete key jni_thread_env_key jni_thread_env_key=%p\n", jni_thread_env_key);
	}
	javaVM = NULL;
	jni_thread_env_key = (int) NULL;
}

#define NUM_JNI_VERSIONS 4
//---------------------------------------------------------------------------------------------------
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
	int res;
	int jniV;
	int allowedVersions[NUM_JNI_VERSIONS];
	const char * className = "com/gpac/Osmo4/GPACInstance";
	allowedVersions[0] = JNI_VERSION_1_6;
	allowedVersions[1] = JNI_VERSION_1_4;
	allowedVersions[2] = JNI_VERSION_1_2;
	allowedVersions[3] = JNI_VERSION_1_1;
	JNIEnv * env;
	if (!vm)
		return -1;
	for (int i = 0 ; i < NUM_JNI_VERSIONS; i++) {
		jniV = allowedVersions[i];
		if (vm->GetEnv((void**)(&env), jniV) == JNI_OK) {
			LOGI("Selected JNI VERSION[%d]\n", i);
			break;
		} else {
			if (i == NUM_JNI_VERSIONS - 1) {
				LOGW("Failed to find any supported JNI VERSION of the %d proposed.", NUM_JNI_VERSIONS);
				return -1;
			}
		}

	}
	javaVM = vm;
	LOGI("Registering %s natives\n", className);
	res = jniRegisterNativeMethods(env, className, sMethods, (sizeof(sMethods) - 1)/sizeof(JNINativeMethod));
	if (res < 0) {
		LOGE("Failed to register native methods, result was = %d, try to continue anyway...\n", res);
		/*return -1;*/
	}
	LOGI("Registering natives DONE, now registering pthread_keys with destructor=%p\n", &jni_destroy_env_func);
	int ret = pthread_key_create(&jni_thread_env_key, &jni_destroy_env_func);
	if (ret) {
		LOGE("Failed to register jni_thread_env_key jni_thread_env_key=%p\n", jni_thread_env_key);
	}
	return jniV;
}

//---------------------------------------------------------------------------------------------------
//CNativeWrapper
//-------------------------------

CNativeWrapper::CNativeWrapper() {
	do_log = 1;
	m_term = NULL;
	m_mx = NULL;
	mainJavaEnv = NULL;
#ifndef GPAC_GUI_ONLY
	memset(&m_user, 0, sizeof(GF_User));
	memset(&m_rti, 0, sizeof(GF_SystemRTInfo));
#endif
}
//-------------------------------
CNativeWrapper::~CNativeWrapper() {
	debug_log("~CNativeWrapper()");
	JavaEnvTh * env = getEnv();
	if (env && env->cbk_obj)
		env->env->DeleteGlobalRef(env->cbk_obj);
	Shutdown();
	debug_log("~CNativeWrapper() : DONE\n");
}
//-------------------------------
void CNativeWrapper::debug_log(const char* msg) {
	LOGV("%s", msg);
}
//-------------------------------
void CNativeWrapper::Shutdown()
{
	debug_log("shutdown");
	if (m_term)
		gf_term_disconnect(m_term);
	if (m_mx)
		gf_mx_del(m_mx);
	m_mx = NULL;
#ifndef GPAC_GUI_ONLY
	if (m_term) {
		GF_Terminal *t = m_term;
		m_term = NULL;
		gf_term_del(t);
	}
	if (m_user.config) {
		gf_cfg_del(m_user.config);
		m_user.config = NULL;
	}
	if (m_user.modules) {
		gf_modules_del(m_user.modules);
		m_user.modules = NULL;
	}
#endif
	m_term = NULL;
	gf_sys_close();
	debug_log("shutdown end");
}

void CNativeWrapper::setJavaEnv(JavaEnvTh * envToSet, JNIEnv *env, jobject callback) {
	assert( envToSet );
	jclass localRef = env->GetObjectClass(callback);
	envToSet->env = env;
	envToSet->javaThreadId = gf_th_id();
	envToSet->cbk_obj = callback;
	envToSet->cbk_displayMessage =
	    env->GetMethodID(localRef, "displayMessage", "(Ljava/lang/String;Ljava/lang/String;I)V");
	envToSet->cbk_onProgress =
	    env->GetMethodID(localRef, "onProgress", "(Ljava/lang/String;II)V");
	envToSet->cbk_onLog =
	    env->GetMethodID(localRef, "onLog", "(IILjava/lang/String;)V");
	envToSet->cbk_setCaption =
	    env->GetMethodID(localRef, "setCaption", "(Ljava/lang/String;)V");
	envToSet->cbk_showKeyboard =
	    env->GetMethodID(localRef, "showKeyboard", "(Z)V");
	envToSet->cbk_setLogFile =
	    env->GetMethodID(localRef, "setLogFile", "(Ljava/lang/String;)V");
	env->DeleteLocalRef(localRef);
}

static u32 beforeThreadExits(void * param) {
	/* Ivica - I think there is no need for this because the detach is done in jni_destroy_env_func()
	/*LOGI("Before Thread exist, detach the JavaVM from Thread for thread %p...\n", gf_th_current());
	if (javaVM)
	javaVM->DetachCurrentThread();
	*/
}

JavaEnvTh * CNativeWrapper::getEnv() {
	JNIEnv *env;
	JavaEnvTh * javaEnv;
	if (!javaVM) {
		debug_log("************* No JVM Found ************");
		return NULL;
	}
	javaEnv = (JavaEnvTh*) pthread_getspecific( jni_thread_env_key );
	if (javaEnv)
		return javaEnv;
	javaEnv = (JavaEnvTh *) gf_malloc(sizeof(JavaEnvTh));
	if (!javaEnv)
		return NULL;
	memset(javaEnv, 0, sizeof(JavaEnvTh));
	javaVM->AttachCurrentThread(&env, NULL);
	if (!env) {
		LOGE("Attaching to thread did failed for thread id=%d", gf_th_id());
		gf_free(javaEnv);
		return NULL;
	}
	LOGI("Rebuilding methods for thread %d", gf_th_id());
	setJavaEnv(javaEnv, env, mainJavaEnv->cbk_obj);
	if (pthread_setspecific(jni_thread_env_key, javaEnv)) {
		LOGE("Failed to set specific thread data to jni_thread_env_key for thread=%d. No ENV available !", gf_th_id());
		gf_free(javaEnv);
		return NULL;
	}
	GF_Thread * t;
	LOGI("Getting current Thread %d...", gf_th_id());
	t = gf_th_current();
	LOGI("Getting current Thread DONE = %p, now registering before exit...", t);

	if (GF_OK != gf_register_before_exit_function(gf_th_current(), &beforeThreadExits)) {
		LOGE("Failed to register exit function for thread %p, no javaEnv for current thread.", gf_th_current());
		//javaVM->DetachCurrentThread();
		gf_free(javaEnv);
		javaEnv = NULL;
	}
	LOGI("Registering DONE for %d", gf_th_id());
	return javaEnv;
}


//-------------------------------
int CNativeWrapper::MessageBox(const char* msg, const char* title, GF_Err status) {
	LOGV("MessageBox start %s", msg);
	JavaEnvTh * env = getEnv();
	if (!env || !env->cbk_displayMessage)
		return 0;
	env->env->PushLocalFrame(2);
	jstring tit = env->env->NewStringUTF(title?title:"null");
	jstring mes = env->env->NewStringUTF(msg?msg:"null");
	env->env->CallVoidMethod(env->cbk_obj, env->cbk_displayMessage, mes, tit, status);
	env->env->PopLocalFrame(NULL);
	LOGV("MessageBox done %s", msg);
	return 1;
}
//-------------------------------
void CNativeWrapper::DisplayRTI() {
#ifndef GPAC_GUI_ONLY
	// display some system informations ?
#endif
}
//-------------------------------
int CNativeWrapper::Quit(int code) {

	Shutdown();
	// send shutdown request to java
	return code;
}

#include <stdio.h>

void CNativeWrapper::on_fm_request(void *cbk, u32 type, u32 param, int *value) {
	CNativeWrapper * self = (CNativeWrapper *) cbk;
	JavaEnvTh *envth = self->getEnv();

	envth->env->PushLocalFrame(1);
	switch(type) {
	case 0: // Get PI
	case 1: // Get RT
	case 2: // Get PS
		jint jvalue;
		jvalue = envth->env->CallIntMethod(envth->cbk_obj, envth->cbk_onFmRequest, type, param);
		*value = (int)jvalue;
		break;
	case 3: // Set Freq
	case 4: // Set Volume
		envth->env->CallVoidMethod(envth->cbk_obj, envth->cbk_onFmRequest, type, param);
		break;
	}
	envth->env->PopLocalFrame(NULL);
}


//-------------------------------
void CNativeWrapper::on_gpac_log(void *cbk, u32 ll, u32 lm, const char *fmt, va_list list) {
	char szMsg[4096];
	const char * tag;
	char unknTag[32];
	int level = (ll == GF_LOG_ERROR) ? ANDROID_LOG_ERROR : ANDROID_LOG_DEBUG;
	vsnprintf(szMsg, 4096, fmt, list);
	CNativeWrapper * self = (CNativeWrapper *) cbk;
	if (!self)
		goto displayInAndroidlogs;

	{
		JavaEnvTh *env = self->getEnv();
		jstring msg;

		if (!env || !env->cbk_onLog)
			goto displayInAndroidlogs;

		env->env->PushLocalFrame(1);
		msg = env->env->NewStringUTF(szMsg);
		env->env->CallVoidMethod(env->cbk_obj, env->cbk_onLog, level, lm, msg);
		env->env->PopLocalFrame(NULL);
		return;
	}
displayInAndroidlogs:
	{
		/* When no callback is properly set, we use direct logging */
		switch( lm) {
		case GF_LOG_CORE:
			tag="GF_LOG_CORE";
			break;
		case GF_LOG_CODING:
			tag="GF_LOG_CODING";
			break;
		case GF_LOG_CONTAINER:
			tag="GF_LOG_CONTAINER";
			break;
		case GF_LOG_NETWORK:
			tag="GF_LOG_NETWORK";
			break;
		case GF_LOG_RTP:
			tag="GF_LOG_RTP";
			break;
		case GF_LOG_AUTHOR:
			tag="GF_LOG_AUTHOR";
			break;
		case GF_LOG_SYNC:
			tag="GF_LOG_SYNC";
			break;
		case GF_LOG_CODEC:
			tag="GF_LOG_CODEC";
			break;
		case GF_LOG_PARSER:
			tag="GF_LOG_PARSER";
			break;
		case GF_LOG_MEDIA:
			tag="GF_LOG_MEDIA";
			break;
		case GF_LOG_SCENE:
			tag="GF_LOG_SCENE";
			break;
		case GF_LOG_SCRIPT:
			tag="GF_LOG_SCRIPT";
			break;
		case GF_LOG_INTERACT:
			tag="GF_LOG_INTERACT";
			break;
		case GF_LOG_COMPOSE:
			tag="GF_LOG_COMPOSE";
			break;
		case GF_LOG_CACHE:
			tag="GF_LOG_CACHE";
			break;
		case GF_LOG_MMIO:
			tag="GF_LOG_MMIO";
			break;
		case GF_LOG_RTI:
			tag="GF_LOG_RTI";
			break;
		case GF_LOG_SMIL:
			tag="GF_LOG_SMIL";
			break;
		case GF_LOG_MEMORY:
			tag="GF_LOG_MEMORY";
			break;
		case GF_LOG_AUDIO:
			tag="GF_LOG_AUDIO";
			break;
		case GF_LOG_MODULE:
			tag="GF_LOG_MODULE";
			break;
		case GF_LOG_MUTEX:
			tag="GF_LOG_MUTEX";
			break;
		case GF_LOG_CONDITION:
			tag="GF_LOG_CONDITION";
			break;
		case GF_LOG_DASH:
			tag="GF_LOG_DASH";
			break;
		case GF_LOG_CONSOLE:
			tag="GF_LOG_CONSOLE";
			break;
		case GF_LOG_APP:
			tag="GF_LOG_APP";
			break;
		case GF_LOG_SCHEDULER:
			tag="GF_LOG_SCHEDULER";
			break;
		default:
			snprintf(unknTag, 32, "GPAC_UNKNOWN[%d]", lm);
			tag = unknTag;
		}
		__android_log_print(level, tag, "%s\n", szMsg);
	}
}
//-------------------------------
Bool CNativeWrapper::GPAC_EventProc(void *cbk, GF_Event *evt) {
	if (cbk)
	{
		CNativeWrapper* ptr = (CNativeWrapper*)cbk;
		char msg[4096];
		msg[0] = 0;
		LOGD("GPAC_EventProc() Message=%d", evt->type);
		switch (evt->type) {
		case GF_EVENT_CLICK:
		case GF_EVENT_MOUSEUP:
		case GF_EVENT_MOUSEDOWN:
		case GF_EVENT_MOUSEOVER:
		case GF_EVENT_MOUSEOUT:
		case GF_EVENT_MOUSEMOVE:
		case GF_EVENT_MOUSEWHEEL:
		case GF_EVENT_KEYUP:
		case GF_EVENT_KEYDOWN:
		case GF_EVENT_LONGKEYPRESS:
		case GF_EVENT_TEXTINPUT:
			/* We ignore all these events */
			break;
		case GF_EVENT_MEDIA_SETUP_BEGIN:
		case GF_EVENT_MEDIA_SETUP_DONE:
		case GF_EVENT_MEDIA_LOAD_START:
		case GF_EVENT_MEDIA_PLAYING:
		case GF_EVENT_MEDIA_WAITING:
		case GF_EVENT_MEDIA_PROGRESS:
		case GF_EVENT_MEDIA_LOAD_DONE:
		case GF_EVENT_ABORT:
		case GF_EVENT_ERROR:
			LOGD("GPAC_EventProc() Media Event detected = [index=%d]", evt->type - GF_EVENT_MEDIA_SETUP_BEGIN);
			break;
		case GF_EVENT_MESSAGE:
		{
			ptr->debug_log("GPAC_EventProc start");
			if ( evt->message.message )
			{
				strcat(msg, evt->message.message);
				strcat(msg, ": ");
			}
			strcat(msg, gf_error_to_string(evt->message.error));

			ptr->debug_log(msg);
			//ptr->MessageBox(msg, evt->message.service ? evt->message.service : "GF_EVENT_MESSAGE", evt->message.error);
			ptr->debug_log("GPAC_EventProc end");
		};
		break;
		case GF_EVENT_CONNECT:
			if (evt->connect.is_connected)
				ptr->MessageBox("Connected", "Connected to scene", GF_OK);
			else
				ptr->MessageBox("Disconnected", "Disconnected from scene.", GF_OK);
			break;
		case GF_EVENT_PROGRESS:
		{
			const char * szTitle;;
			if (evt->progress.progress_type==0)
				szTitle = "Buffering";
			else if (evt->progress.progress_type==1)
				szTitle = "Downloading...";
			else if (evt->progress.progress_type==2)
				szTitle = "Import ";
			else
				szTitle = "Unknown Progress Event";
			ptr->Osmo4_progress_cbk(ptr, szTitle, evt->progress.done, evt->progress.total);
			gf_set_progress(szTitle, evt->progress.done, evt->progress.total);
		}
		break;
		case GF_EVENT_TEXT_EDITING_START:
		case GF_EVENT_TEXT_EDITING_END:
		{
			JavaEnvTh * env = ptr->getEnv();
			if (!env || !env->cbk_showKeyboard)
				return GF_FALSE;
			LOGI("Needs to display/hide the Virtual Keyboard (%d)", evt->type);
			env->env->CallVoidMethod(env->cbk_obj, env->cbk_showKeyboard, GF_EVENT_TEXT_EDITING_START == evt->type);
			LOGV("Done showing virtual keyboard (%d)", evt->type);
		}
		break;
		case GF_EVENT_EOS:
			LOGI("EOS Reached (%d)", evt->type);
			break;
		case GF_EVENT_DISCONNECT:
			/* FIXME : not sure about this behaviour */
			if (ptr)
				ptr->disconnect();
			break;
		case GF_EVENT_NAVIGATE:
			ptr->navigate( evt);
			break;
		default:
			LOGI("Unknown Message %d", evt->type);
		}
	}
	return GF_FALSE;
}

void CNativeWrapper::navigate( GF_Event* evt)
{
	if (gf_term_is_supported_url(m_term, evt->navigate.to_url, GF_TRUE, GF_TRUE))
	{
		gf_term_navigate_to(m_term, evt->navigate.to_url);
	}
}

void CNativeWrapper::progress_cbk(const char *title, u64 done, u64 total) {
	JavaEnvTh *env = getEnv();
	if (!env || !env->cbk_onProgress)
		return;
	//debug_log("Osmo4_progress_cbk start");
	env->env->PushLocalFrame(1);
	jstring js = env->env->NewStringUTF(title);
	env->env->CallVoidMethod(env->cbk_obj, env->cbk_onProgress, js, done, total);
	env->env->PopLocalFrame(NULL);
	//debug_log("Osmo4_progress_cbk end");
}


//-------------------------------
void CNativeWrapper::Osmo4_progress_cbk(const void *usr, const char *title, u64 done, u64 total) {
	if (!usr)
		return;
	CNativeWrapper * self = (CNativeWrapper *) usr;
	self->progress_cbk(title, done, total);
}
//-------------------------------
void CNativeWrapper::SetupLogs() {
	const char *opt;
	debug_log("SetupLogs()");

	gf_mx_p(m_mx);
	gf_log_set_tools_levels( gf_cfg_get_key(m_user.config, "General", "Logs") );
	gf_log_set_callback(this, on_gpac_log);
	opt = gf_cfg_get_key(m_user.config, "General", "LogFile");
	if (opt) {
		JavaEnvTh *env = getEnv();
		if (env && env->cbk_setLogFile) {
			env->env->PushLocalFrame(1);
			jstring js = env->env->NewStringUTF(opt);
			env->env->CallVoidMethod(env->cbk_obj, env->cbk_setLogFile, js);
			env->env->PopLocalFrame(NULL);
		}
	}
	gf_mx_v(m_mx);

	GF_LOG(GF_LOG_INFO, GF_LOG_CORE, ("Osmo4 logs initialized\n"));
	/* Test for JNI invocations, should work properly
	int k;
	for (k = 0 ; k < 512; k++){
		GF_LOG(GF_LOG_INFO, GF_LOG_CORE, ("Message %d\n", k));
	}*/
}
//-------------------------------
// dir should end with /
int CNativeWrapper::init(JNIEnv * env, void * bitmap, jobject * callback, int width, int height, const char * cfg_dir, const char * modules_dir, const char * cache_dir, const char * font_dir, const char * gui_dir, const char * urlToLoad) {
	LOGI("Initializing GPAC with URL=%s...", urlToLoad);
	strcpy(m_modules_dir, modules_dir);
	strcpy(m_cache_dir, cache_dir);
	strcpy(m_font_dir, font_dir);
	if (cfg_dir)
		strcpy(m_cfg_dir, cfg_dir);

	char m_cfg_filename[GF_MAX_PATH];
	if (m_cfg_dir) {
		LOGI("GPAC.cfg found in %s, force using it.\n", m_cfg_dir);
		strcpy(m_cfg_filename, m_cfg_dir);
		strcat(m_cfg_filename, "GPAC.cfg");
	}

	int m_Width = width;
	int m_Height = height;

	int first_launch = 0;
	const char *opt;

	m_window = env;
	m_session = bitmap;
	if (!mainJavaEnv)
		mainJavaEnv = (JavaEnvTh *) gf_malloc(sizeof(JavaEnvTh));
	memset(mainJavaEnv, 0, sizeof(JavaEnvTh));
	setJavaEnv(mainJavaEnv, env, env->NewGlobalRef(*callback));
	if (pthread_setspecific( jni_thread_env_key, mainJavaEnv)) {
		LOGE("Failed to set specific thread data to jni_thread_env_key=%p for main thread !", jni_thread_env_key);
	}

	m_mx = gf_mx_new("Osmo4");

	//load config file
	LOGI("Loading User Config %s...", "GPAC.cfg");
	m_user.config = gf_cfg_init(m_cfg_dir ? m_cfg_filename : NULL, NULL);
	gf_set_progress_callback(this, Osmo4_progress_cbk);

	opt = gf_cfg_get_key(m_user.config, "General", "ModulesDirectory");
	LOGI("loading modules in directory %s...", opt);
	m_user.modules = gf_modules_new(opt, m_user.config);
	if (!m_user.modules || !gf_modules_get_count(m_user.modules)) {
		LOGE("No modules found in directory %s !", opt);
		if (m_user.modules)
			gf_modules_del(m_user.modules);
		gf_cfg_del(m_user.config);
		m_user.config = NULL;
		return Quit(KErrGeneral);
	}

	/*we don't thread the visual compositor to be able to minimize the app and still have audio running*/
	m_user.init_flags = GF_TERM_NO_COMPOSITOR_THREAD;
	m_user.opaque = this;

	m_user.os_window_handler = m_window;
	m_user.os_display = m_session;
	m_user.EventProc = GPAC_EventProc;
	if (!javaVM) {
		LOGE("NO JAVA VM FOUND, m_user=%p !!!!\n", &m_user);
		return Quit(KErrGeneral);
	}

	LOGD("Loading GPAC terminal, m_user=%p...", &m_user);
	gf_sys_init(GF_FALSE);
	gf_fm_request_set_callback(this, on_fm_request);
	SetupLogs();
	m_term = gf_term_new(&m_user);
	if (!m_term) {
		LOGE("Cannot load GPAC Terminal with m_user=%p", m_user);
		MessageBox("Cannot load GPAC terminal", "Fatal Error", GF_SERVICE_ERROR);
		gf_modules_del(m_user.modules);
		m_user.modules = NULL;
		gf_cfg_del(m_user.config);
		m_user.config = NULL;
		return Quit(KErrGeneral);
	}

	/*force fullscreen*/
	gf_term_set_option(m_term, GF_OPT_FULLSCREEN, 1);

	//setAudioEnvironment(javaVM);

	LOGD("Setting term size m_user=%p...", &m_user);
	gf_term_set_size(m_term, m_Width, m_Height);

	opt = gf_cfg_get_key(m_user.config, "General", "StartupFile");
	LOGD("File loaded at startup=%s.", opt);

	if (!urlToLoad)
		urlToLoad = opt;
	if (urlToLoad) {
		LOGI("Connecting to %s...", urlToLoad);
		gf_term_connect(m_term, urlToLoad);
	}
	debug_log("init end");
	LOGD("Saving config file ...\n", m_cfg_filename);
	gf_cfg_save(m_user.config);
	LOGI("Initialization complete, config file saved.\n", m_cfg_filename);

	return 0;
}
//-------------------------------
int CNativeWrapper::connect(const char *url)
{
	const char *str;
	char the_url[256];

	if (m_term)
	{
		debug_log("Starting to connect ...");
		str = gf_cfg_get_key(m_user.config, "General", "StartupFile");
		if (str)
		{
			gf_cfg_set_key(m_user.config, "Temp", "GUIStartupFile", url);
			gf_term_connect(m_term, str);
		}
		if( url )
		{
			gf_term_connect_from_time(m_term, url, 0, GF_FALSE);
		}
	}
	debug_log("connected ...");
}

void CNativeWrapper::setGpacPreference( const char * category, const char * name, const char * value)
{
	if (m_user.config) {
		gf_cfg_set_key(m_user.config, category, name, value);
		gf_cfg_save(m_user.config);
	}
}

//-----------------------------------------------------
void CNativeWrapper::disconnect() {
	if (m_term) {
		debug_log("disconnecting");
		gf_term_disconnect(m_term);
		debug_log("disconnected ...");
	}
}
//-----------------------------------------------------
void CNativeWrapper::step(void * env, void * bitmap) {
	m_window = env;
	m_session = bitmap;
	//debug_log("Step ...");
	if (!m_term) {
		debug_log("step(): No m_term found.");
		return;
	} else if (!m_term->compositor)
		debug_log("step(): No compositor found.");
	else if (!m_term->compositor->video_out)
		debug_log("step(): No video_out found");
	else if (!m_term->compositor->video_out->Setup)
		debug_log("step(): No video_out->Setup found");
	else {
		m_term->compositor->frame_draw_type = GF_SC_DRAW_FRAME;
		gf_term_process_step(m_term);
	}
}

//-----------------------------------------------------
void CNativeWrapper::setAudioEnvironment(JavaVM* javaVM) {
	if (!m_term) {
		debug_log("setAudioEnvironment(): no m_term found.");
		return;
	}
	debug_log("setAudioEnvironment start");
	m_term->compositor->audio_renderer->audio_out->Setup(m_term->compositor->audio_renderer->audio_out, javaVM, 0, 0);
	debug_log("setAudioEnvironment end");
}
//-----------------------------------------------------
void CNativeWrapper::resize(int w, int h) {
	if (!m_term)
		return;
	debug_log("resize start");
	gf_term_set_size(m_term, w, h);
	debug_log("resize end");
}

//-----------------------------------------------------
void CNativeWrapper::onMouseDown(float x, float y) {
	if (!m_term)
		return;
	debug_log("onMouseDown start");
	//char msg[100];
	//sprintf(msg, "onMousedown x=%f, y=%f", x, y );
	//debug_log(msg);

	GF_Event evt;
	evt.type = GF_EVENT_MOUSEDOWN;
	evt.mouse.button = GF_MOUSE_LEFT;
	evt.mouse.x = x;
	evt.mouse.y = y;

	int ret = gf_term_user_event(m_term, &evt);
	debug_log("onMouseDown end");
}
//-----------------------------------------------------
void CNativeWrapper::onMouseUp(float x, float y) {
	if (!m_term)
		return;
	debug_log("onMouseUp start");
	//char msg[100];
	//sprintf(msg, "onMouseUp x=%f, y=%f", x, y );
	//debug_log(msg);

	GF_Event evt;
	evt.type = GF_EVENT_MOUSEUP;
	evt.mouse.button = GF_MOUSE_LEFT;
	evt.mouse.x = x;
	evt.mouse.y = y;

	int ret = gf_term_user_event(m_term, &evt);
	debug_log("onMouseUp end");
}
//-----------------------------------------------------
void CNativeWrapper::onMouseMove(float x, float y) {
	if (!m_term)
		return;
	GF_Event evt;
	evt.type = GF_EVENT_MOUSEMOVE;
	evt.mouse.button = GF_MOUSE_LEFT;
	evt.mouse.x = x;
	evt.mouse.y = y;

	int ret = gf_term_user_event(m_term, &evt);
}
//-----------------------------------------------------
void CNativeWrapper::onKeyPress(int keycode, int rawkeycode, int up, int flag, int unicode) {
	if (!m_term)
		return;
	debug_log("onKeyPress start");
	GF_Event evt;
	memset(&evt, 0, sizeof(GF_Event));
	if (up == 0) evt.type = GF_EVENT_KEYUP;
	else evt.type = GF_EVENT_KEYDOWN;

	evt.key.flags = 0;
	evt.key.hw_code = rawkeycode;

	translate_key(keycode, &evt.key);
	//evt.key.key_code = GF_KEY_A;
	int ret = gf_term_user_event(m_term, &evt);

	if (evt.type == GF_EVENT_KEYUP && unicode) {
		memset(&evt, 0, sizeof(GF_Event));
		evt.type = GF_EVENT_TEXTINPUT;
		evt.character.unicode_char = unicode;
		ret = gf_term_user_event(m_term, &evt);
	}
}
//-----------------------------------------------------
void CNativeWrapper::translate_key(ANDROID_KEYCODE keycode, GF_EventKey *evt) {
	evt->flags = 0;
	switch (keycode) {
	case ANDROID_KEYCODE_BACK:
		evt->key_code = GF_KEY_BACKSPACE;
		evt->hw_code = 8;
		break;
	case ANDROID_KEYCODE_TAB:
		evt->key_code = GF_KEY_TAB;
		evt->hw_code = 9;
		break;
	case ANDROID_KEYCODE_CLEAR:
		evt->key_code = GF_KEY_CLEAR;
		break;
	case ANDROID_KEYCODE_ENTER:
		evt->key_code = GF_KEY_ENTER;
		evt->hw_code = 13;
		break;
	case ANDROID_KEYCODE_SHIFT_LEFT:
		evt->key_code = GF_KEY_SHIFT;
		break;
	case ANDROID_KEYCODE_SHIFT_RIGHT:
		evt->key_code = GF_KEY_SHIFT;
		break;
	case ANDROID_KEYCODE_ALT_LEFT:
		evt->key_code = GF_KEY_ALT;
		break;
	case ANDROID_KEYCODE_ALT_RIGHT:
		evt->key_code = GF_KEY_ALT;
		break;
	case ANDROID_KEYCODE_SPACE:
		evt->key_code = GF_KEY_SPACE;
		evt->hw_code = 32;
		break;
	case ANDROID_KEYCODE_HOME:
		evt->key_code = GF_KEY_HOME;
		break;
	case ANDROID_KEYCODE_DPAD_LEFT:
		evt->key_code = GF_KEY_LEFT;
		break;
	case ANDROID_KEYCODE_DPAD_UP:
		evt->key_code = GF_KEY_UP;
		break;
	case ANDROID_KEYCODE_DPAD_RIGHT:
		evt->key_code = GF_KEY_RIGHT;
		break;
	case ANDROID_KEYCODE_DPAD_DOWN:
		evt->key_code = GF_KEY_DOWN;
		break;
	case ANDROID_KEYCODE_DEL:
		evt->key_code = GF_KEY_BACKSPACE;
		evt->hw_code = 8;
		break;
	case ANDROID_KEYCODE_0:
		evt->key_code = GF_KEY_0;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case ANDROID_KEYCODE_1:
		evt->key_code = GF_KEY_1;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case ANDROID_KEYCODE_2:
		evt->key_code = GF_KEY_2;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case ANDROID_KEYCODE_3:
		evt->key_code = GF_KEY_3;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case ANDROID_KEYCODE_4:
		evt->key_code = GF_KEY_4;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case ANDROID_KEYCODE_5:
		evt->key_code = GF_KEY_5;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case ANDROID_KEYCODE_6:
		evt->key_code = GF_KEY_6;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case ANDROID_KEYCODE_7:
		evt->key_code = GF_KEY_7;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case ANDROID_KEYCODE_8:
		evt->key_code = GF_KEY_8;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case ANDROID_KEYCODE_9:
		evt->key_code = GF_KEY_9;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	/*thru VK_9 are the same as ASCII '0' thru '9' (0x30 - 0x39) */
	/* VK_A thru VK_Z are the same as ASCII 'A' thru 'Z' (0x41 - 0x5A) */
	default:
		if ((keycode>=ANDROID_KEYCODE_A) && (keycode<=ANDROID_KEYCODE_Z)) {
			evt->key_code = GF_KEY_A + keycode - ANDROID_KEYCODE_A;
		} else {
			evt->key_code = GF_KEY_UNIDENTIFIED;
		}
		break;
	}
}

//-----------------------------------------------------
void CNativeWrapper::setGpacLogs(const char *tools_at_level)
{
	gf_log_set_tools_levels(tools_at_level);
}

//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
