/**
@file jni_dlm.cpp
Swype Connected SDK (Android Connected SDK - AC-SDK)
(c) Copyright 2014 Nuance Communications, Inc All Rights Reserved
*/

#include <jni.h>
#include <string.h>
#include "et9api.h"
#include "log.h"

#define C_DECL extern "C"

struct JXT9DlmDB {
    JNIEnv* env;
    jobject callbackRef;
    jmethodID asyncEventMethod;
    jmethodID cacheFullMethod;
    jmethodID firstEventMethod;

    JXT9DlmDB() : env(NULL) {
    }
    ~JXT9DlmDB() {
        if (env != NULL) {
            if (callbackRef != NULL) {
                env->DeleteGlobalRef(callbackRef);
            }
            env = NULL;
        }
    }
};

#ifdef ET9_ALPHABETIC_MODULE
#undef API
#define API(fun) Java_com_nuance_dlm_ACAlphaInput_##fun

extern "C" ET9AWLingInfo* getAlphaLingInfo();
/**
* getAlphaLingInfo()
*
* This should return the current ET9LingInfo for the current ling info
*
*/

 const int MAX_BUFFER = 10240; // Arbitrary 10K
 static JXT9DlmDB g_AlphaDlmDB_onEventCallback;
 static volatile ET9U8 g_alphaEventBuffer[MAX_BUFFER];
 static volatile int g_alphaCurrentPosition = 0;

ET9STATUS DLMAlphaEventHandlerCallback(void* const pEventHandlerInfo, ET9U8 const * const pbBuf,
        const ET9U32 dwBufLen) {

    //LOGV("DLMAlphaEventHandlerCallback Type: %d, length: %d", (dwBufLen > 0) ? pbBuf[0] : -1, dwBufLen);
    JXT9DlmDB* dlmDB = (JXT9DlmDB*)pEventHandlerInfo;
    if (!dlmDB || !dlmDB->env) {
        return ET9STATUS_ERROR;
    }

    if (g_alphaCurrentPosition == 0) {
        dlmDB->env->CallVoidMethod(dlmDB->callbackRef, dlmDB->firstEventMethod);
    }
    if (MAX_BUFFER < (g_alphaCurrentPosition + dwBufLen)) {
        //LOGE("DLMAlphaEventHandlerCallback cache full!");
        dlmDB->env->CallVoidMethod(dlmDB->callbackRef, dlmDB->cacheFullMethod);
    }
    if (MAX_BUFFER < (g_alphaCurrentPosition + dwBufLen)) {
         LOGE("DLMAlphaEventHandlerCallback cache not cleared!  Sending events asynchronously...");
         // first send the buffer
         jbyteArray events = dlmDB->env->NewByteArray(g_alphaCurrentPosition);
         dlmDB->env->SetByteArrayRegion(events, 0, g_alphaCurrentPosition, (jbyte*)g_alphaEventBuffer);
         dlmDB->env->CallVoidMethod(dlmDB->callbackRef, dlmDB->asyncEventMethod,
             events, 0); // TODO: ET9_SYNC_IsHighPriorityEvent(pbBuf, dwBufLen));
         dlmDB->env->DeleteLocalRef(events);

         // reset the current pointer
         g_alphaCurrentPosition = 0;

         // send the buffer from this callback
         events = dlmDB->env->NewByteArray(dwBufLen);
         dlmDB->env->SetByteArrayRegion(events, 0, dwBufLen, (jbyte*)g_alphaEventBuffer);
         dlmDB->env->CallVoidMethod(dlmDB->callbackRef, dlmDB->asyncEventMethod,
             events, ET9_SYNC_IsHighPriorityEvent(pbBuf, dwBufLen));
         dlmDB->env->DeleteLocalRef(events);
     } else {
         // store the event
         memcpy((void*)g_alphaEventBuffer + g_alphaCurrentPosition, pbBuf, dwBufLen);
         g_alphaCurrentPosition += dwBufLen;
    }

    return ET9STATUS_NONE;
}

