#ifndef APPPLATFORM_ANDROID_H__
#define APPPLATFORM_ANDROID_H__

#include "AppPlatform.h"
#include "client/renderer/gles.h"
#include "platform/log.h"
#include "platform/time.h"
#include <jni.h>
#include <cmath>
#include <exception>
#include <android/native_activity.h>
class JVMAttacher {
public:
    JVMAttacher(JavaVM* vm)
    :   _vm(vm),
        _isAttached(false),
        _env(NULL)
    {
        if( _vm->GetEnv((void**)&_env, JNI_VERSION_1_4 ) != JNI_OK ) {
            _vm->AttachCurrentThread(&_env, NULL);
            _isAttached = _env != NULL;
        }
    }

    ~JVMAttacher() {
        forceDetach();
    }

    JNIEnv* getEnv() {
        return _env;
    }

    void forceDetach() {
        if (!_isAttached) return;
        _vm->DetachCurrentThread();
        _isAttached = false;
    }

private:
    JavaVM* _vm;
    JNIEnv* _env;
    bool    _isAttached;
};

class AppPlatform_android: public AppPlatform
{
    typedef AppPlatform super;
public:
    AppPlatform_android()
    :   _vm(NULL),
        _isInited(false),

        instance(0),
        _activityClass(0),

        _methodSaveScreenshot(0),
        _postScreenshotToFacebook(0),
        _getImageData(0),
		_readAssetFile(0),
        _methodPlaySound(0),
        _showDialog(0),
        _methodTick(0),
        _methodFinish(0),
        
        _methodUserInputInitiate(0),
        _methodUserInputStatus(0),
        _methodUserInputString(0),

        _methodGetDateString(0),

        _methodCheckLicense(0),
        _methodHasBuyButton(0),
        _methodBuyGame(0),
        _methodVibrate(0),
        _methodSupportsTouchscreen(0),
		_methodSetIsPowerVR(0),
		_methodIsNetworkEnabled(0),

        _getScreenWidth(0),
        _getScreenHeight(0),
        _methodGetPixelsPerMillimeter(0),
		_methodGetPlatformStringVar(0),

		_classWindow(0),
		_classContext(0),
		_fieldINPUT_METHOD_SERVICE(0),
		_classInputManager(0),
		_methodGetSystemService(0),
		_methodGetWindow(0),
		_methodGetDecorView(0),
		_methodShowSoftInput(0),
		_classView(0),
		_methodGetWindowToken(0),
		_methodHideSoftInput(0),

        _screenWidth(854),
        _screenHeight(480),
		_hasSetPowerVR(false),
		_nativeActivity(0),

		_methodGetKeyFromKeyCode(0)
    {
    }

    int getScreenWidth() { return _screenWidth; }
    int getScreenHeight() { return _screenHeight; }
    void setScreenDimensions(int w, int h) { _screenWidth = w; _screenHeight = h; }

