//
// Created by CORP\jingi.jung on 19. 5. 14.
//

#include <jni.h>
#include <string.h>
#include <android/log.h>
#include <tl/TZ_Vendor_debug_tl.h>
#include <TuiControl.h>
#include <TuiBackupScreenController.h>
#include <TuiScreen.h>
#include <TuiPng.h>
#include <TuiConfirmScreenController.h>
#include <Vendor_Interface.h>
#include <process_cmd.h>
#include <pthread.h>
#include <TuiPinpadState.h>
#include <spay_tui_common.h>
#include "bctui-c.h"
#include "ImageResources.h"

pthread_t g_th;
void *getTouchEvent(void *commandId);

uint32_t pthread_create_common(uint32_t commandId);
uint32_t processKeyEventInternal(uint32_t x, uint32_t y, bool pressed);

typedef struct bctui_context {
    JavaVM  *javaVM;
    jclass   jniHelperClz;
    jobject  jniHelperObj;
} BCTuiContext;
BCTuiContext g_ctx;

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env;
    memset(&g_ctx, 0, sizeof(g_ctx));
    g_ctx.javaVM = vm;

    if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR; // JNI version not supported.
    }

    TTY_LOG("JNI_OnLoad");

    // create BcTuiNativeLibHandler instance
    jclass  clz = (*env)->FindClass(env,
                                    "com/samsung/android/blockchainnativelibs/BcTuiNativeHandler");
    g_ctx.jniHelperClz = (*env)->NewGlobalRef(env, clz);
    jmethodID  jniHelperCtor = (*env)->GetMethodID(env, g_ctx.jniHelperClz,
                                                   "<init>", "()V");

    jobject    handler = (*env)->NewObject(env, g_ctx.jniHelperClz,
                                           jniHelperCtor);

    g_ctx.jniHelperObj = (*env)->NewGlobalRef(env, handler);

    return JNI_VERSION_1_6;
}

JNIEnv* getEnv(){
    JavaVM *javaVM = g_ctx.javaVM;
    JNIEnv *env;
    jint res = (*javaVM)->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_6);
    if (res != JNI_OK) {
        res = (*javaVM)->AttachCurrentThread(javaVM, &env, NULL);
        if (JNI_OK != res) {
            return NULL;
        }
    }
    return env;
}


void sendJavaDrawImage(uint32_t x, uint32_t y, uint8_t *pngData, uint32_t length, uint32_t rotation){

    JNIEnv* env = getEnv();
    if(env != NULL){
        // void drawImage(int x, int y, byte[] pngData, int length, int rotation)
        jmethodID methodId = (*env)->GetMethodID(env, g_ctx.jniHelperClz, "drawImage", "(II[BII)V");

        jbyteArray pngJByte = (*env)->NewByteArray(env, length);
        (*env)->SetByteArrayRegion(env, pngJByte, 0, length, pngData);
        (*env)->CallVoidMethod(env, g_ctx.jniHelperObj, methodId, (jint)x, (jint)y, pngJByte, length, rotation);
        (*env)->DeleteLocalRef (env, pngJByte);
    }

}

void sendJavaShowFrameBuffer(){
    JNIEnv* env = getEnv();
    if(env != NULL){
        // void showFrameBuffer()
        jmethodID  methodId = (*env)->GetMethodID(env, g_ctx.jniHelperClz, "showFrameBuffer", "()V");
        (*env)->CallVoidMethod(env, g_ctx.jniHelperObj, methodId);
    }
}

void sendJavaShowPng(uint32_t x, uint32_t y, uint8_t *pngData, uint32_t length, uint32_t rotation) {
    JNIEnv* env = getEnv();
    if(env != NULL){
        // void showPng(int x, int y, byte[] pngData, int length, int rotation)
        jmethodID  methodId = (*env)->GetMethodID(env, g_ctx.jniHelperClz, "showPng", "(II[BII)V");

        jbyteArray pngJByte = (*env)->NewByteArray(env, length);
        (*env)->SetByteArrayRegion(env, pngJByte, 0, length, pngData);
        (*env)->CallVoidMethod(env, g_ctx.jniHelperObj, methodId, (jint)x, (jint)y, pngJByte, length, rotation);
        (*env)->DeleteLocalRef (env, pngJByte);
    }
}

void sendJavaStartTuiSession(uint8_t *bgImage, uint32_t bgImageSize, uint32_t bgRotation) {
    JNIEnv* env = getEnv();
    if(env != NULL){
        // void startTuiSession(byte[] bgImage, int bgImageSize, int rotation)
        TTY_LOG("send Java Start Tui Session");
        jmethodID  methodId = (*env)->GetMethodID(env, g_ctx.jniHelperClz, "startTuiSession", "([BII)V");
        TTY_LOG("methodId:%d", methodId);
        jbyteArray pngJByte = (*env)->NewByteArray(env, bgImageSize);
        (*env)->SetByteArrayRegion(env, pngJByte, 0, bgImageSize, bgImage);
        TTY_LOG("copyBytes");
        (*env)->CallVoidMethod(env, g_ctx.jniHelperObj, methodId, pngJByte, bgImageSize, bgRotation);
        TTY_LOG("callFinish");
        (*env)->DeleteLocalRef (env, pngJByte);
    }

}