C_DECL jbyteArray API(acAlphaGetCachedEvents)(JNIEnv* env, jobject thiz) {
    jbyteArray events = env->NewByteArray(g_alphaCurrentPosition);
    env->SetByteArrayRegion(events, 0, g_alphaCurrentPosition, (jbyte*)g_alphaEventBuffer);
    g_alphaCurrentPosition = 0;

    return events;
}

C_DECL jint API(acAlphaExportAsEvent)(JNIEnv* env, jobject thiz, jboolean usingCategory, jint category) {
    //LOGV("acAlphaExportAsEvent");
    ET9AWLingInfo* info = getAlphaLingInfo();
    if (info == NULL) {
        return -1;
    }
    return ET9AWDLMExportAsEvents(info, (ET9BOOL)usingCategory, (ET9U16)category);
}

C_DECL jint API(acAlphaRegisterEventHandlerCallback)(JNIEnv* env, jobject thiz) {
    //LOGV("acAlphaRegisterEventHandlerCallback env: %x", &env);
    ET9AWLingInfo* info = getAlphaLingInfo();

    if (g_AlphaDlmDB_onEventCallback.env == NULL) {
        g_AlphaDlmDB_onEventCallback.env = env;
    }
    if (info == NULL || g_AlphaDlmDB_onEventCallback.env == NULL) {
        return -1;
    }

    if (g_AlphaDlmDB_onEventCallback.callbackRef != NULL) {
        g_AlphaDlmDB_onEventCallback.env->DeleteGlobalRef(g_AlphaDlmDB_onEventCallback.callbackRef);
    }
    g_AlphaDlmDB_onEventCallback.callbackRef = env->NewGlobalRef(thiz);
    jclass c = env->GetObjectClass(thiz);
	g_AlphaDlmDB_onEventCallback.asyncEventMethod = env->GetMethodID(c, "onEventCallback", "([BZ)V");
    g_AlphaDlmDB_onEventCallback.cacheFullMethod = env->GetMethodID(c, "onEventCacheFull", "()V");
    g_AlphaDlmDB_onEventCallback.firstEventMethod = env->GetMethodID(c, "onFirstCachedEvent", "()V");

    return ET9AWDLMRegisterForEvents(info, &DLMAlphaEventHandlerCallback, (void*)&g_AlphaDlmDB_onEventCallback);
}

C_DECL jint API(acAlphaProcessEvent)(JNIEnv* env, jobject thiz, jbyteArray jevent) {
    //LOGV("acAlphaProcessEvent");
    ET9AWLingInfo* info = getAlphaLingInfo();
    if (info == NULL) {
        return -1;
    }
    jsize len = env->GetArrayLength(jevent);
    jbyte* event = env->GetByteArrayElements(jevent, NULL);

    ET9STATUS status = ET9AWDLMHandleEvents(info, (ET9U8*)event, (ET9U16) len);

    env->ReleaseByteArrayElements(jevent, event, JNI_ABORT);

    return status;
}

C_DECL jint API(acAlphaDeleteCategory)(JNIEnv* env, jobject thiz, jint category) {
    //LOGV("acAlphaDeleteCategory");
    ET9AWLingInfo* info = getAlphaLingInfo();
    if (info == NULL) {
        return -1;
    }
    return ET9AWDLMDeleteCategory(info, (ET9U16)category);
}

C_DECL jint API(acAlphaDeleteCategoryLanguage)(JNIEnv* env, jobject thiz, jint category, jint languageId) {
    //LOGV("acAlphaDeleteCategoryLanguage");
    ET9AWLingInfo* info = getAlphaLingInfo();
    if (info == NULL) {
        return -1;
    }
    return ET9AWDLMDeleteCategoryLanguage(info, (ET9U16)category, (ET9U16)languageId);
}

