/*************************************************************************/
/*  godot_android.cpp                                                    */
/*************************************************************************/
/*                       This file is part of:                           */
/*                           GODOT ENGINE                                */
/*                    http://www.godotengine.org                         */
/*************************************************************************/
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                 */
/*                                                                       */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the       */
/* "Software"), to deal in the Software without restriction, including   */
/* without limitation the rights to use, copy, modify, merge, publish,   */
/* distribute, sublicense, and/or sell copies of the Software, and to    */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions:                                             */
/*                                                                       */
/* The above copyright notice and this permission notice shall be        */
/* included in all copies or substantial portions of the Software.       */
/*                                                                       */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
/*************************************************************************/
#ifdef ANDROID_NATIVE_ACTIVITY

#include <jni.h>
#include <errno.h>

#include <EGL/egl.h>
#include <GLES2/gl2.h>

#include <android/sensor.h>
#include <android/window.h>
#include <android/log.h>
#include <android_native_app_glue.h>
#include "file_access_android.h"
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "os_android.h"
#include "globals.h"
#include "main/main.h"
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "godot", __VA_ARGS__))
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "godot", __VA_ARGS__))


extern "C" {
    JNIEXPORT void JNICALL Java_com_android_godot_Godot_registerSingleton(JNIEnv * env, jobject obj, jstring name,jobject p_object);
    JNIEXPORT void JNICALL Java_com_android_godot_Godot_registerMethod(JNIEnv * env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args);
    JNIEXPORT jstring JNICALL Java_com_android_godot_Godot_getGlobal(JNIEnv * env, jobject obj, jstring path);
};

class JNISingleton : public Object {

	OBJ_TYPE( JNISingleton, Object );


	struct MethodData {


		jmethodID method;
		Variant::Type ret_type;
		Vector<Variant::Type> argtypes;
	};

	jobject instance;
	Map<StringName,MethodData> method_map;
	JNIEnv *env;

public:

	void update_env(JNIEnv *p_env) { env=p_env; }

