// Copyright (C) 2002-2007 Nikolaus Gebhardt
// Copyright (C) 2007-2011 Christian Stehno
// Copyright (C) 2016-2017 Dawid Gan
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

#ifdef ANDROID
#include <irrString.h>
#include <atomic>
#include <jni.h>
#include <SDL_system.h>
#include <string>
#include <vector>
#include "../../../../src/utils/utf8/unchecked.h"
#include "../../../../src/guiengine/message_queue.hpp"

using namespace irr;

// Call when android keyboard is opened or close, and save its height for
// moving screen
std::atomic<int> g_keyboard_height(0);
std::atomic<int> g_moved_height(0);
std::atomic<int> g_disable_padding(0);
extern "C" int Android_getKeyboardHeight()
{
    return g_keyboard_height.load();
}

extern "C" int Android_getMovedHeight()
{
    return g_moved_height.load();
}

extern "C" int Android_disablePadding()
{
    return g_disable_padding.load();
}

#define MAKE_DEBUG_MSG_CALLBACK(x) JNIEXPORT void JNICALL Java_ ## x##_SuperTuxKartActivity_debugMsg(JNIEnv* env, jclass cls, jstring msg)
#define ANDROID_DEBUG_MSG_CALLBACK(PKG_NAME) MAKE_DEBUG_MSG_CALLBACK(PKG_NAME)

extern "C"
ANDROID_DEBUG_MSG_CALLBACK(ANDROID_PACKAGE_CALLBACK_NAME)
{
    if (msg == NULL)
        return;
    const uint16_t* utf16_text =
        (const uint16_t*)env->GetStringChars(msg, NULL);
    if (utf16_text == NULL)
        return;
    const size_t str_len = env->GetStringLength(msg);
    std::u32string tmp;
    utf8::unchecked::utf16to32(
        utf16_text, utf16_text + str_len, std::back_inserter(tmp));
    env->ReleaseStringChars(msg, utf16_text);
    core::stringw message = (wchar_t*)tmp.c_str();
    MessageQueue::add(MessageQueue::MT_GENERIC, message);
}

#define MAKE_ANDROID_SAVE_KBD_HEIGHT_CALLBACK(x) JNIEXPORT void JNICALL Java_ ## x##_SuperTuxKartActivity_saveKeyboardHeight(JNIEnv* env, jclass cls, jint height)
#define ANDROID_SAVE_KBD_HEIGHT_CALLBACK(PKG_NAME) MAKE_ANDROID_SAVE_KBD_HEIGHT_CALLBACK(PKG_NAME)

extern "C"
ANDROID_SAVE_KBD_HEIGHT_CALLBACK(ANDROID_PACKAGE_CALLBACK_NAME)
{
    g_keyboard_height.store((int)height);
}

#define MAKE_ANDROID_SAVE_MOVED_HEIGHT_CALLBACK(x) JNIEXPORT void JNICALL Java_ ## x##_SuperTuxKartActivity_saveMovedHeight(JNIEnv* env, jclass cls, jint height)
#define ANDROID_SAVE_MOVED_HEIGHT_CALLBACK(PKG_NAME) MAKE_ANDROID_SAVE_MOVED_HEIGHT_CALLBACK(PKG_NAME)

extern "C"
ANDROID_SAVE_MOVED_HEIGHT_CALLBACK(ANDROID_PACKAGE_CALLBACK_NAME)
{
    g_moved_height.store((int)height);
}

#define MAKE_ANDROID_HANDLE_PADDING_CALLBACK(x) JNIEXPORT void JNICALL Java_ ## x##_SuperTuxKartActivity_handlePadding(JNIEnv* env, jclass cls, jboolean val)
#define ANDROID_HANDLE_PADDING_CALLBACK(PKG_NAME) MAKE_ANDROID_HANDLE_PADDING_CALLBACK(PKG_NAME)

extern "C"
ANDROID_HANDLE_PADDING_CALLBACK(ANDROID_PACKAGE_CALLBACK_NAME)
{
    g_disable_padding.store((int)val);
}

extern "C" void Android_initDisplayCutout(float* top, float* bottom,
                                          float* left, float* right,
                                          int* initial_orientation)
{
    JNIEnv* env = NULL;
    jobject activity = NULL;
    jclass class_native_activity = NULL;
    jmethodID top_method = NULL;
    jmethodID bottom_method = NULL;
    jmethodID left_method = NULL;
    jmethodID right_method = NULL;
    jmethodID initial_orientation_method = NULL;

    env = (JNIEnv*)SDL_AndroidGetJNIEnv();
    if (!env)
        goto exit;

    activity = (jobject)SDL_AndroidGetActivity();
    if (!activity)
        goto exit;

    class_native_activity = env->GetObjectClass(activity);
    if (class_native_activity == NULL)
        goto exit;

    top_method = env->GetMethodID(class_native_activity, "getTopPadding", "()F");
    if (top_method == NULL)
        goto exit;
    *top = env->CallFloatMethod(activity, top_method);

    bottom_method = env->GetMethodID(class_native_activity, "getBottomPadding", "()F");
    if (bottom_method == NULL)
        goto exit;
    *bottom = env->CallFloatMethod(activity, bottom_method);

    left_method = env->GetMethodID(class_native_activity, "getLeftPadding", "()F");
    if (left_method == NULL)
        goto exit;
    *left = env->CallFloatMethod(activity, left_method);

    right_method = env->GetMethodID(class_native_activity, "getRightPadding", "()F");
    if (right_method == NULL)
        goto exit;
    *right = env->CallFloatMethod(activity, right_method);

    initial_orientation_method = env->GetMethodID(class_native_activity, "getInitialOrientation", "()I");
    if (initial_orientation_method == NULL)
        goto exit;
    *initial_orientation = env->CallIntMethod(activity, initial_orientation_method);
exit:
    if (!env)
        return;
    env->DeleteLocalRef(class_native_activity);
    env->DeleteLocalRef(activity);
}

