#define EXTENSION_NAME SIWG
#define LIB_NAME "SIWG"
#define MODULE_NAME "siwg"

// include the Defold SDK
#define DLIB_LOG_DOMAIN LIB_NAME
#include <dmsdk/sdk.h>

#if defined(DM_PLATFORM_ANDROID)

// #include "siwg_jni.h"

#include <string.h>
#include <dmsdk/dlib/android.h>
#include "siwg.h"
#include "siwg_callback.h"
#include "com_aterve_siwg_SiwgJNI.h"

static bool luaL_checkbool(lua_State *L, int numArg)
{
    bool b = false;
    if (lua_isboolean(L, numArg))
    {
        b = lua_toboolean(L, numArg);
    }
    else
    {
        luaL_typerror(L, numArg, lua_typename(L, LUA_TBOOLEAN));
    }
    return b;
}

static bool luaL_checkboold(lua_State *L, int numArg, int def)
{
    int type = lua_type(L, numArg);
    if (type != LUA_TNONE && type != LUA_TNIL)
    {
        return luaL_checkbool(L, numArg);
    }
    return def;
}

static lua_Number luaL_checknumberd(lua_State *L, int numArg, lua_Number def)
{
    int type = lua_type(L, numArg);
    if (type != LUA_TNONE && type != LUA_TNIL)
    {
        return luaL_checknumber(L, numArg);
    }
    return def;
}

static char* luaL_checkstringd(lua_State *L, int numArg, const char* def)
{
    int type = lua_type(L, numArg);
    if (type != LUA_TNONE && type != LUA_TNIL)
    {
        return (char*)luaL_checkstring(L, numArg);
    }
    return (char*)def;
}

static lua_Number luaL_checktable_number(lua_State *L, int numArg, const char* field, lua_Number def)
{
    lua_Number result = def;
    if(lua_istable(L, numArg))
    {
        lua_getfield(L, numArg, field);
        if(!lua_isnil(L, -1))
        {
            result = luaL_checknumber(L, -1);
        }
        lua_pop(L, 1);
    }
    return result;
}

static char* luaL_checktable_string(lua_State *L, int numArg, const char* field, char* def)
{
    char* result = def;
    if(lua_istable(L, numArg))
    {
        lua_getfield(L, numArg, field);
        if(!lua_isnil(L, -1))
        {
            result = (char*)luaL_checkstring(L, -1);
        }
        lua_pop(L, 1);
    }
    return result;
}


//
// JNI Method Executors
//

// void method()
static int CallVoidMethod(jobject instance, jmethodID method)
{
    dmAndroid::ThreadAttacher threadAttacher;
    JNIEnv* env = threadAttacher.GetEnv();
    env->CallVoidMethod(instance, method);
    return 0;
}

// string method()
static int CallStringMethod(lua_State* L, jobject instance, jmethodID method)
{
    DM_LUA_STACK_CHECK(L, 1);
    dmAndroid::ThreadAttacher threadAttacher;
    JNIEnv* env = threadAttacher.GetEnv();

    jstring return_value = (jstring)env->CallObjectMethod(instance, method);
    if (return_value)
    {
        const char* cstr = env->GetStringUTFChars(return_value, 0);
        lua_pushstring(L, cstr);
        env->ReleaseStringUTFChars(return_value, cstr);
        env->DeleteLocalRef(return_value);
    }
    else
    {
        lua_pushnil(L);
    }
    return 1;
}

// boolean method()
static int CallBooleanMethod(lua_State* L, jobject instance, jmethodID method)
{
    DM_LUA_STACK_CHECK(L, 1);
    dmAndroid::ThreadAttacher threadAttacher;
    JNIEnv* env = threadAttacher.GetEnv();
    jboolean return_value = (jboolean)env->CallBooleanMethod(instance, method);
    lua_pushboolean(L, JNI_TRUE == return_value);
    return 1;
}

//
// SIWG Handler
//

struct SIWG
{
    jobject m_SiwgJNI;
    jmethodID m_silentLogin;
    jmethodID m_login;
    jmethodID m_logout;
    jmethodID m_activityResult;
    jmethodID m_getDisplayName;
    jmethodID m_getEmail;
    jmethodID m_getId;
    jmethodID m_getIdToken;
    jmethodID m_getServerAuthCode;
    jmethodID m_isLoggedIn;
    jmethodID m_isSupported;
};

static SIWG g_siwg;

