// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

#include <jni.h>
#include <stdio.h>
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
#include <android/bitmap.h>

#include "core/pch.h"
#include "emu.h"
#include "core/io.h"
#include "core/kml.h"
#include "win32-layer.h"

extern AAssetManager * assetManager;
static jobject mainActivity = NULL;
jobject bitmapMainScreen = NULL;
AndroidBitmapInfo androidBitmapInfo;
//RECT mainViewRectangleToUpdate = { 0, 0, 0, 0 };
enum DialogBoxMode currentDialogBoxMode;
LPBYTE pbyRomBackup = NULL;
enum ChooseKmlMode chooseCurrentKmlMode;
TCHAR szChosenCurrentKml[MAX_PATH];
TCHAR szKmlLog[10240];
TCHAR szKmlLogBackup[10240];
TCHAR szKmlTitle[10240];
BOOL securityExceptionOccured;
BOOL kmlFileNotFound = FALSE;
BOOL settingsPort2en;
BOOL settingsPort2wr;
BOOL soundAvailable = FALSE;
BOOL soundEnabled = FALSE;
BOOL serialPortSlowDown = FALSE;



extern void win32Init();

extern void draw();
extern BOOL buttonDown(int x, int y);
extern void buttonUp(int x, int y);
extern void keyDown(int virtKey);
extern void keyUp(int virtKey);


JavaVM *java_machine;
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    java_machine = vm;
    win32Init();
    return JNI_VERSION_1_6;
}

JNIEnv *getJNIEnvironment() {
    JNIEnv * jniEnv;
    jint ret;
    BOOL needDetach = FALSE;
    ret = (*java_machine)->GetEnv(java_machine, (void **) &jniEnv, JNI_VERSION_1_6);
    if (ret == JNI_EDETACHED) {
        // GetEnv: not attached
        ret = (*java_machine)->AttachCurrentThread(java_machine, &jniEnv, NULL);
        if (ret == JNI_OK) {
            needDetach = TRUE;
        }
    }
    return jniEnv;
}

int jniDetachCurrentThread() {
    jint ret = (*java_machine)->DetachCurrentThread(java_machine);
    return ret;
}

enum CALLBACK_TYPE {
    CALLBACK_TYPE_INVALIDATE = 0,
    CALLBACK_TYPE_WINDOW_RESIZE = 1
};

// https://stackoverflow.com/questions/9630134/jni-how-to-callback-from-c-or-c-to-java
int mainViewCallback(int type, int param1, int param2) {
    if (mainActivity) {
        JNIEnv *jniEnv = getJNIEnvironment();
        if(jniEnv) {
            jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
            if(mainActivityClass) {
	            jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "updateCallback", "(III)I");
	            int result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, type, param1, param2);
                (*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
                //if(needDetach) ret = (*java_machine)->DetachCurrentThread(java_machine);
                return result;
            }
        }
    }
    return 0;
}

void mainViewUpdateCallback() {
//	if(!IsRectEmpty(&mainViewRectangleToUpdate)) {
//		int param1 = ((mainViewRectangleToUpdate.left & 0xFFFF) << 16) | (mainViewRectangleToUpdate.top & 0xFFFF);
//		int param2 = ((mainViewRectangleToUpdate.right & 0xFFFF) << 16) | (mainViewRectangleToUpdate.bottom & 0xFFFF);
//		mainViewCallback(CALLBACK_TYPE_INVALIDATE,
//		                 param1,
//		                 param2);
//		SetRectEmpty(&mainViewRectangleToUpdate);
//	} else
		mainViewCallback(CALLBACK_TYPE_INVALIDATE,
		                 0,
		                 0);
}

void mainViewResizeCallback(int x, int y) {
    mainViewCallback(CALLBACK_TYPE_WINDOW_RESIZE, x, y);

    JNIEnv * jniEnv;
    int ret = (*java_machine)->GetEnv(java_machine, (void **) &jniEnv, JNI_VERSION_1_6);
    if(jniEnv) {
        ret = AndroidBitmap_getInfo(jniEnv, bitmapMainScreen, &androidBitmapInfo);
        if (ret < 0) {
            LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
        }
    }
}

// Must be called in the main thread
int openFileFromContentResolver(const TCHAR * fileURL, int writeAccess) {
    int result = -1;
    JNIEnv *jniEnv = getJNIEnvironment();
    if(jniEnv) {
        jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
        if(mainActivityClass) {
            jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "openFileFromContentResolver", "(Ljava/lang/String;I)I");
            jstring utfFileURL = (*jniEnv)->NewStringUTF(jniEnv, fileURL);
            result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, utfFileURL, writeAccess);
            (*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
        }
    }
    return result;
}
int openFileInFolderFromContentResolver(const TCHAR * filename, const TCHAR * folderURL, int writeAccess) {
    int result = -1;
    JNIEnv *jniEnv = getJNIEnvironment();
    if(jniEnv) {
        jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
        if(mainActivityClass) {
            jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "openFileInFolderFromContentResolver", "(Ljava/lang/String;Ljava/lang/String;I)I");
            jstring utfFilename = (*jniEnv)->NewStringUTF(jniEnv, filename);
            jstring utfFolderURL = (*jniEnv)->NewStringUTF(jniEnv, folderURL);
            result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, utfFilename, utfFolderURL, writeAccess);
            (*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
        }
    }
    return result;
}
int closeFileFromContentResolver(int fd) {
    int result = -1;
    JNIEnv *jniEnv = getJNIEnvironment();
    if(jniEnv) {
        jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
        if(mainActivityClass) {
            jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "closeFileFromContentResolver", "(I)I");
            result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, fd);
            (*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
        }
    }
    return result;
}

int showAlert(const TCHAR * messageText, int flags) {
    JNIEnv *jniEnv = getJNIEnvironment();
    if(jniEnv) {
        jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
        if(mainActivityClass) {
            jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "showAlert", "(Ljava/lang/String;)V");
            jstring messageUTF = (*jniEnv)->NewStringUTF(jniEnv, messageText);
            (*jniEnv)->CallVoidMethod(jniEnv, mainActivity, midStr, messageUTF);
            (*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
        }
    }
    return IDOK;
}