static bool acCopyAppPredictionContext(JNIEnv* env, jbyteArray jcontext, ET9AW_ApplicationContextItemInfo& appContextItemInfo) {
    jsize len;
    if (jcontext == NULL || (len = env->GetArrayLength(jcontext)) == 0) {
        return false;
    }

    if (len > ET9AW_MAX_APPLICATION_CONTEXT_SIZE) {
        LOGE("appContext size %d > %d", len, ET9AW_MAX_APPLICATION_CONTEXT_SIZE);
        return false;
    }

    jbyte* context = env->GetByteArrayElements(jcontext, NULL);
    for (int i = 0; i < len; i++) {
        appContextItemInfo.pbData[i] = context[i];
    }
    appContextItemInfo.nSize = len;

    env->ReleaseByteArrayElements(jcontext, context, 0);
    return true;
}

ET9AW_ApplicationContextInfo* pSavedAppContextInfo = NULL;
ET9AW_ApplicationContextInfo g_appContextInfo;
ET9BOOL savedLearningScanAction = false;
ET9BOOL savedLearningUserAction = false;
ET9BOOL savedAskForLanguageDiff = false;
ET9U8 savedDelayedReorderScanAction = 0;
ET9U8 savedDelayedReorderUserImplicitAction = 0;
ET9U8 savedDelayedReorderUserExplicitAction = 0;


C_DECL jint API(acAlphaScanSessionBegin)(JNIEnv* env, jobject thiz,
        jbyteArray context1, jbyteArray context2, jbyteArray context3,
        jint userAction, jint scanAction) {

    ET9AWLingInfo* info = getAlphaLingInfo();
    if (info == NULL) {
        return -1;
    }

    ET9AWGetApplicationContext(info, pSavedAppContextInfo);
    ET9AWGetExplicitLearning(info, (ET9BOOL*) &savedLearningUserAction, (ET9BOOL*) &savedLearningScanAction, (ET9BOOL*) &savedAskForLanguageDiff);
    ET9AWGetUDBDelayedReorder(info, (ET9U8*) &savedDelayedReorderUserImplicitAction, (ET9U8*) &savedDelayedReorderUserExplicitAction, (ET9U8*) &savedDelayedReorderScanAction);

    g_appContextInfo.nCount = 0;
    g_appContextInfo.bPlainUserCategoryIsActive = 1;

    if (acCopyAppPredictionContext(env, context1, g_appContextInfo.pContexts[g_appContextInfo.nCount])) {
        g_appContextInfo.nCount++;

        if (acCopyAppPredictionContext(env, context2, g_appContextInfo.pContexts[g_appContextInfo.nCount])) {
            g_appContextInfo.nCount++;
        }
        if (acCopyAppPredictionContext(env, context3, g_appContextInfo.pContexts[g_appContextInfo.nCount])) {
            g_appContextInfo.nCount++;
        }
        ET9AWSetApplicationContext(info, &g_appContextInfo);
    } else {
        ET9AWSetApplicationContext(info, NULL);
    }
    ET9AWSetUDBDelayedReorder(info, (ET9U8) userAction, (ET9U8) userAction, (ET9U8) scanAction);
    ET9AWSetExplicitLearning(info, false, false, false);

    return ET9STATUS_NONE;
}

C_DECL jint API(acAlphaScanSessionEnd)(JNIEnv* env, jobject thiz) {
    ET9AWLingInfo* info = getAlphaLingInfo();
    if (info == NULL) {
        return -1;
    }

    ET9AWSetExplicitLearning(info, savedLearningUserAction, savedLearningScanAction, savedAskForLanguageDiff);
    ET9AWSetUDBDelayedReorder(info, (ET9U8) savedDelayedReorderUserImplicitAction, (ET9U8) savedDelayedReorderUserExplicitAction, (ET9U8) savedDelayedReorderScanAction);
    ET9AWSetApplicationContext(info, pSavedAppContextInfo);
}