static int SiwgAuth_Login(lua_State* L)
{
    CallVoidMethod(g_siwg.m_SiwgJNI, g_siwg.m_login);
    return 0;
}

static int SiwgAuth_Logout(lua_State* L)
{
    CallVoidMethod(g_siwg.m_SiwgJNI, g_siwg.m_logout);
    return 0;
}

static int SiwgAuth_SilentLogin(lua_State* L)
{
    CallVoidMethod(g_siwg.m_SiwgJNI, g_siwg.m_silentLogin);
    return 0;
}

static int SiwgAuth_getDisplayName(lua_State* L)
{
    return CallStringMethod(L, g_siwg.m_SiwgJNI, g_siwg.m_getDisplayName);
}

static int SiwgAuth_getEmail(lua_State* L)
{
    return CallStringMethod(L, g_siwg.m_SiwgJNI, g_siwg.m_getEmail);
}

static int SiwgAuth_getId(lua_State* L)
{
    return CallStringMethod(L, g_siwg.m_SiwgJNI, g_siwg.m_getId);
}

static int SiwgAuth_getIdToken(lua_State* L)
{
    return CallStringMethod(L, g_siwg.m_SiwgJNI, g_siwg.m_getIdToken);
}

static int SiwgAuth_getServerAuthCode(lua_State* L)
{
    return CallStringMethod(L, g_siwg.m_SiwgJNI, g_siwg.m_getServerAuthCode);
}

static int SiwgAuth_isLoggedIn(lua_State* L)
{
    return CallBooleanMethod(L, g_siwg.m_SiwgJNI, g_siwg.m_isLoggedIn);
}

static int SiwgAuth_isSupported(lua_State* L)
{
    return CallBooleanMethod(L, g_siwg.m_SiwgJNI, g_siwg.m_isSupported);
}

static int SiwgAuth_set_callback(lua_State* L)
{
    siwg_set_callback(L, 1);
    return 0;
}

//
// JNI Extension Methods
//

static void OnActivityResult(JNIEnv* env, jobject activity, int32_t request_code, int32_t result_code, void* result)
{
    env->CallVoidMethod(g_siwg.m_SiwgJNI, g_siwg.m_activityResult, request_code, result_code, result);
}

JNIEXPORT void JNICALL Java_com_aterve_siwg_SiwgJNI_siwgAddToQueue(JNIEnv * env, jclass cls, jint jmsg, jstring jjson)
{
    const char* json = env->GetStringUTFChars(jjson, 0);
    siwg_add_to_queue((MESSAGE_ID)jmsg, json);
    env->ReleaseStringUTFChars(jjson, json);
}

// Functions exposed to Lua
static const luaL_reg Siwg_methods[] =
{
    {"is_supported", SiwgAuth_isSupported},
    {"login", SiwgAuth_Login},
    {"logout", SiwgAuth_Logout},
    {"silent_login", SiwgAuth_SilentLogin},
    {"get_display_name", SiwgAuth_getDisplayName},
    {"get_email", SiwgAuth_getEmail},
    {"get_id", SiwgAuth_getId},
    {"get_id_token", SiwgAuth_getIdToken},
    {"get_server_auth_code", SiwgAuth_getServerAuthCode},
    {"is_logged_in", SiwgAuth_isLoggedIn},
    {"set_callback", SiwgAuth_set_callback},
    {0, 0}
};

dmExtension::Result AppInitializeSIWG(dmExtension::AppParams* params)
{
    dmLogInfo("[SIWG] Registered Extension!");
    return dmExtension::RESULT_OK;
}

