/*************************************************************************/
/*  godot_android.cpp                                                    */
/*************************************************************************/
/*                       This file is part of:                           */
/*                           GODOT ENGINE                                */
/*                      https://godotengine.org                          */
/*************************************************************************/
/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur.                 */
/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md)    */
/*                                                                       */
/* 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 "core/engine.h"
#include "core/project_settings.h"
#include "file_access_android.h"
#include "main/main.h"
#include "os_android.h"

#include <EGL/egl.h>
#include <android/log.h>
#include <android/sensor.h>
#include <android/window.h>
#include <android_native_app_glue.h>
#include <errno.h>
#include <jni.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.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_org_godotengine_godot_Godot_registerSingleton(JNIEnv *env, jobject obj, jstring name, jobject p_object);
JNIEXPORT void JNICALL Java_org_godotengine_godot_Godot_registerMethod(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args);
JNIEXPORT jstring JNICALL Java_org_godotengine_godot_Godot_getGlobal(JNIEnv *env, jobject obj, jstring path);
};

class JNISingleton : public Object {

	GDCLASS(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) {

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

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

			r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD;
			return Variant();
		}

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

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

		if (ac > p_argcount) {

			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: {

					PoolVector<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: {

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

				} break;
				case Variant::REAL_ARRAY: {

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

				} break;
				default: {

					ERR_FAIL_V(Variant());
				} break;
			}
		}

		Variant ret;

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

			case Variant::NIL: {

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

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

				ret = env->CallIntMethodA(instance, E->get().method, v);
			} 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);
				PoolVector<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);
				PoolVector<int> sarr;
				sarr.resize(fCount);

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

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

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

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

				ERR_FAIL_V(Variant());
			} break;
		}

		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;
	const ASensor *magnetometerSensor;
	const ASensor *gyroscopeSensor;
	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);

	//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()) {

		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 (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;

		} 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);

					__android_log_print(ANDROID_LOG_INFO, "godot", "pre asdasd setup...");

					Error err = Main::setup("apk", 0, NULL);

					String modules = ProjectSettings::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_org_godotengine_godot_Godot_registerSingleton },
							{ "registerMethod", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V", (void *)&Java_org_godotengine_godot_Godot_registerMethod },
							{ "getGlobal", "(Ljava/lang/String;)Ljava/lang/String;", (void *)&Java_org_godotengine_godot_Godot_getGlobal },
						};

						jstring gstrClassName = engine->jni->NewStringUTF("org/godotengine/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;)Lorg/godotengine/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);
						}
					}

					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);
			}
			// start monitoring gravity
			if (engine->gravitySensor != NULL) {
				ASensorEventQueue_enableSensor(engine->sensorEventQueue,
						engine->gravitySensor);
				// We'd like to get 60 events per second (in us).
				ASensorEventQueue_setEventRate(engine->sensorEventQueue,
						engine->gravitySensor, (1000L / 60) * 1000);
			}
			// Also start monitoring the magnetometer.
			if (engine->magnetometerSensor != NULL) {
				ASensorEventQueue_enableSensor(engine->sensorEventQueue,
						engine->magnetometerSensor);
				// We'd like to get 60 events per second (in us).
				ASensorEventQueue_setEventRate(engine->sensorEventQueue,
						engine->magnetometerSensor, (1000L / 60) * 1000);
			}
			// And the gyroscope.
			if (engine->gyroscopeSensor != NULL) {
				ASensorEventQueue_enableSensor(engine->sensorEventQueue,
						engine->gyroscopeSensor);
				// We'd like to get 60 events per second (in us).
				ASensorEventQueue_setEventRate(engine->sensorEventQueue,
						engine->gyroscopeSensor, (1000L / 60) * 1000);
			}
			engine->animating = 1;
			break;
		case APP_CMD_LOST_FOCUS:
			// When our app loses focus, we stop monitoring the sensors.
			// This is to avoid consuming battery while not being used.
			if (engine->accelerometerSensor != NULL) {
				ASensorEventQueue_disableSensor(engine->sensorEventQueue,
						engine->accelerometerSensor);
			}
			if (engine->gravitySensor != NULL) {
				ASensorEventQueue_disableSensor(engine->sensorEventQueue,
						engine->gravitySensor);
			}
			if (engine->magnetometerSensor != NULL) {
				ASensorEventQueue_disableSensor(engine->sensorEventQueue,
						engine->magnetometerSensor);
			}
			if (engine->gyroscopeSensor != NULL) {
				ASensorEventQueue_disableSensor(engine->sensorEventQueue,
						engine->gyroscopeSensor);
			}
			// Also stop animating.
			engine->animating = 0;
			engine_draw_frame(engine);
			break;
	}
}

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

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

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

	// Prepare to monitor sensors
	engine.sensorManager = ASensorManager_getInstance();
	engine.accelerometerSensor = ASensorManager_getDefaultSensor(engine.sensorManager,
			ASENSOR_TYPE_ACCELEROMETER);
	engine.gravitySensor = ASensorManager_getDefaultSensor(engine.sensorManager,
			ASENSOR_TYPE_GRAVITY);
	engine.magnetometerSensor = ASensorManager_getDefaultSensor(engine.sensorManager,
			ASENSOR_TYPE_MAGNETIC_FIELD);
	engine.gyroscopeSensor = ASensorManager_getDefaultSensor(engine.sensorManager,
			ASENSOR_TYPE_GYROSCOPE);
	engine.sensorEventQueue = ASensorManager_createEventQueue(engine.sensorManager,
			app->looper, LOOPER_ID_USER, NULL, NULL);

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

	app->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(app, 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 || engine.magnetometerSensor != NULL || engine.gyroscopeSensor != NULL) {
					ASensorEvent event;
					while (ASensorEventQueue_getEvents(engine.sensorEventQueue,
								   &event, 1) > 0) {

						if (engine.os) {
							if (event.acceleration != NULL) {
								engine.os->process_accelerometer(Vector3(event.acceleration.x, event.acceleration.y,
										event.acceleration.z));
							}
							if (event.magnetic != NULL) {
								engine.os->process_magnetometer(Vector3(event.magnetic.x, event.magnetic.y,
										event.magnetic.z));
							}
							if (event.vector != NULL) {
								engine.os->process_gyroscope(Vector3(event.vector.x, event.vector.y,
										event.vector.z));
							}
						}
					}
				}
			}

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

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

			//     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_org_godotengine_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;

	Engine::get_singleton()->add_singleton(Engine::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 },
		{ "[Ljava.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" },
		{ "[Ljava.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_org_godotengine_godot_Godot_getGlobal(JNIEnv *env, jobject obj, jstring path) {

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

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

JNIEXPORT void JNICALL Java_org_godotengine_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);

	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());
	jmethodID mid = env->GetMethodID(cls, mname.ascii().get_data(), cs.ascii().get_data());
	if (!mid) {

		print_line("RegisterMethod: Failed getting method ID: " + mname);
	}

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

#endif