bool Android_isHardwareKeyboardConnected()
{
    JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv();
    if (!env)
    {
        return false;
    }

    jobject activity = (jobject)SDL_AndroidGetActivity();
    if (!activity)
    {
        return false;
    }

    jclass class_native_activity = env->GetObjectClass(activity);
    if (class_native_activity == NULL)
    {
        env->DeleteLocalRef(activity);
        return false;
    }

    jmethodID method_id = env->GetMethodID(class_native_activity, "isHardwareKeyboardConnected", "()Z");
    if (method_id == NULL)
    {
        env->DeleteLocalRef(class_native_activity);
        env->DeleteLocalRef(activity);
        return false;
    }

    bool ret = env->CallBooleanMethod(activity, method_id);
    env->DeleteLocalRef(class_native_activity);
    env->DeleteLocalRef(activity);
    return ret;
}

void Android_toggleOnScreenKeyboard(bool show, int type, int y)
{
    JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv();
    if (!env)
    {
        return;
    }

    jobject activity = (jobject)SDL_AndroidGetActivity();
    if (!activity)
    {
        return;
    }

    jclass class_native_activity = env->GetObjectClass(activity);
    if (class_native_activity == NULL)
    {
        env->DeleteLocalRef(activity);
        return;
    }

    jmethodID method_id = NULL;
    if (show)
        method_id = env->GetMethodID(class_native_activity, "showKeyboard", "(II)V");
    else
        method_id = env->GetMethodID(class_native_activity, "hideKeyboard", "(Z)V");

    if (method_id == NULL)
    {
        env->DeleteLocalRef(class_native_activity);
        env->DeleteLocalRef(activity);
        return;
    }

    if (show)
        env->CallVoidMethod(activity, method_id, (jint)type, (jint)y);
    else
        env->CallVoidMethod(activity, method_id, (jboolean)(type != 0));
    env->DeleteLocalRef(class_native_activity);
    env->DeleteLocalRef(activity);
}

void Android_fromSTKEditBox(int widget_id, const core::stringw& text, int selection_start, int selection_end, int type)
{
    JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv();
    if (!env)
    {
        return;
    }

    jobject activity = (jobject)SDL_AndroidGetActivity();
    if (!activity)
    {
        return;
    }

    jclass class_native_activity = env->GetObjectClass(activity);
    if (class_native_activity == NULL)
    {
        env->DeleteLocalRef(activity);
        return;
    }

    jmethodID method_id = env->GetMethodID(class_native_activity, "fromSTKEditBox", "(ILjava/lang/String;III)V");
    if (method_id == NULL)
    {
        env->DeleteLocalRef(class_native_activity);
        env->DeleteLocalRef(activity);
        return;
    }

    // Android use 32bit wchar_t and java use utf16 string
    // We should not use the modified utf8 from java as it fails for emoji
    // because it's larger than 16bit

    std::vector<uint16_t> utf16;
    // Use utf32 for emoji later
    static_assert(sizeof(wchar_t) == sizeof(uint32_t), "wchar_t is not 32bit");
    const uint32_t* chars = (const uint32_t*)text.c_str();
    utf8::unchecked::utf32to16(chars, chars + text.size(), back_inserter(utf16));

    std::vector<int> mappings;
    int pos = 0;
    mappings.push_back(pos++);
    for (unsigned i = 0; i < utf16.size(); i++)
    {
        if (utf8::internal::is_lead_surrogate(utf16[i]))
        {
            pos++;
            mappings.push_back(pos++);
            i++;
        }
        else
            mappings.push_back(pos++);
    }

    // Correct start / end position for utf16
    if (selection_start < (int)mappings.size())
        selection_start = mappings[selection_start];
    if (selection_end < (int)mappings.size())
        selection_end = mappings[selection_end];

    jstring jstring_text = env->NewString((const jchar*)utf16.data(), utf16.size());

    env->CallVoidMethod(activity, method_id, (jint)widget_id, jstring_text, (jint)selection_start, (jint)selection_end, (jint)type);
    env->DeleteLocalRef(jstring_text);
    env->DeleteLocalRef(class_native_activity);
    env->DeleteLocalRef(activity);
}

#endif