	virtual Variant call(const StringName& p_method,const Variant** p_args,int p_argcount,Variant::CallError &r_error) {

		print_line("attempt to call "+String(p_method));

		r_error.error=Variant::CallError::CALL_OK;

		Map<StringName,MethodData >::Element *E=method_map.find(p_method);
		if (!E) {

			print_line("no exists");
			r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD;
			return Variant();
		}


		int ac = E->get().argtypes.size();
		if (ac<p_argcount) {

			print_line("fewargs");
			r_error.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
			r_error.argument=ac;
			return Variant();
		}

		if (ac>p_argcount) {

			print_line("manyargs");
			r_error.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
			r_error.argument=ac;
			return Variant();
		}



		for(int i=0;i<p_argcount;i++) {

			if (!Variant::can_convert(p_args[i]->get_type(),E->get().argtypes[i])) {

				r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;
				r_error.argument=i;
				r_error.expected=E->get().argtypes[i];
			}
		}


		jvalue *v=NULL;

		if (p_argcount) {

			v=(jvalue*)alloca( sizeof(jvalue)*p_argcount );
		}

		for(int i=0;i<p_argcount;i++) {


			switch(E->get().argtypes[i]) {

				case Variant::BOOL: {

					v[i].z=*p_args[i];
				} break;
				case Variant::INT: {

					v[i].i=*p_args[i];
				} break;
				case Variant::REAL: {

					v[i].f=*p_args[i];
				} break;
				case Variant::STRING: {

					String s = *p_args[i];
					jstring jStr = env->NewStringUTF(s.utf8().get_data());
					v[i].l=jStr;
				} break;
				case Variant::STRING_ARRAY: {

					DVector<String> sarray = *p_args[i];
					jobjectArray arr = env->NewObjectArray(sarray.size(),env->FindClass("java/lang/String"),env->NewStringUTF(""));

					for(int j=0;j<sarray.size();j++) {

						env->SetObjectArrayElement(arr,j,env->NewStringUTF( sarray[i].utf8().get_data() ));
					}
					v[i].l=arr;

				} break;
				case Variant::INT_ARRAY: {

					DVector<int> array = *p_args[i];
					jintArray arr = env->NewIntArray(array.size());
					DVector<int>::Read r = array.read();
					env->SetIntArrayRegion(arr,0,array.size(),r.ptr());
					v[i].l=arr;

				} break;
				case Variant::REAL_ARRAY: {

					DVector<float> array = *p_args[i];
					jfloatArray arr = env->NewFloatArray(array.size());
					DVector<float>::Read r = array.read();
					env->SetFloatArrayRegion(arr,0,array.size(),r.ptr());
					v[i].l=arr;

				} break;
				default: {

					ERR_FAIL_V(Variant());
				} break;

			}
		}

		print_line("calling method!!");

		Variant ret;

		switch(E->get().ret_type) {

			case Variant::NIL: {


				print_line("call void");
				env->CallVoidMethodA(instance,E->get().method,v);
			} break;
			case Variant::BOOL: {

				ret = env->CallBooleanMethodA(instance,E->get().method,v);
				print_line("call bool");
			} break;
			case Variant::INT: {

				ret = env->CallIntMethodA(instance,E->get().method,v);
				print_line("call int");
			} break;
			case Variant::REAL: {

				ret = env->CallFloatMethodA(instance,E->get().method,v);
			} break;
			case Variant::STRING: {

				jobject o = env->CallObjectMethodA(instance,E->get().method,v);
				String singname = env->GetStringUTFChars((jstring)o, NULL );
			} break;
			case Variant::STRING_ARRAY: {

				jobjectArray arr = (jobjectArray)env->CallObjectMethodA(instance,E->get().method,v);

				int stringCount = env->GetArrayLength(arr);
				DVector<String> sarr;

				for (int i=0; i<stringCount; i++) {
					jstring string = (jstring) env->GetObjectArrayElement(arr, i);
					const char *rawString = env->GetStringUTFChars(string, 0);
					sarr.push_back(String(rawString));
				}

				ret=sarr;

			} break;
			case Variant::INT_ARRAY: {

				jintArray arr = (jintArray)env->CallObjectMethodA(instance,E->get().method,v);

				int fCount = env->GetArrayLength(arr);
				DVector<int> sarr;
				sarr.resize(fCount);

				DVector<int>::Write w = sarr.write();
				env->GetIntArrayRegion(arr,0,fCount,w.ptr());
				w = DVector<int>::Write();
				ret=sarr;
			} break;
			case Variant::REAL_ARRAY: {

				jfloatArray arr = (jfloatArray)env->CallObjectMethodA(instance,E->get().method,v);

				int fCount = env->GetArrayLength(arr);
				DVector<float> sarr;
				sarr.resize(fCount);

				DVector<float>::Write w = sarr.write();
				env->GetFloatArrayRegion(arr,0,fCount,w.ptr());
				w = DVector<float>::Write();
				ret=sarr;
			} break;
			default: {


				print_line("failure..");
				ERR_FAIL_V(Variant());
			} break;
		}

		print_line("success");

		return ret;
	}


	jobject get_instance() const {

		return instance;
	}
	void set_instance(jobject p_instance) {

		instance=p_instance;
	}


	void add_method(const StringName& p_name, jmethodID p_method,const Vector<Variant::Type>& p_args, Variant::Type p_ret_type) {

		MethodData md;
		md.method=p_method;
		md.argtypes=p_args;
		md.ret_type=p_ret_type;
		method_map[p_name]=md;

	}


	JNISingleton() {}

};

//JNIEnv *JNISingleton::env=NULL;

static HashMap<String,JNISingleton*> jni_singletons;


struct engine {
    struct android_app* app;
    OS_Android *os;
    JNIEnv *jni;

    ASensorManager* sensorManager;
    const ASensor* accelerometerSensor;
    ASensorEventQueue* sensorEventQueue;

    bool display_active;
    bool requested_quit;
    int animating;
    EGLDisplay display;
    EGLSurface surface;
    EGLContext context;
    int32_t width;
    int32_t height;

};

/**
 * Initialize an EGL context for the current display.
 */