C_DECL jint API(acAlphaScanBuffer)(JNIEnv* env, jobject thiz, jcharArray charArray,
        jint len, jint startPos, jint endPos, jint languageId, jint wordQuality,
        bool sentenceBased, bool rescanning) {
    //LOGV("acAlphaScanBuffer");
    ET9AWLingInfo* info = getAlphaLingInfo();
    if (info == NULL) {
        return -1;
    }
    //jsize len = env->GetArrayLength(jevent);
    jchar* buffer = env->GetCharArrayElements(charArray, NULL);

    ET9STATUS status = ET9AWDLMScanBuf(info, buffer, len, startPos, endPos,
            (ET9U16)languageId, wordQuality != 0, sentenceBased, rescanning, true);

    env->ReleaseCharArrayElements(charArray, buffer, JNI_ABORT);

    return status;
}

C_DECL jstring API(acAlphaLegacySecretKey)(JNIEnv* env, jobject thiz) {
    const char *cStr = "75DE491A38FD348E06C1B0313A97CB00";
    return env->NewStringUTF(cStr);
}

#endif


#ifdef ET9_KOREAN_MODULE
#undef API
#define API(fun) Java_com_nuance_dlm_ACKoreanInput_##fun

extern "C" ET9KLingInfo* getKoreanLingInfo();

static JXT9DlmDB g_KoreanDlmDB_onEventCallback;

ET9STATUS DLMKoreanEventHandlerCallback(void* const pEventHandlerInfo, ET9U8 const * const pbBuf,
        const ET9U32 dwBufLen) {

    //LOGV("DLMKoreanEventHandlerCallback Type: %d, length: %d", (dwBufLen > 0) ? pbBuf[0] : -1, dwBufLen);
    JXT9DlmDB* dlmDB = (JXT9DlmDB*)pEventHandlerInfo;
    if (!dlmDB) {
        return ET9STATUS_ERROR;
    }

    jbyteArray event = dlmDB->env->NewByteArray(dwBufLen);
    dlmDB->env->SetByteArrayRegion(event, 0, dwBufLen, (jbyte*)pbBuf);
    dlmDB->env->CallVoidMethod(dlmDB->callbackRef, dlmDB->asyncEventMethod,
        event, ET9_SYNC_IsHighPriorityEvent(pbBuf, dwBufLen));
    dlmDB->env->DeleteLocalRef(event);

    return ET9STATUS_NONE;
}

C_DECL jint API(acKoreanExportAsEvent)(JNIEnv* env, jobject thiz, jboolean usingCategory, jint category) {
    //LOGV("acKoreanExportAsEvent");
    ET9KLingInfo* info = getKoreanLingInfo();
    if (info == NULL) {
        return -1;
    }
    return ET9KDLMExportAsEvents(info, (ET9BOOL)usingCategory, (ET9U16)category);
}

C_DECL jint API(acKoreanRegisterEventHandlerCallback)(JNIEnv* env, jobject thiz) {
    //LOGV("acKoreanRegisterEventHandlerCallback 1");
    ET9KLingInfo* info = getKoreanLingInfo();
    g_KoreanDlmDB_onEventCallback.env = env;
    if (info == NULL || g_KoreanDlmDB_onEventCallback.env == NULL) {
        return -1;
    }

    if (g_KoreanDlmDB_onEventCallback.callbackRef != NULL) {
        g_KoreanDlmDB_onEventCallback.env->DeleteGlobalRef(g_KoreanDlmDB_onEventCallback.callbackRef);
    }
    g_KoreanDlmDB_onEventCallback.callbackRef = env->NewGlobalRef(thiz);
    jclass c = env->GetObjectClass(thiz);
    g_KoreanDlmDB_onEventCallback.asyncEventMethod = env->GetMethodID(c, "onEventCallback", "([BZ)V");

    return ET9KDLMRegisterForEvents(info, &DLMKoreanEventHandlerCallback, (void*)&g_KoreanDlmDB_onEventCallback);
}

C_DECL jint API(acKoreanProcessEvent)(JNIEnv* env, jobject thiz, jbyteArray jevent) {
    //LOGV("acKoreanProcessEvent");
    ET9KLingInfo* info = getKoreanLingInfo();
    if (info == NULL) {
        return -1;
    }
    jsize len = env->GetArrayLength(jevent);
    jbyte* event = env->GetByteArrayElements(jevent, NULL);

    ET9STATUS status = ET9KDLMHandleEvents(info, (ET9U8*)event, (ET9U16) len);

    env->ReleaseByteArrayElements(jevent, event, JNI_ABORT);

    return status;
}