void sendMenuItemCommand(int menuItem) {
    JNIEnv *jniEnv = getJNIEnvironment();
    if(jniEnv) {
        jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
        if(mainActivityClass) {
            jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "sendMenuItemCommand", "(I)V");
            (*jniEnv)->CallVoidMethod(jniEnv, mainActivity, midStr, menuItem);
            (*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
        }
    }
}

TCHAR lastKMLFilename[MAX_PATH];

BOOL getFirstKMLFilenameForType(BYTE chipsetType) {
	JNIEnv *jniEnv = getJNIEnvironment();
	if(jniEnv) {
		jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
		if(mainActivityClass) {
			jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "getFirstKMLFilenameForType", "(C)I");
			int result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, (char)chipsetType);
			(*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
			return result ? TRUE : FALSE;
		}
	}
	return FALSE;
}


void clipboardCopyText(const TCHAR * text) {
    JNIEnv *jniEnv = getJNIEnvironment();
    if(jniEnv) {
        jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
        if(mainActivityClass) {
            jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "clipboardCopyText", "(Ljava/lang/String;)V");
            jint length = _tcslen(text);
            unsigned short * utf16String =  malloc(2 * (length + 1));
            for (int i = 0; i <= length; ++i)
                utf16String[i] = ((unsigned char *)text)[i];
            jstring messageUTF = (*jniEnv)->NewString(jniEnv, utf16String, length);
            (*jniEnv)->CallVoidMethod(jniEnv, mainActivity, midStr, messageUTF);
            (*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
            free(utf16String);
        }
    }
}
const TCHAR * clipboardPasteText() {
    JNIEnv *jniEnv = getJNIEnvironment();
    if(jniEnv) {
        jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
        if(mainActivityClass) {
            jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "clipboardPasteText", "()Ljava/lang/String;");
            jobject result = (*jniEnv)->CallObjectMethod(jniEnv, mainActivity, midStr);
            (*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
            if(result) {
                const jchar *strReturn = (*jniEnv)->GetStringChars(jniEnv, result, 0);
                jsize length = (*jniEnv)->GetStringLength(jniEnv, result);
                TCHAR * pasteText = (TCHAR *) GlobalAlloc(0, length + 2);
                for (int i = 0; i <= length; ++i)
                    pasteText[i] = strReturn[i] & 0xFF;
                pasteText[length] = 0;
                (*jniEnv)->ReleaseStringChars(jniEnv, result, strReturn);
                return pasteText;
            }
        }
    }
    return NULL;
}

void performHapticFeedback() {
    JNIEnv *jniEnv = getJNIEnvironment();
    if(jniEnv) {
        jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
        if(mainActivityClass) {
            jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "performHapticFeedback", "()V");
            (*jniEnv)->CallVoidMethod(jniEnv, mainActivity, midStr);
            (*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
        }
    }
}

void sendByteUdp(unsigned char byteSent) {
    JNIEnv *jniEnv = getJNIEnvironment();
    if(jniEnv) {
        jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
        if(mainActivityClass) {
            jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "sendByteUdp", "(I)V");
            (*jniEnv)->CallVoidMethod(jniEnv, mainActivity, midStr, (int)byteSent);
            (*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
        }
    }
}

void setKMLIcon(int imageWidth, int imageHeight, LPBYTE buffer, int bufferSize) {
    JNIEnv *jniEnv = getJNIEnvironment();
    if(jniEnv) {
        jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
        if(mainActivityClass) {
            jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "setKMLIcon", "(II[B)V");

            jbyteArray pixels = NULL;
            if(buffer) {
                pixels = (*jniEnv)->NewByteArray(jniEnv, bufferSize);
                (*jniEnv)->SetByteArrayRegion(jniEnv, pixels, 0, bufferSize, (jbyte *) buffer);
            }
            (*jniEnv)->CallVoidMethod(jniEnv, mainActivity, midStr, imageWidth, imageHeight, pixels);
            (*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
        }
    }
}

int openSerialPort(const TCHAR * serialPort) {
	int result = -1;
	JNIEnv *jniEnv = getJNIEnvironment();
	if(jniEnv) {
		jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
		if(mainActivityClass) {
			jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "openSerialPort", "(Ljava/lang/String;)I");
			jstring utfFileURL = (*jniEnv)->NewStringUTF(jniEnv, serialPort);
			result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, utfFileURL);
			(*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
		}
	}
	return result;
}

int closeSerialPort(int serialPortId) {
	int result = -1;
	JNIEnv *jniEnv = getJNIEnvironment();
	if(jniEnv) {
		jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
		if(mainActivityClass) {
			jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "closeSerialPort", "(I)I");
			result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, serialPortId);
			(*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
		}
	}
	return result;
}

int setSerialPortParameters(int serialPortId, int baudRate) {
	int result = -1;
	JNIEnv *jniEnv = getJNIEnvironment();
	if(jniEnv) {
		jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
		if(mainActivityClass) {
			jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "setSerialPortParameters", "(II)I");
			result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, serialPortId, baudRate);
			(*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
		}
	}
	return result;
}

int readSerialPort(int serialPortId, LPBYTE buffer, int nNumberOfBytesToRead) {
	int nNumberOfReadBytes = 0;
	JNIEnv *jniEnv = getJNIEnvironment();
	if(jniEnv && buffer && nNumberOfBytesToRead > 0) {
		jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
		if(mainActivityClass) {
			jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "readSerialPort", "(II)[B");
			jbyteArray readBytes = (jbyteArray)(*jniEnv)->CallObjectMethod(jniEnv, mainActivity, midStr, serialPortId, nNumberOfBytesToRead);
			nNumberOfReadBytes = (*jniEnv)->GetArrayLength(jniEnv, readBytes);
			jbyte* elements = (*jniEnv)->GetByteArrayElements(jniEnv, readBytes, NULL);
			if (elements) {
				for(int i = 0; i < nNumberOfReadBytes; i++)
					buffer[i] = elements[i];
				(*jniEnv)->ReleaseByteArrayElements(jniEnv, readBytes, elements, JNI_ABORT);
			}
			(*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
		}
	}
	return nNumberOfReadBytes;
}