static int engine_init_display(struct engine* engine,bool p_gl2) {
    // initialize OpenGL ES and EGL

    /*
     * Here specify the attributes of the desired configuration.
     * Below, we select an EGLConfig with at least 8 bits per color
     * component compatible with on-screen windows
     */
    const EGLint gl2_attribs[] = {
	  //  EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
	    EGL_BLUE_SIZE, 4,
	    EGL_GREEN_SIZE, 4,
	    EGL_RED_SIZE, 4,
	    EGL_ALPHA_SIZE, 0,
	    EGL_DEPTH_SIZE,     16,
	    EGL_STENCIL_SIZE,   EGL_DONT_CARE,
	    EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
	    EGL_NONE
    };

    const EGLint gl1_attribs[] = {
	  //  EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
	    EGL_BLUE_SIZE, 4,
	    EGL_GREEN_SIZE, 4,
	    EGL_RED_SIZE, 4,
	    EGL_ALPHA_SIZE, 0,
	    EGL_DEPTH_SIZE,     16,
	    EGL_STENCIL_SIZE,   EGL_DONT_CARE,
	    EGL_NONE
    };

    const EGLint *attribs=p_gl2?gl2_attribs:gl1_attribs;


    EGLint w, h, dummy, format;
    EGLint numConfigs;
    EGLConfig config;
    EGLSurface surface;
    EGLContext context;

    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

    eglInitialize(display, 0, 0);


    /* Here, the application chooses the configuration it desires. In this
     * sample, we have a very simplified selection process, where we pick
     * the first EGLConfig that matches our criteria */

    eglChooseConfig(display, attribs, &config, 1, &numConfigs);

    LOGI("Num configs: %i\n",numConfigs);

    /* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is
     * guaranteed to be accepted by ANativeWindow_setBuffersGeometry().
     * As soon as we picked a EGLConfig, we can safely reconfigure the
     * ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. */
    eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);

    ANativeWindow_setBuffersGeometry(engine->app->window, 0, 0, format);
    //ANativeWindow_setFlags(engine->app->window, 0, 0, format|);

    surface = eglCreateWindowSurface(display, config, engine->app->window, NULL);

    const EGLint context_attribs[] = {
	    EGL_CONTEXT_CLIENT_VERSION,2,
	    EGL_NONE
    };
    context = eglCreateContext(display, config, EGL_NO_CONTEXT, p_gl2?context_attribs:NULL);

    if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {
	LOGW("Unable to eglMakeCurrent");
	return -1;
    }

    eglQuerySurface(display, surface, EGL_WIDTH, &w);
    eglQuerySurface(display, surface, EGL_HEIGHT, &h);
    print_line("INIT VIDEO MODE: "+itos(w)+","+itos(h));

    //engine->os->set_egl_extensions(eglQueryString(display,EGL_EXTENSIONS));
    engine->os->init_video_mode(w,h);


    engine->display = display;
    engine->context = context;
    engine->surface = surface;
    engine->width = w;
    engine->height = h;
    engine->display_active=true;

    //engine->state.angle = 0;

    // Initialize GL state.
    //glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
    glEnable(GL_CULL_FACE);
  //  glShadeModel(GL_SMOOTH);
    glDisable(GL_DEPTH_TEST);
    LOGI("GL Version: %s - %s %s\n", glGetString(GL_VERSION),glGetString(GL_VENDOR), glGetString(GL_RENDERER));

    return 0;
}


static void engine_draw_frame(struct engine* engine) {
    if (engine->display == NULL) {
	// No display.
	return;
    }

    // Just fill the screen with a color.
    //glClearColor(0,1,0,1);
    //glClear(GL_COLOR_BUFFER_BIT);
    if (engine->os && engine->os->main_loop_iterate()==true) {

	    engine->requested_quit=true;
	    return; //should exit instead
    }

    eglSwapBuffers(engine->display, engine->surface);
}