static void LuaInit(lua_State* L)
{
    DM_LUA_STACK_CHECK(L, 0);
    luaL_register(L, MODULE_NAME, Siwg_methods);

#define SETCONSTANT(name) \
    lua_pushnumber(L, (lua_Number) name); \
    lua_setfield(L, -2, #name); \

    SETCONSTANT(MSG_SIGN_IN)
    SETCONSTANT(MSG_SILENT_SIGN_IN)
    SETCONSTANT(MSG_SIGN_OUT)

    SETCONSTANT(STATUS_SUCCESS)
    SETCONSTANT(STATUS_FAILED)
#undef SETCONSTANT

    lua_pop(L, 1);
}

//
// JNI Setup Methods
//

static void InitJNIMethods(JNIEnv* env, jclass cls)
{
    //general
    g_siwg.m_isSupported = env->GetMethodID(cls, "isSupported", "()Z");

    //authorization
    g_siwg.m_silentLogin = env->GetMethodID(cls, "silentLogin", "()V");
    g_siwg.m_login = env->GetMethodID(cls, "login", "()V");
    g_siwg.m_logout = env->GetMethodID(cls, "logout", "()V");
    g_siwg.m_isLoggedIn = env->GetMethodID(cls, "isLoggedIn", "()Z");
    g_siwg.m_getDisplayName = env->GetMethodID(cls, "getDisplayName", "()Ljava/lang/String;");
    g_siwg.m_getEmail = env->GetMethodID(cls, "getEmail", "()Ljava/lang/String;");
    g_siwg.m_getId = env->GetMethodID(cls, "getId", "()Ljava/lang/String;");
    g_siwg.m_getIdToken = env->GetMethodID(cls, "getIdToken", "()Ljava/lang/String;");
    g_siwg.m_getServerAuthCode = env->GetMethodID(cls, "getServerAuthCode", "()Ljava/lang/String;");

    //private methods
    g_siwg.m_activityResult = env->GetMethodID(cls, "activityResult", "(IILandroid/content/Intent;)V");
}

static void InitializeJNI(const char* client_id)
{
    dmAndroid::ThreadAttacher threadAttacher;
    JNIEnv* env = threadAttacher.GetEnv();
    jclass cls =  dmAndroid::LoadClass(env, "com.aterve.siwg.SiwgJNI");
    dmLogInfo("[SIWG] InitJNI Loaded Class");

    InitJNIMethods(env, cls);
    dmLogInfo("[SIWG] InitJNI Methods Initialized");

    jmethodID jni_constructor = env->GetMethodID(cls, "<init>", "(Landroid/app/Activity;ZZZLjava/lang/String;)V");
    jstring java_client_id = env->NewStringUTF(client_id);

    g_siwg.m_SiwgJNI = env->NewGlobalRef(env->NewObject(cls, jni_constructor, threadAttacher.GetActivity()->clazz, java_client_id));
    env->DeleteLocalRef(java_client_id);
    dmLogInfo("[SIWG] InitJNI JNI Refrences Initialized");
}

//
// Extention Event Methods
//

dmExtension::Result InitializeSIWG(dmExtension::Params* params)
{
    dmLogInfo("[SIWG] Initializing Extension...");

    LuaInit(params->m_L);
    const char* client_id = dmConfigFile::GetString(params->m_ConfigFile, "siwg.client_id", 0);
    dmLogInfo("[SIWG] Loaded Settings");

    InitializeJNI(client_id);

    dmAndroid::RegisterOnActivityResultListener(OnActivityResult);
    dmLogInfo("[SIWG] Activity Result Listener Initialized");
    
    siwg_callback_initialize();

    dmLogInfo("[SIWG] Initialization Completed!");

    return dmExtension::RESULT_OK;
}

dmExtension::Result UpdateSIWG(dmExtension::Params* params)
{
    siwg_callback_update();
    return dmExtension::RESULT_OK;
}

dmExtension::Result FinalizeSIWG(dmExtension::Params* params)
{
    dmLogInfo("[SIWG] Finalizing Extension...");

    siwg_callback_finalize();

    dmAndroid::UnregisterOnActivityResultListener(OnActivityResult);
    dmLogInfo("[SIWG] Activity Result Listener Unregistered");

    return dmExtension::RESULT_OK;
}

dmExtension::Result AppFinalizeSIWG(dmExtension::AppParams* params)
{
    return dmExtension::RESULT_OK;
}

DM_DECLARE_EXTENSION(EXTENSION_NAME, LIB_NAME, AppInitializeSIWG, AppFinalizeSIWG, InitializeSIWG, UpdateSIWG, 0, FinalizeSIWG)

#else

dmExtension::Result InitializeSIWG(dmExtension::Params* params)
{
    dmLogInfo("[SIWG] Initializing Extension...");
    dmLogInfo("[SIWG] Initialization Failed! (Invalid OS Type)");
    return dmExtension::RESULT_OK;
}

dmExtension::Result FinalizeSIWG(dmExtension::Params* params)
{
    dmLogInfo("[SIWG] Finalizing Extension...");
    return dmExtension::RESULT_OK;
}

DM_DECLARE_EXTENSION(EXTENSION_NAME, LIB_NAME, 0, 0, InitializeSIWG, 0, 0, FinalizeSIWG)

#endif