void sendJavaCloseTuiSession() {
    JNIEnv* env = getEnv();
    if(env != NULL){
        // void closeTuiSesson()
        jmethodID  methodId = (*env)->GetMethodID(env, g_ctx.jniHelperClz, "closeTuiSession", "()V");
        (*env)->CallVoidMethod(env, g_ctx.jniHelperObj, methodId);
    }

}


JNIEXPORT jobject JNICALL
Java_com_samsung_android_blockchainnativelibs_BcTuiNativeHandler_processCommandTui(JNIEnv *env,
                                                                                jclass type,
                                                                                jint commandId,
                                                                                jbyteArray payload_,
                                                                                jint payloadLength) {
    jbyte *payload = (*env)->GetByteArrayElements(env, payload_, NULL);

    tciMessage_t sendTciMessage;
    tciMessage_t respTciMessage;

    memset(&sendTciMessage, 0x00, sizeof(sendTciMessage));
    memset(&respTciMessage, 0x00, sizeof(respTciMessage));

    memcpy(&sendTciMessage.payload, payload, (size_t)payloadLength);
    uint32_t result = process_cmd((uint32_t )commandId, &sendTciMessage, sizeof(sendTciMessage)
            , &respTciMessage, sizeof(respTciMessage));

    // PIN Exception codes.
    // TODO : Need to refactor like coldwallet's pthread_create
    if (0 == result && (SPAY_TUI_CMD_SETUP == commandId || SPAY_TUI_CMD_RESUME == commandId || SPAY_TUI_CMD_VERIFY == commandId)) {
        result = pthread_create_common(commandId);

        TTY_LOG("%s: ret = 0x%08X, ret&spay_tui_mask: 0x%08X, ^: 0x%08X", LOG_TAG, result, (result&SPAY_TUI_MASK), result^SPAY_TUI_MASK);

        if((result & SPAY_TUI_MASK) == SPAY_TUI_MASK) {
            result = result ^ SPAY_TUI_MASK;
            TTY_LOG("%s: ret = 0x%08X", LOG_TAG, result);
            // all response has retCode
            respTciMessage.payload.rsp.data.commonRsp.retCode = result;
            result = 0;
        }
    }

    (*env)->ReleaseByteArrayElements(env, payload_, payload, 0);

    jbyteArray retVal = (*env)->NewByteArray(env, sizeof(respTciMessage.payload));
    (*env)->SetByteArrayRegion(env, retVal, 0, sizeof(respTciMessage.payload),
                               (const jbyte *) &respTciMessage.payload);

    jclass class = (*env)->FindClass(env,"com/samsung/android/blockchainnativelibs/response/TANativeResponse");

    jmethodID cid = (*env)->GetMethodID(env,class, "<init>", "()V");
    jobject newObject = (*env)->NewObject(env, class, cid, "()V");

    jfieldID fid = (*env)->GetFieldID(env, class, "result", "I");
    (*env)->SetIntField(env, newObject, fid, result);

    fid = (*env)->GetFieldID(env, class, "errorMessage", "Ljava/lang/String;");
    jstring errorMessage = (*env)->NewStringUTF(env, "");
    (*env)->SetObjectField(env, newObject, fid, errorMessage);

    fid = (*env)->GetFieldID(env, class, "payload", "[B");
    (*env)->SetObjectField(env, newObject, fid, retVal);

    (*env)->DeleteLocalRef(env, retVal);

    return newObject;
}

JNIEXPORT void JNICALL
Java_com_samsung_android_blockchainnativelibs_BcTuiNativeHandler_setTouchEvent(JNIEnv *env,
                                                                            jclass type,
                                                                            jint x,
                                                                            jint y,
                                                                            jint event) {
    setNDKTouchEvent(x, y, event);
}

void setNDKTouchEvent(int x, int y, int event) {
    g_ndk_touch_event.x = x;
    g_ndk_touch_event.y = y;
    g_ndk_touch_event.event = event;
    g_touch_event_read_flag = false;
}

NDKTouchEvent* getNDKTouchEvent() {
    if(g_touch_event_read_flag == false){
        g_touch_event_read_flag = true;
        return &g_ndk_touch_event;
    } else {
        return NULL;
    }

}