C_DECL jint API(acKoreanDeleteCategory)(JNIEnv* env, jobject thiz, jint category) {
    //LOGV("acKoreanDeleteCategory");
    ET9KLingInfo* info = getKoreanLingInfo();
    if (info == NULL) {
        return -1;
    }
    return ET9KDLMDeleteCategory(info, (ET9U16)category);
}

C_DECL jint API(acKoreanDeleteCategoryLanguage)(JNIEnv* env, jobject thiz, jint category, jint languageId) {
    //LOGV("acKoreanDeleteCategoryLanguage");
    return API(acKoreanDeleteCategory)(env, thiz, category);
}

C_DECL jint API(acKoreanScanBuffer)(JNIEnv* env, jobject thiz, jcharArray charArray,
        jint len, jint startPos, jint endPos, jint wordQuality, bool sentenceBased, bool rescanning) {
    //LOGV("acKoreanScanBuffer");
    ET9KLingInfo* info = getKoreanLingInfo();
    if (info == NULL) {
        return -1;
    }
    //jsize len = env->GetArrayLength(jevent);
    jchar* buffer = env->GetCharArrayElements(charArray, NULL);

    ET9STATUS status = ET9KDLMScanBuf(info, buffer, len, startPos, endPos, 0, wordQuality != 0,
            sentenceBased, rescanning, true);

    env->ReleaseCharArrayElements(charArray, buffer, JNI_ABORT);

    return status;
}

#endif

#ifdef ET9_CHINESE_MODULE
#undef API
#define API(fun) Java_com_nuance_dlm_ACChineseInput_##fun

extern "C" ET9CPLingInfo* getChineseLingInfo();

static JXT9DlmDB g_ChineseDlmDB_onEventCallback;

ET9STATUS DLMChineseEventHandlerCallback(void* const pEventHandlerInfo, ET9U8 const * const pbBuf,
        const ET9U32 dwBufLen) {

    //LOGV("DLMChineseEventHandlerCallback Type: %d, length: %d", (dwBufLen > 0) ? pbBuf[0] : -1, dwBufLen);
    return ET9STATUS_NONE;
}

C_DECL jint API(acChineseExportAsEvent)(JNIEnv* env, jobject thiz, jboolean usingCategory, jint category) {
    //LOGV("acChineseExportAsEvent");
    return -1;
}

C_DECL jint API(acChineseRegisterEventHandlerCallback)(JNIEnv* env, jobject thiz) {
    //LOGV("acChineseRegisterEventHandlerCallback");
    ET9CPLingInfo* info = getChineseLingInfo();
    g_ChineseDlmDB_onEventCallback.env = env;
    if (info == NULL || g_ChineseDlmDB_onEventCallback.env == NULL) {
        return -1;
    }
    return -1;
}

C_DECL jint API(acChineseProcessEvent)(JNIEnv* env, jobject thiz, jbyteArray jevent) {
    //LOGV("acChineseProcessEvent");
    ET9CPLingInfo* info = getChineseLingInfo();
    if (info == NULL) {
        return -1;
    }
    jsize len = env->GetArrayLength(jevent);
    jbyte* event = env->GetByteArrayElements(jevent, NULL);

    ET9STATUS status = ET9CPSyncHandleEvents(info, (ET9U8*)event, (ET9U16) len);

    env->ReleaseByteArrayElements(jevent, event, JNI_ABORT);

    return status;
}

C_DECL jint API(acChineseDeleteCategory)(JNIEnv* env, jobject thiz, jint category) {
    //LOGV("acChineseDeleteCategory");
    return -1;
}

C_DECL jint API(acChineseDeleteCategoryLanguage)(JNIEnv* env, jobject thiz, jint category, jint languageId) {
    //LOGV("acChineseDeleteCategoryLanguage");
    return API(acChineseDeleteCategory)(env, thiz, category);
}

#endif