int writeSerialPort(int serialPortId, LPBYTE buffer, int bufferSize) {
	int result = 0;
	JNIEnv *jniEnv = getJNIEnvironment();
	if(jniEnv) {
		jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
		if(mainActivityClass) {
			jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "writeSerialPort", "(I[B)I");

			jbyteArray javaBuffer = NULL;
			if(buffer) {
				javaBuffer = (*jniEnv)->NewByteArray(jniEnv, bufferSize);
				(*jniEnv)->SetByteArrayRegion(jniEnv, javaBuffer, 0, bufferSize, (jbyte *) buffer);
			}
			result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, serialPortId, javaBuffer);
			(*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
		}
	}
	return result;
}

int serialPortPurgeComm(int serialPortId, int dwFlags) {
	int result = 0;
	JNIEnv *jniEnv = getJNIEnvironment();
	if(jniEnv) {
		jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
		if(mainActivityClass) {
			jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "serialPortPurgeComm", "(II)I");
			result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, serialPortId);
			(*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
		}
	}
	return result;
}

int serialPortSetBreak(int serialPortId) {
	int result = 0;
	JNIEnv *jniEnv = getJNIEnvironment();
	if(jniEnv) {
		jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
		if(mainActivityClass) {
			jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "serialPortSetBreak", "(I)I");
			result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, serialPortId);
			(*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
		}
	}
	return result;
}

int serialPortClearBreak(int serialPortId) {
	int result = 0;
	JNIEnv *jniEnv = getJNIEnvironment();
	if(jniEnv) {
		jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
		if(mainActivityClass) {
			jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "serialPortClearBreak", "(I)I");
			result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, serialPortId);
			(*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
		}
	}
	return result;
}


JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_start(JNIEnv *env, jobject thisz, jobject assetMgr, jobject activity) {

    chooseCurrentKmlMode = ChooseKmlMode_UNKNOWN;
    szChosenCurrentKml[0] = '\0';

    mainActivity = (*env)->NewGlobalRef(env, activity);

    assetManager = AAssetManager_fromJava(env, assetMgr);



    // OnCreate
    InitializeCriticalSection(&csGDILock);
    InitializeCriticalSection(&csLcdLock);
    InitializeCriticalSection(&csKeyLock);
    InitializeCriticalSection(&csTLock);
    InitializeCriticalSection(&csSlowLock);
    InitializeCriticalSection(&csDbgLock);





    DWORD dwThreadId;

    // read emulator settings
    GetCurrentDirectory(ARRAYSIZEOF(szCurrentDirectory),szCurrentDirectory);
    //ReadSettings();
    //bRomWriteable = FALSE;

    _tcscpy(szCurrentDirectory, "");
    _tcscpy(szEmuDirectory, "assets/calculators/");
//    _tcscpy(szRomDirectory, "assets/calculators/");
//    _tcscpy(szPort2Filename, "");

    // initialization
    QueryPerformanceFrequency(&lFreq);		// init high resolution counter
    //QueryPerformanceCounter(&lAppStart);



    hWnd = CreateWindow();
    //hWindowDC = CreateCompatibleDC(NULL);
    hWindowDC = GetDC(hWnd);



    szCurrentKml[0] = 0;					// no KML file selected
    SetSpeed(bRealSpeed);					// set speed
	//MruInit(4);								// init MRU entries

    // create auto event handle
    hEventShutdn = CreateEvent(NULL,FALSE,FALSE,NULL);
    if (hEventShutdn == NULL)
    {
        AbortMessage(_T("Event creation failed."));
//		DestroyWindow(hWnd);
        return;
    }

    nState     = SM_RUN;					// init state must be <> nNextState
    nNextState = SM_INVALID;				// go into invalid state
    hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&WorkerThread, NULL, CREATE_SUSPENDED, &dwThreadId);
    if (hThread == NULL)
    {
        CloseHandle(hEventShutdn);			// close event handle
        AbortMessage(_T("Thread creation failed."));
//		DestroyWindow(hWnd);
        return;
    }

	idDdeInst = 0;							// initialize DDE server
	if (DdeInitialize(&idDdeInst,(PFNCALLBACK) &DdeCallback,
	                  APPCLASS_STANDARD |
	                  CBF_FAIL_EXECUTES | CBF_FAIL_ADVISES |
	                  CBF_SKIP_REGISTRATIONS | CBF_SKIP_UNREGISTRATIONS,0))
	{
		// TerminateThread(hThread, 0);		// kill emulation thread
		CloseHandle(hEventShutdn);			// close event handle
		AbortMessage(_T("Could not initialize server!"));
		//DestroyWindow(hWnd);
		return; // FALSE;
	}

	// init clipboard format and name service
	HSZ hszService, hszTopic;				// variables for DDE server
	uCF_HpObj = RegisterClipboardFormat(_T(CF_HPOBJ));
	hszService = DdeCreateStringHandle(idDdeInst,szAppName,0);
	hszTopic   = DdeCreateStringHandle(idDdeInst,szTopic,0);
	DdeNameService(idDdeInst,hszService,NULL,DNS_REGISTER);


    soundEnabled = soundAvailable = SoundOpen(uWaveDevId);					// open waveform-audio output device

    ResumeThread(hThread);					// start thread
    while (nState!=nNextState) Sleep(0);	// wait for thread initialized
}

JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_stop(JNIEnv *env, jobject thisz) {

    //if (hThread)
    SwitchToState(SM_RETURN);	// exit emulation thread

    ReleaseDC(hWnd, hWindowDC);
    DestroyWindow(hWnd);
	hWindowDC = NULL;						// hWindowDC isn't valid any more
	hWnd = NULL;

	// Prevent crash+++
	// FORTIFY: pthread_mutex_destroy called on a destroyed mutex (0x<sanitized>)
    // DeleteCriticalSection(&csGDILock);
	// DeleteCriticalSection(&csLcdLock);
	// DeleteCriticalSection(&csKeyLock);
	// DeleteCriticalSection(&csTLock);
	// DeleteCriticalSection(&csSlowLock);
	// DeleteCriticalSection(&csDbgLock);
	// Prevent crash---

    SoundClose();							// close waveform-audio output device
    soundEnabled = FALSE;

    if(bitmapMainScreen) {
        (*env)->DeleteGlobalRef(env, bitmapMainScreen);
        bitmapMainScreen = NULL;
    }
}


JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_changeBitmap(JNIEnv *env, jobject thisz, jobject bitmapMainScreen0) {

    if(bitmapMainScreen) {
        (*env)->DeleteGlobalRef(env, bitmapMainScreen);
        bitmapMainScreen = NULL;
    }

    bitmapMainScreen = (*env)->NewGlobalRef(env, bitmapMainScreen0);
    int ret = AndroidBitmap_getInfo(env, bitmapMainScreen, &androidBitmapInfo);
    if (ret < 0) {
        LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
    }
}

JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_draw(JNIEnv *env, jobject thisz) {
    draw();
}
JNIEXPORT jboolean JNICALL Java_org_emulator_calculator_NativeLib_buttonDown(JNIEnv *env, jobject thisz, jint x, jint y) {
    return buttonDown(x, y) ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_buttonUp(JNIEnv *env, jobject thisz, jint x, jint y) {
    buttonUp(x, y);
}
JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_keyDown(JNIEnv *env, jobject thisz, jint virtKey) {
    keyDown(virtKey);
}
JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_keyUp(JNIEnv *env, jobject thisz, jint virtKey) {
    keyUp(virtKey);
}



JNIEXPORT jboolean JNICALL Java_org_emulator_calculator_NativeLib_isDocumentAvailable(JNIEnv *env, jobject thisz) {
    return bDocumentAvail ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT jstring JNICALL Java_org_emulator_calculator_NativeLib_getCurrentFilename(JNIEnv *env, jobject thisz) {
    jstring result = (*env)->NewStringUTF(env, szCurrentFilename);
    return result;
}

JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_getCurrentModel(JNIEnv *env, jobject thisz) {
    return cCurrentRomType;
}

JNIEXPORT jboolean JNICALL Java_org_emulator_calculator_NativeLib_isBackup(JNIEnv *env, jobject thisz) {
    return (jboolean) (bBackup ? JNI_TRUE : JNI_FALSE);
}

JNIEXPORT jstring JNICALL Java_org_emulator_calculator_NativeLib_getKMLLog(JNIEnv *env, jobject thisz) {
    jstring result = (*env)->NewStringUTF(env, szKmlLog);
    return result;
}

JNIEXPORT jstring JNICALL Java_org_emulator_calculator_NativeLib_getKMLTitle(JNIEnv *env, jobject thisz) {
    jstring result = (*env)->NewStringUTF(env, szKmlTitle);
    return result;
}

JNIEXPORT jstring JNICALL Java_org_emulator_calculator_NativeLib_getCurrentKml(JNIEnv *env, jobject thisz) {
	jstring result = (*env)->NewStringUTF(env, szCurrentKml);
	return result;
}

JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_setCurrentKml(JNIEnv *env, jobject thisz, jstring currentKml) {
	const char *currentKmlUTF8 = (*env)->GetStringUTFChars(env, currentKml, NULL);
	_tcscpy(szCurrentKml, currentKmlUTF8);
	(*env)->ReleaseStringUTFChars(env, currentKml, currentKmlUTF8);
}

JNIEXPORT jstring JNICALL Java_org_emulator_calculator_NativeLib_getEmuDirectory(JNIEnv *env, jobject thisz) {
	jstring result = (*env)->NewStringUTF(env, szEmuDirectory);
	return result;
}

JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_setEmuDirectory(JNIEnv *env, jobject thisz, jstring emuDirectory) {
	const char *emuDirectoryUTF8 = (*env)->GetStringUTFChars(env, emuDirectory, NULL);
	_tcscpy(szEmuDirectory, emuDirectoryUTF8);
	//_tcscpy(szRomDirectory, emuDirectoryUTF8);
	(*env)->ReleaseStringUTFChars(env, emuDirectory, emuDirectoryUTF8);
}

JNIEXPORT jboolean JNICALL Java_org_emulator_calculator_NativeLib_getPort1Plugged(JNIEnv *env, jobject thisz) {
    return (jboolean) FALSE; //((Chipset.cards_status & PORT1_PRESENT) != 0);
}

JNIEXPORT jboolean JNICALL Java_org_emulator_calculator_NativeLib_getPort1Writable(JNIEnv *env, jobject thisz) {
    return (jboolean) FALSE; //((Chipset.cards_status & PORT1_WRITE) != 0);
}

JNIEXPORT jboolean JNICALL Java_org_emulator_calculator_NativeLib_getSoundEnabled(JNIEnv *env, jobject thisz) {
    return (jboolean) soundAvailable;
}

JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_getGlobalColor(JNIEnv *env, jobject thisz) {
    return (jint) dwTColor;
}

JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_getMacroState(JNIEnv *env, jobject thisz) {
    return nMacroState;
}


JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_onFileNew(JNIEnv *env, jobject thisz, jstring kmlFilename, jstring kmlFolder) {
    if (bDocumentAvail) {
        SwitchToState(SM_INVALID);
        if(bAutoSave) {
            SaveDocument();
        }
    }

    const char *filenameUTF8 = (*env)->GetStringUTFChars(env, kmlFilename, NULL);
    chooseCurrentKmlMode = ChooseKmlMode_FILE_NEW;
    _tcscpy(szChosenCurrentKml, filenameUTF8);
    (*env)->ReleaseStringUTFChars(env, kmlFilename, filenameUTF8);

	TCHAR * documentScheme = _T("document:");
	TCHAR * documentSchemeFound = _tcsstr(szChosenCurrentKml, documentScheme);
    if(kmlFolder) {
	    const char *kmlFolderUTF8 = (*env)->GetStringUTFChars(env, kmlFolder, NULL);
        // The folder URL is separated from the script filename and comes from the JSON settings in the state file.
	    _tcscpy(szEmuDirectory, kmlFolderUTF8);
	    //_tcscpy(szRomDirectory, kmlFolderUTF8);
	    (*env)->ReleaseStringUTFChars(env, kmlFolder, kmlFolderUTF8);
    } else if(documentSchemeFound) {
	    // Keep the compatibility by allowing to put the KML folder combined with the KML script filename with a document: scheme.
	    _tcscpy(szEmuDirectory, szChosenCurrentKml + _tcslen(documentScheme) * sizeof(TCHAR));
        TCHAR * filename = _tcschr(szEmuDirectory, _T('|'));
	    if(filename)
            *filename = _T('\0');
        //_tcscpy(szRomDirectory, szEmuDirectory);
    } else {
        _tcscpy(szEmuDirectory, "assets/calculators/");
        //_tcscpy(szRomDirectory, "assets/calculators/");
    }

    BOOL result = NewDocument();

    chooseCurrentKmlMode = ChooseKmlMode_UNKNOWN;

    if(result) {
        mainViewResizeCallback(nBackgroundW, nBackgroundH);
        draw();

        if (bStartupBackup) SaveBackup();        // make a RAM backup at startup

        if (pbyRom) SwitchToState(SM_RUN);
    }
    return result;
}
JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_onFileOpen(JNIEnv *env, jobject thisz, jstring stateFilename, jstring kmlFolder) {
    if (bDocumentAvail) {
        SwitchToState(SM_INVALID);
        if(bAutoSave) {
            SaveDocument();
        }
    }
    const char *stateFilenameUTF8 = (*env)->GetStringUTFChars(env, stateFilename, NULL);
    _tcscpy(szBufferFilename, stateFilenameUTF8);
	(*env)->ReleaseStringUTFChars(env, stateFilename, stateFilenameUTF8);

	chooseCurrentKmlMode = ChooseKmlMode_FILE_OPEN;
	if(kmlFolder) {
		const char *kmlFolderUTF8 = (*env)->GetStringUTFChars(env, kmlFolder, NULL);
		// The folder URL is separated from the script filename (not in the document: URL) and comes from the JSON settings in the state file.
		_tcscpy(szEmuDirectory, kmlFolderUTF8);
		//_tcscpy(szRomDirectory, kmlFolderUTF8);
		(*env)->ReleaseStringUTFChars(env, kmlFolder, kmlFolderUTF8);
		chooseCurrentKmlMode = ChooseKmlMode_FILE_OPEN_WITH_FOLDER;
	} else { //TODO asset folder does not exist with Emu42!!!
		// We are loading a KML script from the embedded asset folder inside the Android App.
		// We directly set the variable "szEmuDirectory"/"szRomDirectory" and "szCurrentAssetDirectory" with the KML folder
		// which contain the script and its dependencies like the includes, the images and the ROMs.
		_tcscpy(szEmuDirectory, "assets/calculators/");
		//_tcscpy(szRomDirectory, "assets/calculators/");
	}

	kmlFileNotFound = FALSE;
    lastKMLFilename[0] = '\0';
    BOOL result = OpenDocument(szBufferFilename);
    chooseCurrentKmlMode = ChooseKmlMode_UNKNOWN;
    mainViewResizeCallback(nBackgroundW, nBackgroundH);
    if(result) {
        if (pbyRom) SwitchToState(SM_RUN);
    }
    draw();
    if(securityExceptionOccured) {
        securityExceptionOccured = FALSE;
        result = -2;
    }
    if(kmlFileNotFound) {
	    kmlFileNotFound = FALSE;
	    result = -3;
    }
    return result;
}
JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_onFileSave(JNIEnv *env, jobject thisz) {
    // szBufferFilename must be set before calling that!!!
    BOOL result = FALSE;
    if (bDocumentAvail)
    {
        SwitchToState(SM_INVALID);
        result = SaveDocument();
        //SaveMapViewToFile(pbyPort2);
        SwitchToState(SM_RUN);
    }
    return result;
}
JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_onFileSaveAs(JNIEnv *env, jobject thisz, jstring newStateFilename) {
    const char *newStateFilenameUTF8 = (*env)->GetStringUTFChars(env, newStateFilename , NULL) ;

    BOOL result = FALSE;
    if (bDocumentAvail)
    {
        SwitchToState(SM_INVALID);
        _tcscpy(szBufferFilename, newStateFilenameUTF8);
        result = SaveDocumentAs(szBufferFilename);
        //SaveMapViewToFile(pbyPort2);
        SwitchToState(SM_RUN);
    }

    (*env)->ReleaseStringUTFChars(env, newStateFilename, newStateFilenameUTF8);
    return result;
}

JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_onFileClose(JNIEnv *env, jobject thisz) {
    if (bDocumentAvail)
    {
        SwitchToState(SM_INVALID);
        if(bAutoSave)
            SaveDocument();
        ResetDocument();
        SetWindowTitle(NULL);
        szKmlTitle[0] = '\0';
        mainViewResizeCallback(nBackgroundW, nBackgroundH);
        draw();
        return TRUE;
    }
    return FALSE;
}

JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_onObjectLoad(JNIEnv *env, jobject thisz, jstring filename) {
    const char *filenameUTF8 = (*env)->GetStringUTFChars(env, filename , NULL) ;

    SuspendDebugger();						// suspend debugger
    bDbgAutoStateCtrl = FALSE;				// disable automatic debugger state control

    if (!(Chipset.IORamM[DSPCTL]&DON))		// calculator off, turn on
    {
        KeyboardEvent(TRUE,0,0x8000);
        Sleep(dwWakeupDelay);
        KeyboardEvent(FALSE,0,0x8000);

        // wait for sleep mode
        while (Chipset.Shutdn == FALSE) Sleep(0);
    }

    if (nState != SM_RUN)
    {
        InfoMessage(_T("The emulator must be running to load an object."));
        goto cancel;
    }

    if (WaitForSleepState())				// wait for cpu SHUTDN then sleep state
    {
        InfoMessage(_T("The emulator is busy."));
        goto cancel;
    }

    _ASSERT(nState == SM_SLEEP);

//    if (bLoadObjectWarning)
//    {
//        UINT uReply = YesNoCancelMessage(
//                _T("Warning: Trying to load an object while the emulator is busy\n")
//                _T("will certainly result in a memory lost. Before loading an object\n")
//                _T("you should be sure that the calculator is in idle state.\n")
//                _T("Do you want to see this warning next time you try to load an object?"),0);
//        switch (uReply)
//        {
//            case IDYES:
//                break;
//            case IDNO:
//                bLoadObjectWarning = FALSE;
//                break;
//            case IDCANCEL:
//                SwitchToState(SM_RUN);
//                goto cancel;
//        }
//    }

//        if (!GetLoadObjectFilename(_T(HP_FILTER),_T("HP")))
//        {
//            SwitchToState(SM_RUN);
//            goto cancel;
//        }

    if (!LoadObject(filenameUTF8))
    {
        SwitchToState(SM_RUN);
        goto cancel;
    }

    SwitchToState(SM_RUN);				// run state
    while (nState!=nNextState) Sleep(0);
    _ASSERT(nState == SM_RUN);
    KeyboardEvent(TRUE,0,0x8000);
    Sleep(dwWakeupDelay);
    KeyboardEvent(FALSE,0,0x8000);
    while (Chipset.Shutdn == FALSE) Sleep(0);

    cancel:
    bDbgAutoStateCtrl = TRUE;				// enable automatic debugger state control
    ResumeDebugger();

    (*env)->ReleaseStringUTFChars(env, filename, filenameUTF8);

    return TRUE;
}

JNIEXPORT jobjectArray JNICALL Java_org_emulator_calculator_NativeLib_getObjectsToSave(JNIEnv *env, jobject thisz) {

//    if (nState != SM_RUN)
//    {
//        InfoMessage(_T("The emulator must be running to save an object."));
//        return 0;
//    }
//
//    if (WaitForSleepState())				// wait for cpu SHUTDN then sleep state
//    {
//        InfoMessage(_T("The emulator is busy."));
//        return 0;
//    }
//
//    _ASSERT(nState == SM_SLEEP);
//
//    labels[0] = 0;
//    if (cCurrentRomType == 'N') { // HP32SII
//        currentDialogBoxMode = DialogBoxMode_GET_USRPRG32;
//        OnSelectProgram32();
//    } else if(cCurrentRomType == 'D') { // HP42S
//        currentDialogBoxMode = DialogBoxMode_GET_USRPRG42;
//        OnSelectProgram42();
//    }
//    currentDialogBoxMode = DialogBoxMode_UNKNOWN;
//
//    int labelCount = 0;
//    if(labels[0]) {
//        int i = 0;
//        while(i < MAX_LABEL_SIZE && labels[i]) {
//            if(labels[i] == 9)
//                labelCount++;
//            i++;
//        }
//    }

//    jobjectArray objectArray = (jobjectArray)(*env)->NewObjectArray(env,
//       labelCount,
//       (*env)->FindClass(env, "java/lang/String"),
//       (*env)->NewStringUTF(env, ""));
//
//    if(labelCount) {
//        TCHAR * label = labels;
//        int l = 0;
//        int i = 0;
//        while(i < MAX_LABEL_SIZE && labels[i]) {
//            if(labels[i] == 9) {
//                labels[i] = 0;
//                (*env)->SetObjectArrayElement(env, objectArray,
//                        l, (*env)->NewStringUTF(env, label));
//                label = &labels[i + 1];
//                l++;
//            }
//            i++;
//        }
//    }
//
//    SwitchToState(SM_RUN);
//
//    return objectArray;

    return NULL;
}

JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_onObjectSave(JNIEnv *env, jobject thisz, jstring filename, jbooleanArray objectsToSaveItemChecked) {
    //OnObjectSave();

    const char *filenameUTF8 = (*env)->GetStringUTFChars(env, filename , NULL);

    if (nState != SM_RUN)
    {
        InfoMessage(_T("The emulator must be running to save an object."));
        return 0;
    }

    if (WaitForSleepState())				// wait for cpu SHUTDN then sleep state
    {
        InfoMessage(_T("The emulator is busy."));
        return 0;
    }

    _ASSERT(nState == SM_SLEEP);

//    if (GetSaveObjectFilename(_T(HP_FILTER),_T("HP")))
//    {
        SaveObject(filenameUTF8);
//    }

    SwitchToState(SM_RUN);

    (*env)->ReleaseStringUTFChars(env, filename, filenameUTF8);

    return TRUE;
}

JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_onViewCopy(JNIEnv *env, jobject thisz, jobject bitmapScreen) {

    AndroidBitmapInfo bitmapScreenInfo;
    int ret = AndroidBitmap_getInfo(env, bitmapScreen, &bitmapScreenInfo);
    if (ret < 0) {
        LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
        return;
    }

    void * pixelsDestination;
    if ((ret = AndroidBitmap_lockPixels(env, bitmapScreen, &pixelsDestination)) < 0) {
        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
    }

    // DIB bitmap
    #define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4)
    #define PALVERSION       0x300

    HDC hSrcDC;
    BITMAP bm;
    LPBITMAPINFOHEADER lpbi;
    PLOGPALETTE ppal;
    HBITMAP hBmp;
    HDC hBmpDC;
    HANDLE hClipObj;
    WORD wBits;
    DWORD dwLen, dwSizeImage;
    INT nxO, nyO, nxSize, nySize;

    GetSizeLcdBitmap(&nxSize,&nySize); // get LCD size

    nxO = nyO = 0;					// origin in HDC
    hSrcDC = hLcdDC;				// use display HDC as source


//	TCHAR cBuffer[256];				// temp. buffer for text
//	// get text display string
//	GetLcdNumber(cBuffer);
//	dwLen = (lstrlen(cBuffer) + 1) * sizeof(cBuffer[0]);
//	// memory allocation for clipboard data
//	if ((hClipObj = GlobalAlloc(GMEM_MOVEABLE, dwLen)) != NULL)
//	{
//		LPTSTR szText = (LPTSTR) GlobalLock(hClipObj);
//		lstrcpy(szText, cBuffer);
//#if defined _UNICODE
//		SetClipboardData(CF_UNICODETEXT, hClipObj);
//#else
//		SetClipboardData(CF_TEXT, hClipObj);
//#endif
//		GlobalUnlock(hClipObj);
//	}

    hBmp = CreateCompatibleBitmap(hSrcDC,nxSize,nySize);
    hBmpDC = CreateCompatibleDC(hSrcDC);
    hBmp = (HBITMAP) SelectObject(hBmpDC,hBmp);
    EnterCriticalSection(&csGDILock); // solving NT GDI problems
    {
        // copy display area
        BitBlt(hBmpDC,0,0,nxSize,nySize,hSrcDC,nxO,nyO,SRCCOPY);
        GdiFlush();
    }
    LeaveCriticalSection(&csGDILock);
    hBmp = (HBITMAP) SelectObject(hBmpDC,hBmp);

    // fill BITMAP structure for size information
    GetObject((HANDLE) hBmp, sizeof(bm), &bm);

    wBits = bm.bmPlanes * bm.bmBitsPixel;
    // make sure bits per pixel is valid
    if (wBits <= 1)
        wBits = 1;
    else if (wBits <= 4)
        wBits = 4;
    else if (wBits <= 8)
        wBits = 8;
    else // if greater than 8-bit, force to 24-bit
        wBits = 24;

    dwSizeImage = WIDTHBYTES((DWORD)bm.bmWidth * wBits) * bm.bmHeight;

    // calculate memory size to store CF_DIB data
    dwLen = sizeof(BITMAPINFOHEADER) + dwSizeImage;
    if (wBits != 24)				// a 24 bitcount DIB has no color table
    {
        // add size for color table
        dwLen += (DWORD) (1 << wBits) * sizeof(RGBQUAD);
    }





    size_t strideSource = (size_t)(4 * ((hBmp->bitmapInfoHeader->biWidth * hBmp->bitmapInfoHeader->biBitCount + 31) / 32));
    size_t strideDestination = bitmapScreenInfo.stride;
    VOID * bitmapBitsSource = (VOID *)hBmp->bitmapBits;
    VOID * bitmapBitsDestination = pixelsDestination + (hBmp->bitmapInfoHeader->biHeight - 1) * strideDestination;
    for(int y = 0; y < hBmp->bitmapInfoHeader->biHeight; y++) {
        memcpy(bitmapBitsDestination, bitmapBitsSource, strideSource);
        bitmapBitsSource += strideSource;
        bitmapBitsDestination -= strideDestination;
    }


    DeleteDC(hBmpDC);
    DeleteObject(hBmp);
    #undef WIDTHBYTES
    #undef PALVERSION


    AndroidBitmap_unlockPixels(env, bitmapScreen);
}

JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_onStackCopyVisible(JNIEnv *env, jobject thisz) {
	TCHAR cBuffer[256];				// temp. buffer for text
	HANDLE hClipObj;
	DWORD dwLen;

	// get text display string
	GetLcdNumber(cBuffer);
	dwLen = (lstrlen(cBuffer) + 1) * sizeof(cBuffer[0]);
	// memory allocation for clipboard data
	if ((hClipObj = GlobalAlloc(GMEM_MOVEABLE, dwLen)) != NULL)
	{
		LPTSTR szText = (LPTSTR) GlobalLock(hClipObj);
		lstrcpy(szText, cBuffer);
#if defined _UNICODE
		SetClipboardData(CF_UNICODETEXT, hClipObj);
#else
		SetClipboardData(CF_TEXT, hClipObj);
#endif
		GlobalUnlock(hClipObj);
	}
}

JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_onStackCopy(JNIEnv *env, jobject thisz) {
    OnStackCopy();
}

JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_onStackPaste(JNIEnv *env, jobject thisz) {
    //TODO Memory leak -> No GlobalFree of the paste data!!!!
    OnStackPaste();
}

JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_onViewReset(JNIEnv *env, jobject thisz) {
    if (nState != SM_RUN)
        return;
    SwitchToState(SM_SLEEP);
    CpuReset();							// register setting after Cpu Reset
    SwitchToState(SM_RUN);
}

JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_onViewScript(JNIEnv *env, jobject thisz, jstring kmlFilename, jstring kmlFolder) {

    TCHAR szKmlFile[MAX_PATH];
    SwitchToState(SM_INVALID);

    // make a copy of the current KML script file name
    lstrcpyn(szKmlFile,szCurrentKml,ARRAYSIZEOF(szKmlFile));

    const char * filenameUTF8 = (*env)->GetStringUTFChars(env, kmlFilename , NULL) ;
    _tcscpy(szCurrentKml, filenameUTF8);
    (*env)->ReleaseStringUTFChars(env, kmlFilename, filenameUTF8);

	if(kmlFolder) {
		const char * kmlFolderUTF8 = (*env)->GetStringUTFChars(env, kmlFolder, NULL);
		// The folder URL is separated from the script filename and comes from the JSON settings in the state file.
		_tcscpy(szEmuDirectory, kmlFolderUTF8);
		//_tcscpy(szRomDirectory, kmlFolderUTF8);
		(*env)->ReleaseStringUTFChars(env, kmlFolder, kmlFolderUTF8);
	} else {
		// We are loading a KML script from the embedded asset folder inside the Android App.
		// We directly set the variable "szEmuDirectory"/"szRomDirectory" and "szCurrentAssetDirectory" with the KML folder
		// which contain the script and its dependencies like the includes, the images and the ROMs.
		_tcscpy(szEmuDirectory, "assets/calculators/");
		//_tcscpy(szRomDirectory, "assets/calculators/");
	}

	chooseCurrentKmlMode = ChooseKmlMode_CHANGE_KML;

	BOOL bSucc = InitKML(szCurrentKml, FALSE);

    if(!bSucc) {
        // restore KML script file name
        lstrcpyn(szCurrentKml,szKmlFile,ARRAYSIZEOF(szCurrentKml));

        _tcsncpy(szKmlLogBackup, szKmlLog, sizeof(szKmlLog) / sizeof(TCHAR));

        // try to restore old KML script
        bSucc = InitKML(szCurrentKml, FALSE);

        _tcsncpy(szKmlLog, szKmlLogBackup, sizeof(szKmlLog) / sizeof(TCHAR));
    }
    chooseCurrentKmlMode = ChooseKmlMode_UNKNOWN;

    if(bSucc) {
        mainViewResizeCallback(nBackgroundW, nBackgroundH);
        draw();
        if (Chipset.wRomCrc != wRomCrc)		// ROM changed
        {
            CpuReset();
            Chipset.Shutdn = FALSE;			// automatic restart

            Chipset.wRomCrc = wRomCrc;		// update current ROM fingerprint
        }
        if (pbyRom) SwitchToState(SM_RUN);	// continue emulation
    } else {
        ResetDocument();					// close document
        SetWindowTitle(NULL);
    }

    return bSucc;
}

JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_onBackupSave(JNIEnv *env, jobject thisz) {
    UINT nOldState;
    if (pbyRom == NULL) return;
    nOldState = SwitchToState(SM_INVALID);
    SaveBackup();
    SwitchToState(nOldState);
}

JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_onBackupRestore(JNIEnv *env, jobject thisz) {
    SwitchToState(SM_INVALID);
    RestoreBackup();
    if (pbyRom) SwitchToState(SM_RUN);
}

JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_onBackupDelete(JNIEnv *env, jobject thisz) {
    ResetBackup();
}

JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_onToolMacroNew(JNIEnv *env, jobject thisz, jstring filename) {
    const char *filenameUTF8 = (*env)->GetStringUTFChars(env, filename , NULL);
    _tcscpy(getSaveObjectFilenameResult, filenameUTF8);
    (*env)->ReleaseStringUTFChars(env, filename, filenameUTF8);
    currentDialogBoxMode = DialogBoxMode_SAVE_MACRO;
    OnToolMacroNew();
    getSaveObjectFilenameResult[0] = 0;
    currentDialogBoxMode = DialogBoxMode_UNKNOWN;
}

JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_onToolMacroPlay(JNIEnv *env, jobject thisz, jstring filename) {
    const char *filenameUTF8 = (*env)->GetStringUTFChars(env, filename , NULL);
    _tcscpy(getSaveObjectFilenameResult, filenameUTF8);
    (*env)->ReleaseStringUTFChars(env, filename, filenameUTF8);
    currentDialogBoxMode = DialogBoxMode_OPEN_MACRO;
    OnToolMacroPlay();
    getSaveObjectFilenameResult[0] = 0;
    currentDialogBoxMode = DialogBoxMode_UNKNOWN;
}

JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_onToolMacroStop(JNIEnv *env, jobject thisz) {
    OnToolMacroStop();
}


JNIEXPORT jboolean JNICALL Java_org_emulator_calculator_NativeLib_onLoadFlashROM(JNIEnv *env, jobject thisz, jstring filename) {
	return JNI_FALSE;
}

JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_setConfiguration(JNIEnv *env, jobject thisz, jstring key, jint isDynamic, jint intValue1, jint intValue2, jstring stringValue) {
    const char *configKey = (*env)->GetStringUTFChars(env, key, NULL) ;
    const char *configStringValue = stringValue ? (*env)->GetStringUTFChars(env, stringValue, NULL) : NULL;

	//LOGE("NativeLib_setConfiguration(%s, %d, %d, %s)", configKey, intValue1, intValue2, configStringValue);

	bAutoSave = FALSE;
    bAutoSaveOnExit = FALSE;
    bLoadObjectWarning = FALSE;
    bAlwaysDisplayLog = TRUE;

    if(_tcscmp(_T("settings_realspeed"), configKey) == 0) {
        bRealSpeed = (BOOL) intValue1;
        SetSpeed(bRealSpeed);			// set speed
    } else if(_tcscmp(_T("settings_sound_volume"), configKey) == 0) {
        dwWaveVol = (DWORD)intValue1;
        if(soundEnabled && intValue1 == 0) {
            SoundClose();
            soundEnabled = FALSE;
        } else if(!soundEnabled && intValue1 > 0) {
            SoundOpen(uWaveDevId);
            soundEnabled = TRUE;
        }
    } else if(_tcscmp(_T("settings_macro"), configKey) == 0) {
        bMacroRealSpeed = (BOOL)intValue1;
        nMacroTimeout = 500 - intValue2;
    }

    if(configKey)
        (*env)->ReleaseStringUTFChars(env, key, configKey);
    if(configStringValue)
        (*env)->ReleaseStringUTFChars(env, stringValue, configStringValue);
}

JNIEXPORT jboolean JNICALL Java_org_emulator_calculator_NativeLib_isPortExtensionPossible(JNIEnv *env, jobject thisz) {
    return (cCurrentRomType=='S' || cCurrentRomType=='G' || cCurrentRomType==0 ? JNI_TRUE : JNI_FALSE);
}

JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_getState(JNIEnv *env, jobject thisz) {
    return nState;
}

JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_getScreenPositionX(JNIEnv *env, jobject thisz) {
    return nLcdX - nBackgroundX;
}
JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_getScreenPositionY(JNIEnv *env, jobject thisz) {
    return nLcdY - nBackgroundY;
}
JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_getScreenWidth(JNIEnv *env, jobject thisz) {
    INT nxSize,nySize;
    GetSizeLcdBitmap(&nxSize,&nySize);	// get LCD size
    return nxSize; //*nLcdZoom;
}
JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_getScreenHeight(JNIEnv *env, jobject thisz) {
    INT nxSize,nySize;
    GetSizeLcdBitmap(&nxSize,&nySize);	// get LCD size
    return nySize; //*nLcdZoom;
}
JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_getScreenWidthNative(JNIEnv *env, jobject thisz) {
	INT nxSize,nySize;
	GetSizeLcdBitmap(&nxSize,&nySize);	// get LCD size
	return nxSize / nLcdZoom;
}
JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_getScreenHeightNative(JNIEnv *env, jobject thisz) {
	INT nxSize,nySize;
	GetSizeLcdBitmap(&nxSize,&nySize);	// get LCD size
	return nySize / nLcdZoom;
}
JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_getLCDBackgroundColor(JNIEnv *env, jobject thisz) {
	if (hMainDC) {
		COLORREF brushColor = GetPixel(hMainDC, nLcdX, nLcdY);
		return ((brushColor & 0xFF0000) >> 16) | ((brushColor & 0xFF) << 16) | (brushColor & 0xFF00);
	}
	return -1;
}

JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_commEvent(JNIEnv *env, jclass clazz, jint commId, jint eventMask) {
	commEvent(commId, eventMask);
}