void *getTouchEvent(void *commandId) {
    uint32_t ret = 0;

    uint32_t cmd_id = *(uint32_t*)commandId;
    DBG_LOG("%s getTouchEvent() entered, 0x%08X", LOG_TAG, cmd_id);

    for (;;) {
        bool notifyTlc = true;  // Should we notify TLC or not. Do not notify for driver events

        NDKTouchEvent* ndkTouchEvent = getNDKTouchEvent();

        if(ndkTouchEvent != NULL){
            TTY_LOG("TouchEvent : <%d,%d> - %d", ndkTouchEvent->x, ndkTouchEvent->y, ndkTouchEvent->event);
            uint32_t touchType;
            if(ndkTouchEvent->event == 0 || ndkTouchEvent->event == 2) {
                touchType = PRESSED_TOUCH_EVENT;
            } else {
                touchType = RELEASED_TOUCH_EVENT;
            }

            setPinpadState(processKeyEventInternal(ndkTouchEvent->x, ndkTouchEvent->y, (touchType == PRESSED_TOUCH_EVENT)));
            TTY_LOG("%s processKeyEvent return=0x%08X", LOG_TAG, getPinpadState());
            if (getPinpadState() & _SPAY_TUI_ST_ENTER) {
                showFrameBuffer_vendor(); /* To display last pin image '*' */
                ret = processPinpadState();
                DBG_LOG("%s process_enter return=0x%08X", LOG_TAG, ret);
            }

            // don't notify if TUI is started and running correctly
            TTY_LOG("%s Let's notify TLC cmdId: 0x%08X, ret: 0x%08X, getPinpadState(): 0x%08X"
            , LOG_TAG, cmd_id, ret, getPinpadState());

            if ((getPinpadState() & _SPAY_TUI_ST_START) && ret == 0) {
                notifyTlc = false;
            }

            if (notifyTlc) {
                DBG_LOG("%s Let's notify TLC cmdId: 0x%08X, ret: 0x%08X, getPinpadState(): 0x%08X"
                , LOG_TAG, cmd_id, ret, getPinpadState());

                if (SPAY_TUI_CMD_SETUP == cmd_id
                    || SPAY_TUI_CMD_VERIFY == cmd_id
                    || SPAY_TUI_CMD_RESUME == cmd_id) {
                    // When notify after TUI session, set header len and real cmd id correctly

                    if (0 == ret) {
                        switch (getPinpadState()) {
                            case SPAY_TUI_ST_SETUP_MATCH:
                                DBG_LOG("%s SPAY_TUI_ST_SETUP_MATCH: ", LOG_TAG);
                                if (SPAY_TUI_CMD_RESUME == cmd_id) {
                                    // setup is successful, close session
                                    DBG_LOG("%s cmdID: SPAY_TUI_CMD_RESUME", LOG_TAG);
                                    ret = SPAY_TPP_SETUP_PIN_VERIFIED;
                                    closeTuiSession_vendor();
                                } else {
                                    DBG_LOG("%s State is setup_match but real_cmd_id is 0x%x"
                                    , LOG_TAG, cmd_id);
                                }
                                break;
                            case SPAY_TUI_ST_SETUP_ENTER_FIRST:
                                ret = SPAY_TPP_SETUP_PIN_ENTERED;
                                break;
                            case SPAY_TUI_ST_SETUP_UNMATCH:
                                ret = SPAY_TPP_ERROR_SETUP_PIN_MISMATCH;
                                break;
                            case SPAY_TUI_ST_VERIFY_MATCH:
                                closeTuiSession_vendor();
//                                if (g_close_tui_after_verify) {
//                                    DBG_LOG("Close TUI now");
//                                    closeTuiSession_vendor();
//                                }
                                ret = SPAY_TPP_PIN_VERIFYED;
                                break;
                            case SPAY_TUI_ST_VERIFY_UNMATCH: //fall through
                                ret = SPAY_TPP_ERROR_PIN_MISMATCH;
                                break;
                            case SPAY_TUI_ST_CANCELLED:
                                closeTuiSession_vendor();
                                ret = TIMA_ERROR_TUI_CANCELLED;
                                break;
                            case SPAY_TUI_ST_NONE: //fall through
                                break;
                            default:
                                ret = SPAY_TPP_ERROR_INVALID_STATE;
                                break;
                        }
                    }
                }

                if (notifyTlc) {
                    ret |= SPAY_TUI_MASK;
                    break;
                }
            }
        }
    }
    return (void*)ret;
}

uint32_t processKeyEventInternal(uint32_t x, uint32_t y, bool pressed) {
    uint32_t ret = TIMA_SUCCESS;
    sPinPadKey_t touched_key;

    // DBG_LOG("%s Enter processKeyEventInternal, getPinpadState() = 0x%08X, g_update_display_only = 0x%08X", LOG_TAG, getPinpadState(), g_update_display_only);
    return processKeyEvent(x, y, pressed);

}


uint32_t pthread_create_pin(uint32_t commandId) {
    uint32_t ret = TIMA_ERROR_UNKNOWN_ERROR;


    pthread_create(&g_th, NULL, getTouchEvent, (void *)&commandId);

    pthread_join(g_th, (void**)&ret);

    return ret;
}

uint32_t pthread_create_common(uint32_t commandId) {
    uint32_t ret = 0;
    switch (commandId) {
        case SPAY_TUI_CMD_SETUP:
        case SPAY_TUI_CMD_VERIFY:
        case SPAY_TUI_CMD_RESUME:
            ret = pthread_create_pin(commandId);
            break;

        default:
            TTY_LOG("Not supportred command on BC_TUI. command :0x%08X ", commandId);
            ret = TIMA_ERROR_TUI_UNKNOWN_CMD;
            break;
    }

    return ret;
}