static void engine_term_display(struct engine* engine) {
    if (engine->display != EGL_NO_DISPLAY) {
	eglMakeCurrent(engine->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
	if (engine->context != EGL_NO_CONTEXT) {
	    eglDestroyContext(engine->display, engine->context);
	}
	if (engine->surface != EGL_NO_SURFACE) {
	    eglDestroySurface(engine->display, engine->surface);
	}
	eglTerminate(engine->display);
    }

    engine->animating = 0;
    engine->display = EGL_NO_DISPLAY;
    engine->context = EGL_NO_CONTEXT;
    engine->surface = EGL_NO_SURFACE;
    engine->display_active=false;

}

/**
 * Process the next input event.
 */
static int32_t engine_handle_input(struct android_app* app, AInputEvent* event) {
	struct engine* engine = (struct engine*)app->userData;

	if (!engine->os)
		return 0;

	switch(AInputEvent_getType(event)) {

		case AINPUT_EVENT_TYPE_KEY: {

			int ac = AKeyEvent_getAction(event);
			switch(ac) {

				case AKEY_EVENT_ACTION_DOWN: {

					int32_t code = AKeyEvent_getKeyCode(event);
					if (code==AKEYCODE_BACK) {

						//AInputQueue_finishEvent(AInputQueue* queue, AInputEvent* event, int handled);
						if (engine->os)
							engine->os->main_loop_request_quit();
						return 1;

					}


				} break;
				case AKEY_EVENT_ACTION_UP: {


				} break;
			}


		} break;
		case AINPUT_EVENT_TYPE_MOTION: {


			Vector<OS_Android::TouchPos> touchvec;

			int pc = AMotionEvent_getPointerCount(event);

			touchvec.resize(pc);

			for(int i=0;i<pc;i++) {

				touchvec[i].pos.x=AMotionEvent_getX(event,i);
				touchvec[i].pos.y=AMotionEvent_getY(event,i);
				touchvec[i].id=AMotionEvent_getPointerId(event,i);
			}


			//System.out.printf("gaction: %d\n",event.getAction());
			int pidx=(AMotionEvent_getAction(event)&AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)>>8;
			switch(AMotionEvent_getAction(event)&AMOTION_EVENT_ACTION_MASK) {

				case AMOTION_EVENT_ACTION_DOWN: {
					engine->os->process_touch(0,0,touchvec);

				    //System.out.printf("action down at: %f,%f\n", event.getX(),event.getY());
				} break;
				case AMOTION_EVENT_ACTION_MOVE: {
					engine->os->process_touch(1,0,touchvec);
				    //for(int i=0;i<event.getPointerCount();i++) {
				    //	System.out.printf("%d - moved to: %f,%f\n",i, event.getX(i),event.getY(i));
				    //}
				} break;
				case AMOTION_EVENT_ACTION_POINTER_UP: {

					engine->os->process_touch(4,pidx,touchvec);
				    //System.out.printf("%d - s.up at: %f,%f\n",pointer_idx, event.getX(pointer_idx),event.getY(pointer_idx));
				} break;
				case AMOTION_EVENT_ACTION_POINTER_DOWN: {
					engine->os->process_touch(3,pidx,touchvec);
				    //System.out.printf("%d - s.down at: %f,%f\n",pointer_idx, event.getX(pointer_idx),event.getY(pointer_idx));
				} break;
				case AMOTION_EVENT_ACTION_CANCEL:
				case AMOTION_EVENT_ACTION_UP: {
					engine->os->process_touch(2,0,touchvec);
				    //for(int i=0;i<event.getPointerCount();i++) {
				    //	System.out.printf("%d - up! %f,%f\n",i, event.getX(i),event.getY(i));
				    //}
				} break;
			}

			return 1;
		} break;

	}

    return 0;
}

/**
 * Process the next main command.
 */

static void _gfx_init(void *ud,bool p_gl2) {

	struct engine* engine = (struct engine*)ud;
	engine_init_display(engine,p_gl2);
}

static void engine_handle_cmd(struct android_app* app, int32_t cmd) {
    struct engine* engine = (struct engine*)app->userData;
  // LOGI("**** CMD %i\n",cmd);
    switch (cmd) {
	case APP_CMD_SAVE_STATE:
	    // The system has asked us to save our current state.  Do so.
	    //engine->app->savedState = malloc(sizeof(struct saved_state));
	    //*((struct saved_state*)engine->app->savedState) = engine->state;
	    //engine->app->savedStateSize = sizeof(struct saved_state);
	    break;
	case APP_CMD_CONFIG_CHANGED:
	case APP_CMD_WINDOW_RESIZED: {

#if 0
// android blows
		if (engine->display_active) {

			EGLint w,h;
			eglQuerySurface(engine->display, engine->surface, EGL_WIDTH, &w);
			eglQuerySurface(engine->display, engine->surface, EGL_HEIGHT, &h);
			engine->os->init_video_mode(w,h);
			//print_line("RESIZED VIDEO MODE: "+itos(w)+","+itos(h));
			engine_draw_frame(engine);

		}
#else

		    if (engine->display_active) {


			    EGLint w,h;
			    eglQuerySurface(engine->display, engine->surface, EGL_WIDTH, &w);
			    eglQuerySurface(engine->display, engine->surface, EGL_HEIGHT, &h);
			  //  if (w==engine->os->get_video_mode().width && h==engine->os->get_video_mode().height)
				//    break;

			    engine_term_display(engine);


		    }


		    engine->os->reload_gfx();
		    engine_draw_frame(engine);
		    engine->animating=1;

		    /*
			    EGLint w,h;
			    eglQuerySurface(engine->display, engine->surface, EGL_WIDTH, &w);
			    eglQuerySurface(engine->display, engine->surface, EGL_HEIGHT, &h);
			    engine->os->init_video_mode(w,h);
			    //print_line("RESIZED VIDEO MODE: "+itos(w)+","+itos(h));

		    }*/

#endif

	} break;
	case APP_CMD_INIT_WINDOW:
	     //The window is being shown, get it ready.
	//	LOGI("INIT WINDOW");
		if (engine->app->window != NULL) {

			if (engine->os==NULL) {

				//do initialization here, when there's OpenGL! hackish but the only way
				engine->os = new OS_Android(_gfx_init,engine);

			//	char *args[]={"-test","gui",NULL};
				__android_log_print(ANDROID_LOG_INFO,"godot","pre asdasd setup...");
#if 0
				Error err  = Main::setup("apk",2,args);
#else
				Error err  = Main::setup("apk",0,NULL);

				String modules = Globals::get_singleton()->get("android/modules");
				Vector<String> mods = modules.split(",",false);
				mods.push_back("GodotOS");
				__android_log_print(ANDROID_LOG_INFO,"godot","mod count: %i",mods.size());

				if (mods.size()) {

					jclass activityClass = engine->jni->FindClass("android/app/NativeActivity");

					jmethodID getClassLoader = engine->jni->GetMethodID(activityClass,"getClassLoader", "()Ljava/lang/ClassLoader;");

					jobject cls = engine->jni->CallObjectMethod(app->activity->clazz, getClassLoader);

					jclass classLoader = engine->jni->FindClass("java/lang/ClassLoader");

					jmethodID findClass = engine->jni->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");


					static JNINativeMethod methods[] = {
					    {"registerSingleton",    "(Ljava/lang/String;Ljava/lang/Object;)V",(void *)&Java_com_android_godot_Godot_registerSingleton},
					    {"registerMethod",        "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V",(void *)&Java_com_android_godot_Godot_registerMethod},
					    {"getGlobal",      "(Ljava/lang/String;)Ljava/lang/String;",                    (void *)&Java_com_android_godot_Godot_getGlobal},
					};

					jstring gstrClassName = engine->jni->NewStringUTF("com/android/godot/Godot");
					jclass GodotClass = (jclass)engine->jni->CallObjectMethod(cls, findClass, gstrClassName);

					__android_log_print(ANDROID_LOG_INFO,"godot","godot ****^*^*?^*^*class data %x",GodotClass);

					engine->jni->RegisterNatives(GodotClass,methods,sizeof(methods)/sizeof(methods[0]));

					for (int i=0;i<mods.size();i++) {

						String m = mods[i];
						//jclass singletonClass = engine->jni->FindClass(m.utf8().get_data());

						jstring strClassName = engine->jni->NewStringUTF(m.utf8().get_data());
						jclass singletonClass = (jclass)engine->jni->CallObjectMethod(cls, findClass, strClassName);

						__android_log_print(ANDROID_LOG_INFO,"godot","****^*^*?^*^*class data %x",singletonClass);
						jmethodID initialize = engine->jni->GetStaticMethodID(singletonClass, "initialize", "(Landroid/app/Activity;)Lcom/android/godot/Godot$SingletonBase;");


						jobject obj = engine->jni->CallStaticObjectMethod(singletonClass,initialize,app->activity->clazz);
						__android_log_print(ANDROID_LOG_INFO,"godot","****^*^*?^*^*class instance %x",obj);
						jobject gob = engine->jni->NewGlobalRef(obj);


					}

				}

#endif


				if (!Main::start())
					 return; //should exit instead and print the error

				engine->os->main_loop_begin();
			} else {
				//i guess recreate resources?
				engine->os->reload_gfx();
			}


			engine->animating=1;
			engine_draw_frame(engine);
		}
	    break;
	case APP_CMD_TERM_WINDOW:
	    // The window is being hidden or closed, clean it up.
	//    LOGI("TERM WINDOW");
	    engine_term_display(engine);
	    break;
	case APP_CMD_GAINED_FOCUS:
	    // When our app gains focus, we start monitoring the accelerometer.
	    if (engine->accelerometerSensor != NULL) {
		ASensorEventQueue_enableSensor(engine->sensorEventQueue,
			engine->accelerometerSensor);
		// We'd like to get 60 events per second (in us).
		ASensorEventQueue_setEventRate(engine->sensorEventQueue,
			engine->accelerometerSensor, (1000L/60)*1000);

	    }
	    engine->animating = 1;
	    break;
	case APP_CMD_LOST_FOCUS:
	    // When our app loses focus, we stop monitoring the accelerometer.
	    // This is to avoid consuming battery while not being used.
	    if (engine->accelerometerSensor != NULL) {
		ASensorEventQueue_disableSensor(engine->sensorEventQueue,
			engine->accelerometerSensor);
	    }
	    // Also stop animating.
	    engine->animating = 0;
	    engine_draw_frame(engine);
	    break;
    }
}

void android_main(struct android_app* state) {
    struct engine engine;
    // Make sure glue isn't stripped.
    app_dummy();

     memset(&engine, 0, sizeof(engine));
     state->userData = &engine;
     state->onAppCmd = engine_handle_cmd;
     state->onInputEvent = engine_handle_input;
     engine.app = state;
     engine.requested_quit=false;
     engine.os=NULL;
     engine.display_active=false;

     FileAccessAndroid::asset_manager=state->activity->assetManager;

     // Prepare to monitor accelerometer
     engine.sensorManager = ASensorManager_getInstance();
     engine.accelerometerSensor = ASensorManager_getDefaultSensor(engine.sensorManager,
	     ASENSOR_TYPE_ACCELEROMETER);
     engine.sensorEventQueue = ASensorManager_createEventQueue(engine.sensorManager,
	     state->looper, LOOPER_ID_USER, NULL, NULL);


	ANativeActivity_setWindowFlags(state->activity,AWINDOW_FLAG_FULLSCREEN|AWINDOW_FLAG_KEEP_SCREEN_ON,0);

	state->activity->vm->AttachCurrentThread(&engine.jni, NULL);



     // loop waiting for stuff to do.

     while (1) {
	 // Read all pending events.
	 int ident;
	 int events;
	 struct android_poll_source* source;

	 // If not animating, we will block forever waiting for events.
	 // If animating, we loop until all events are read, then continue
	 // to draw the next frame of animation.

	 int nullmax=50;
	 while ((ident=ALooper_pollAll(engine.animating ? 0 : -1, NULL, &events,
		 (void**)&source)) >= 0) {

	     // Process this event.

	     if (source != NULL) {
	//	 LOGI("process\n");
		 source->process(state, source);
	     } else {
		     nullmax--;
		     if (nullmax<0)
			break;
	     }

	     // If a sensor has data, process it now.
	    // LOGI("events\n");
	     if (ident == LOOPER_ID_USER) {
		 if (engine.accelerometerSensor != NULL) {
		     ASensorEvent event;
		     while (ASensorEventQueue_getEvents(engine.sensorEventQueue,
			     &event, 1) > 0) {


			     if (engine.os) {
				     engine.os->process_accelerometer(Vector3(event.acceleration.x, event.acceleration.y,
									      event.acceleration.z));

			     }

		     }
		 }
	     }

	     // Check if we are exiting.
	     if (state->destroyRequested != 0) {
		     if (engine.os) {
			     engine.os->main_loop_request_quit();
		     }
		    state->destroyRequested=0;
	     }

	     if (engine.requested_quit) {
		engine_term_display(&engine);
		exit(0);
		return;
	     }

//	     LOGI("end\n");


	 }

//	 LOGI("engine animating? %i\n",engine.animating);

	 if (engine.animating) {
	     //do os render

	     engine_draw_frame(&engine);
	     //LOGI("TERM WINDOW");

	 }
     }

}




JNIEXPORT void JNICALL Java_com_android_godot_Godot_registerSingleton(JNIEnv * env, jobject obj, jstring name,jobject p_object){

	String singname = env->GetStringUTFChars( name, NULL );
	JNISingleton *s = memnew( JNISingleton );
	s->update_env(env);
	s->set_instance(env->NewGlobalRef(p_object));
	jni_singletons[singname]=s;

	Globals::get_singleton()->add_singleton(Globals::Singleton(singname,s));

}


static Variant::Type get_jni_type(const String& p_type) {

	static struct {
		const char *name;
		Variant::Type type;
	} _type_to_vtype[]={
		{"void",Variant::NIL},
		{"boolean",Variant::BOOL},
		{"int",Variant::INT},
		{"float",Variant::REAL},
		{"java.lang.String",Variant::STRING},
		{"[I",Variant::INT_ARRAY},
		{"[F",Variant::REAL_ARRAY},
		{"[java.lang.String",Variant::STRING_ARRAY},
		{NULL,Variant::NIL}
	};

	int idx=0;

	while (_type_to_vtype[idx].name) {

		if (p_type==_type_to_vtype[idx].name)
			return _type_to_vtype[idx].type;

		idx++;
	}

	return Variant::NIL;
}


static const char* get_jni_sig(const String& p_type) {

	static struct {
		const char *name;
		const char *sig;
	} _type_to_vtype[]={
		{"void","V"},
		{"boolean","Z"},
		{"int","I"},
		{"float","F"},
		{"java.lang.String","Ljava/lang/String;"},
		{"[I","[I"},
		{"[F","[F"},
		{"[java.lang.String","[Ljava/lang/String;"},
		{NULL,"V"}
	};

	int idx=0;

	while (_type_to_vtype[idx].name) {

		if (p_type==_type_to_vtype[idx].name)
			return _type_to_vtype[idx].sig;

		idx++;
	}


	return "";
}

JNIEXPORT jstring JNICALL Java_com_android_godot_Godot_getGlobal(JNIEnv * env, jobject obj, jstring path) {

	String js = env->GetStringUTFChars( path, NULL );

	return env->NewStringUTF(Globals::get_singleton()->get(js).operator String().utf8().get_data());


}



JNIEXPORT void JNICALL Java_com_android_godot_Godot_registerMethod(JNIEnv * env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args){

	String singname = env->GetStringUTFChars( sname, NULL );

	ERR_FAIL_COND(!jni_singletons.has(singname));

	JNISingleton *s = jni_singletons.get(singname);


	String mname = env->GetStringUTFChars( name, NULL );
	String retval = env->GetStringUTFChars( ret, NULL );
	Vector<Variant::Type> types;
	String cs="(";


	int stringCount = env->GetArrayLength(args);

	print_line("Singl:  "+singname+" Method: "+mname+" RetVal: "+retval);
	for (int i=0; i<stringCount; i++) {

		jstring string = (jstring) env->GetObjectArrayElement(args, i);
		const char *rawString = env->GetStringUTFChars(string, 0);
		types.push_back(get_jni_type(String(rawString)));
		cs+=get_jni_sig(String(rawString));
	}

	cs+=")";
	cs+=get_jni_sig(retval);
	jclass cls = env->GetObjectClass(s->get_instance());
	print_line("METHOD: "+mname+" sig: "+cs);
	jmethodID mid = env->GetMethodID(cls, mname.ascii().get_data(), cs.ascii().get_data());
	if (!mid) {

		print_line("FAILED GETTING METHOID "+mname);
	}

	s->add_method(mname,mid,types,get_jni_type(retval));


}

#endif