    // Note, this has to be called from the main thread (e.g. do it from JNI_onLoad)
    // Somewhere between calling this, and calling the AppPlatform methods,
    // this->instance will be assigned by its creator
    int init(JavaVM* vm) {
        if (_isInited)
            return -1;

        JVMAttacher ta(vm);
        JNIEnv* env = ta.getEnv();

        if(!env) {
            LOGI("%s - Failed to get the environment using JVMAttacher::getEnv()", __FUNCTION__);
            return -2;
        }

        // Get Class reference
        const char* interface_path = "com/mojang/minecraftpe/MainActivity";
        jclass clazz = NULL;
        if( (clazz = env->FindClass( interface_path )) == 0 ) {
            return -3;
        }
        _activityClass = (jclass)env->NewGlobalRef(clazz);

        // Save all the method IDs
        _methodSaveScreenshot = env->GetStaticMethodID( _activityClass, "saveScreenshot", "(Ljava/lang/String;II[I)V");
        _postScreenshotToFacebook = env->GetMethodID( _activityClass, "postScreenshotToFacebook", "(Ljava/lang/String;II[I)V");
        _getImageData = env->GetMethodID( _activityClass, "getImageData", "(Ljava/lang/String;)[I");
		_readAssetFile = env->GetMethodID( _activityClass, "getFileDataBytes", "(Ljava/lang/String;)[B");
        #if defined(PRE_ANDROID23)
            _methodPlaySound = env->GetMethodID( _activityClass, "playSound", "(Ljava/lang/String;FF)V");
        #endif
        _showDialog = env->GetMethodID( _activityClass, "displayDialog", "(I)V");
        _methodTick = env->GetMethodID( _activityClass, "tick", "()V");
        _methodFinish = env->GetMethodID( _activityClass, "quit", "()V");

        _methodUserInputInitiate = env->GetMethodID( _activityClass, "initiateUserInput", "(I)V");
        _methodUserInputStatus = env->GetMethodID( _activityClass, "getUserInputStatus", "()I");
        _methodUserInputString = env->GetMethodID( _activityClass, "getUserInputString", "()[Ljava/lang/String;");

        _methodGetDateString = env->GetMethodID( _activityClass, "getDateString", "(I)Ljava/lang/String;");

        _methodCheckLicense = env->GetMethodID( _activityClass, "checkLicense", "()I");
        _methodHasBuyButton = env->GetMethodID( _activityClass, "hasBuyButtonWhenInvalidLicense", "()Z");
        _methodBuyGame = env->GetMethodID( _activityClass, "buyGame", "()V");

        _methodVibrate = env->GetMethodID( _activityClass, "vibrate", "(I)V");
        _methodSupportsTouchscreen = env->GetMethodID( _activityClass, "supportsTouchscreen", "()Z");
		_methodSetIsPowerVR  = env->GetMethodID( _activityClass, "setIsPowerVR", "(Z)V");
		_methodIsNetworkEnabled = env->GetMethodID( _activityClass, "isNetworkEnabled", "(Z)Z");

		_methodGetPixelsPerMillimeter = env->GetMethodID( _activityClass, "getPixelsPerMillimeter", "()F");
		_methodGetPlatformStringVar = env->GetMethodID( _activityClass, "getPlatformStringVar", "(I)Ljava/lang/String;");

		_classWindow = (jclass)env->NewGlobalRef(env->FindClass("android/view/Window"));
		_classContext = (jclass)env->NewGlobalRef(env->FindClass("android/content/Context"));
		_classView = (jclass)env->NewGlobalRef(env->FindClass( "android/view/View"));
		_classInputManager = (jclass)env->NewGlobalRef(env->FindClass("android/view/inputmethod/InputMethodManager"));

		_fieldINPUT_METHOD_SERVICE = env->GetStaticFieldID(_classContext, "INPUT_METHOD_SERVICE", "Ljava/lang/String;");
		_methodGetSystemService = env->GetMethodID(_activityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
		_methodGetWindow = env->GetMethodID( _activityClass, "getWindow", "()Landroid/view/Window;");
		_methodGetDecorView = env->GetMethodID(_classWindow, "getDecorView", "()Landroid/view/View;");
		_methodShowSoftInput = env->GetMethodID(_classInputManager, "showSoftInput", "(Landroid/view/View;I)Z");
		_methodGetWindowToken = env->GetMethodID(_classView, "getWindowToken", "()Landroid/os/IBinder;");
		_methodHideSoftInput = env->GetMethodID(_classInputManager, "hideSoftInputFromWindow", "(Landroid/os/IBinder;I)Z");
		
		_methodGetKeyFromKeyCode = env->GetMethodID( _activityClass, "getKeyFromKeyCode", "(III)I");

        if (env->ExceptionOccurred()) {
            env->ExceptionDescribe();
        }

 //       LOGI("Class: %d\n. Methods: %d,%d\n", _activityClass,
   //         _methodSaveScreenshot,
     //       _postScreenshotToFacebook);

        _vm = vm;
        _isInited = true;

        return JNI_VERSION_1_4;
    }

    // Stuff that should only be written once, e.g. device specs
    // @note: This is called after instance is set, BUT this will
    //        be rewritten later on anyway
    int initConsts() {

        LOGI("initConsts: start\n");
        JVMAttacher ta(_vm);
        JNIEnv* env = ta.getEnv();

        LOGI("initConsts: getting method IDs\n");
        jmethodID fWidth = env->GetMethodID( _activityClass, "getScreenWidth", "()I");
        LOGI("initConsts: got fWidth=%p\n", fWidth);
        jmethodID fHeight = env->GetMethodID( _activityClass, "getScreenHeight", "()I");
        LOGI("initConsts: got fHeight=%p, calling getScreenWidth\n", fHeight);

        _screenWidth = env->CallIntMethod(instance, fWidth);
        LOGI("initConsts: screenWidth=%d, calling getScreenHeight\n", _screenWidth);
        _screenHeight = env->CallIntMethod(instance, fHeight);
        LOGI("initConsts: screenHeight=%d, done\n", _screenHeight);
    }

    void tick() {
        if (!_isInited) return;
        if (!_methodTick) return;

        JVMAttacher ta(_vm);
        JNIEnv* env = ta.getEnv();

        env->CallVoidMethod(instance, _methodTick);
    }

    void showDialog(int dialogId) {
        if (!_isInited) return;
        if (!_showDialog) return;

        JVMAttacher ta(_vm);
        JNIEnv* env = ta.getEnv();

        env->CallVoidMethod(instance, _showDialog, dialogId);
    }

    void createUserInput() {
        if (!_isInited) return;
        if (!_methodUserInputInitiate) return;

        JVMAttacher ta(_vm);
        JNIEnv* env = ta.getEnv();

        env->CallVoidMethod(instance, _methodUserInputInitiate, 1);
    }

    int getUserInputStatus() {
        if (!_isInited) return -2;
        if (!_methodUserInputStatus) return -2;

        JVMAttacher ta(_vm);
        JNIEnv* env = ta.getEnv();

        return env->CallIntMethod(instance, _methodUserInputStatus);
    }

    float getPixelsPerMillimeter() {
        if (!_isInited) return 10;
        if (!_methodGetPixelsPerMillimeter) return 10;

        JVMAttacher ta(_vm);
        JNIEnv* env = ta.getEnv();

        return env->CallFloatMethod(instance, _methodGetPixelsPerMillimeter);
    }

    StringVector getUserInput() {
        if (!_isInited) return std::vector<std::string>();
        if (!_methodUserInputString) return std::vector<std::string>();

        JVMAttacher ta(_vm);
        JNIEnv* env = ta.getEnv();

        jobjectArray sa = (jobjectArray) env->CallObjectMethod(instance, _methodUserInputString);
        jsize len = env->GetArrayLength(sa);

        StringVector out;
        for (int i = 0; i < len; ++i) {

            jstring s = (jstring)env->GetObjectArrayElement(sa, i);
            int length = env->GetStringLength (s);
            if (length > 1023)
                length = 1023;
            static char buf[1024];

            // Only supporting 7 bit chars, right now
            const char* str = env->GetStringUTFChars(s, 0);
            strncpy(buf, str, 1023);
            buf[1023] = 0;

            out.push_back(std::string(buf));
        }
        return out;
    }

    void saveScreenshot(const std::string& filename, int glWidth, int glHeight) {
        if (!_isInited) return;
        if (!_methodSaveScreenshot) return;

        JVMAttacher ta(_vm);
        JNIEnv* env = ta.getEnv();

        const int numPixels = glWidth * glHeight;
        unsigned int* pixels = new unsigned int[numPixels];

        if (!pixels)
            return;

        // Create the OpenGL "screenshot"
        glReadPixels(0, 0, glWidth, glHeight, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
        convertOpenGlToAndroidPixels(glWidth, glHeight, pixels);

        // Tell Android to save the screenshot
        jintArray jPixels = env->NewIntArray(numPixels);
        if (jPixels != NULL)
        {
            jobject gpixRef = env->NewGlobalRef(jPixels);
            env->SetIntArrayRegion(jPixels, 0, numPixels, (int*)pixels);
//                env->CallVoidMethod(    instance,
//                                        _postScreenshotToFacebook,
//                                        env->NewStringUTF(filename.c_str()),
//                                        glWidth, glHeight, jPixels);
            env->CallStaticVoidMethod(  _activityClass,
                                        _methodSaveScreenshot,
                                        env->NewStringUTF(filename.c_str()),
                                        glWidth, glHeight, jPixels);
            // Teardown
            env->DeleteGlobalRef(gpixRef);
            delete[] pixels;
        }
    }

    __inline unsigned int rgbToBgr(unsigned int p) {
        return (p & 0xff00ff00) | ((p >> 16) & 0xff) | ((p << 16) & 0xff0000);
    }

    void convertOpenGlToAndroidPixels(int w, int h, unsigned int* pixels) {
        // Making the conversion in-place
        for (int y = 0; y < h/2; ++y)
        for (int x = 0; x < w;  ++x) {
            const int i0 = y * w + x;
            const int i1 = (h - y - 1) * w + x;
            const unsigned int p1 = pixels[i1];
            const unsigned int p0 = pixels[i0];
            pixels[i0] = rgbToBgr(p1);
            pixels[i1] = rgbToBgr(p0);
        }
        // Handle the middle row, if any
        if (h & 1) {
            const int start = w * h/2;
            const int stop = start + w;
            for (int i = start; i < stop; ++i) {
                const unsigned int p = pixels[i];
                pixels[i] = rgbToBgr(p);
            }
        }
    }

    virtual void playSound(const std::string& filename, float volume, float pitch) {
        if (!_isInited || !_methodPlaySound) return;

        //static Stopwatch w;
        //w.start();
        JVMAttacher ta(_vm);
        JNIEnv* env = ta.getEnv();

        env->CallVoidMethod(instance, _methodPlaySound, env->NewStringUTF(filename.c_str()), volume, pitch);
        //w.stop();
        //w.printEvery(1, "playSound-java");
    }

    virtual void uploadPlatformDependentData(int id, void* data) {
        if (!_isInited) return;

        JVMAttacher ta(_vm);
        JNIEnv* env = ta.getEnv();

        if (id == 1) {
        }
        if (id == 2) {
        }
    }

    TextureData loadTexture(const std::string& filename, bool textureFolder) {
        if (!_isInited) return TextureData();
        if (!_getImageData) return TextureData();

        JVMAttacher ta(_vm);
        JNIEnv* env = ta.getEnv();

        std::string path = textureFolder ? "images/" + filename : filename;
        jintArray arr = (jintArray)env->CallObjectMethod(
            instance, _getImageData, env->NewStringUTF(path.c_str()));

        if (!arr)
            return TextureData();

        jsize len = env->GetArrayLength(arr);
        int numPixels = len-2;

        // pixels = [width, height, p_0, p_1, ..., p_numPixels-1]
        int* pixels = new int[numPixels];
        jint* body = env->GetIntArrayElements(arr, 0);

        int w = body[0];
        int h = body[1];
        memcpy(pixels, &body[2], numPixels * sizeof(int));

        // Convert pixels
        for (int i = 0; i < numPixels; ++i)
            pixels[i] = rgbToBgr(pixels[i]);

        //LOGI("Read-Image-Data: Decided file size to be: %d x %d (len %d)\n", w, h, len);

        env->ReleaseIntArrayElements(arr, body, 0);
  //      LOGI("loadtexture: 9\n");

        // Set the result
        TextureData out;
        out.w = w;
        out.h = h;
        out.data = (unsigned char*)pixels;
        out.memoryHandledExternally = false;
        return out;
    }

	BinaryBlob readAssetFile(const std::string& filename) {
		if (!_isInited) return BinaryBlob();
		if (!_readAssetFile) return BinaryBlob();

		JVMAttacher ta(_vm);
		JNIEnv* env = ta.getEnv();

		jbyteArray arr = (jbyteArray)env->CallObjectMethod(
			instance, _readAssetFile, env->NewStringUTF(filename.c_str()));

		if (!arr)
			return BinaryBlob();

		jsize len = env->GetArrayLength(arr);
		BinaryBlob blob(new unsigned char[len], len);
		jbyte* body = env->GetByteArrayElements(arr, 0);
		memcpy(blob.data, body, len);

		env->ReleaseByteArrayElements(arr, body, 0);
		return blob;
	}

	std::string getDateString(int s) {
        if (!_isInited) return "";
        if (!_methodGetDateString) return "";

        JVMAttacher ta(_vm);
        JNIEnv* env = ta.getEnv();

        std::string out;
        jstring dateString = (jstring)env->CallObjectMethod(instance, _methodGetDateString, s);
        const char* str = env->GetStringUTFChars(dateString, NULL);
        if (str)
            out = str;
        env->ReleaseStringUTFChars(dateString, str);
        return out;
    }

	std::string getPlatformStringVar(int s) {
		if (!_isInited) return "";
		if (!_methodGetPlatformStringVar) return "";

		JVMAttacher ta(_vm);
		JNIEnv* env = ta.getEnv();

		std::string out;
		jstring stringVar = (jstring)env->CallObjectMethod(instance, _methodGetPlatformStringVar, s);
		const char* str = env->GetStringUTFChars(stringVar, NULL);
		if (str)
			out = str;
		env->ReleaseStringUTFChars(stringVar, str);
		return out;
	}

	int checkLicense() {
        if (!_isInited) return -2;
        if (!_methodCheckLicense) return -2;

        JVMAttacher ta(_vm);
        JNIEnv* env = ta.getEnv();

        return env->CallIntMethod(instance, _methodCheckLicense);
    }

    bool hasBuyButtonWhenInvalidLicense() {
        if (!_isInited) return false;
        if (!_methodHasBuyButton) return false;

        JVMAttacher ta(_vm);
        JNIEnv* env = ta.getEnv();

        return JNI_TRUE == env->CallBooleanMethod(instance, _methodHasBuyButton);
    }

    void buyGame() {
        if (!_isInited) return;
        if (!_methodBuyGame) return;

        JVMAttacher ta(_vm);
        JNIEnv* env = ta.getEnv();

        env->CallVoidMethod(instance, _methodBuyGame);
    }

    virtual void finish() {
        if (!_isInited) return;
        if (!_methodFinish) return;

        JVMAttacher ta(_vm);
        JNIEnv* env = ta.getEnv();

        env->CallVoidMethod(instance, _methodFinish);
        env->DeleteGlobalRef( _activityClass );

        env->DeleteGlobalRef( _classWindow );
        env->DeleteGlobalRef( _classContext );
        env->DeleteGlobalRef( _classView );
        env->DeleteGlobalRef( _classInputManager );
    }

    virtual bool supportsTouchscreen() {
        if (!_isInited) return true;
        if (!_methodSupportsTouchscreen) return true;

        JVMAttacher ta(_vm);
        JNIEnv* env = ta.getEnv();

        return env->CallBooleanMethod(instance, _methodSupportsTouchscreen);
    }

	virtual void vibrate(int milliSeconds) {
        if (!_isInited) return;
        if (!_methodVibrate) return;

        JVMAttacher ta(_vm);
        JNIEnv* env = ta.getEnv();

        return env->CallVoidMethod(instance, _methodVibrate, milliSeconds);
    }

	virtual bool isPowerVR() {
	    bool is = super::isPowerVR();

		if (_methodSetIsPowerVR && _isInited && !_hasSetPowerVR ) {
			JVMAttacher ta(_vm);
			JNIEnv* env = ta.getEnv();
			_hasSetPowerVR = true;
			env->CallVoidMethod(instance, _methodSetIsPowerVR, is);
		}
		return is;
	}

	virtual bool isNetworkEnabled(bool onlyWifiAllowed) {
		if (!_isInited) return false;
		if (!_methodIsNetworkEnabled) return false;

		JVMAttacher ta(_vm);
		JNIEnv* env = ta.getEnv();

		return env->CallBooleanMethod(instance, _methodIsNetworkEnabled, onlyWifiAllowed);
	}

    static __inline bool isSquare(int n) {
        int L = n & 0xf;
        if ((1 << L) & 0x213 == 0) return false;

        int t = (int) sqrt((double) n) + 0.5;
        return t*t == n;
    }

	virtual void showKeyboard() {
		showKeyboard(true);
		super::showKeyboard();
	}
	virtual void hideKeyboard() {
		showKeyboard(false);
		super::hideKeyboard();
	}
	virtual void showKeyboard(bool bShow) {
		if (!_isInited) return;

		jint lResult = 0;
		jint lFlags = 0;

		JVMAttacher ta(_vm);
		JNIEnv* env = ta.getEnv();

 		jobject INPUT_METHOD_SERVICE =
 			env->GetStaticObjectField(_classContext,
 			_fieldINPUT_METHOD_SERVICE);

		jobject lInputMethodManager = env->CallObjectMethod(
			instance, _methodGetSystemService,
			INPUT_METHOD_SERVICE);

		jobject lWindow = env->CallObjectMethod(instance,
			_methodGetWindow);

		jobject lDecorView = env->CallObjectMethod(lWindow,
			_methodGetDecorView);

		if (bShow) {
			// Runs lInputMethodManager.showSoftInput(...).
			jboolean lResult = env->CallBooleanMethod(
				lInputMethodManager, _methodShowSoftInput,
				lDecorView, lFlags);
		} else {
			// Runs lWindow.getViewToken()
			jobject lBinder = env->CallObjectMethod(lDecorView,
				_methodGetWindowToken);
			jboolean lRes = env->CallBooleanMethod(
				lInputMethodManager, _methodHideSoftInput,
				lBinder, lFlags);
		}
	}
	virtual int getKeyFromKeyCode(int keyCode, int metaState, int deviceId) {
		JVMAttacher ta(_vm);
		JNIEnv* env = ta.getEnv();

		return (int) env->CallIntMethod(instance, _methodGetKeyFromKeyCode, keyCode, metaState, deviceId);
	}
public:
    jobject instance;

private:
    bool _isInited;
    JavaVM* _vm;

    jclass _activityClass;

    jmethodID _methodSaveScreenshot;
    jmethodID _postScreenshotToFacebook;
    jmethodID _getImageData;
	jmethodID _readAssetFile;
    jmethodID _methodPlaySound;
    jmethodID _showDialog;
    jmethodID _methodTick;
    jmethodID _methodFinish;

    jmethodID _methodUserInputInitiate;
    jmethodID _methodUserInputStatus;
    jmethodID _methodUserInputString;

    jmethodID _methodGetDateString;

    jmethodID _methodCheckLicense;
    jmethodID _methodHasBuyButton;
    jmethodID _methodBuyGame;

    jmethodID _getScreenWidth;
    jmethodID _getScreenHeight;
    jmethodID _methodGetPixelsPerMillimeter;
    jmethodID _methodVibrate;
    jmethodID _methodSupportsTouchscreen;
    jmethodID _methodSetIsPowerVR;
	jmethodID _methodIsNetworkEnabled;

	jmethodID _methodGetPlatformStringVar;

	jclass _classWindow;
	jclass _classContext;
	jfieldID _fieldINPUT_METHOD_SERVICE;
	jclass _classInputManager;
	jmethodID _methodGetSystemService;
	jmethodID _methodGetWindow;
	jmethodID _methodGetDecorView;
	jmethodID _methodShowSoftInput;
	jclass _classView;
	jmethodID _methodGetWindowToken;
	jmethodID _methodHideSoftInput;

	jmethodID _methodGetKeyFromKeyCode;

    int _screenWidth;
    int _screenHeight;
	bool _hasSetPowerVR;
public:
	ANativeActivity* _nativeActivity;
};

#endif /*APPPLATFORM_ANDROID_H__*/
