#include "Misc.hpp"

#include "fmt/core.h"

#ifndef GKMS_WINDOWS
    #include <jni.h>
    #if defined(__ANDROID__) && __ANDROID_API__ >= 31
        #include <unicode/ustring.h>
    #endif
    
    extern JavaVM* g_javaVM;
#else
    #include "cpprest/details/http_helpers.h"
#endif


namespace GakumasLocal::Misc {

#ifdef GKMS_WINDOWS
    std::string ToUTF8(const std::wstring_view& str) {
		return utility::conversions::to_utf8string(str.data());
    }

    std::u16string ToUTF16(const std::string_view& str) {
        std::string input(str);
        std::wstring wstr = utility::conversions::utf8_to_utf16(input);
        return std::u16string(wstr.begin(), wstr.end());
    }

    std::string ToUTF8(const std::u16string_view& str) {
        std::u16string u16(str);
        std::wstring wstr(u16.begin(), u16.end());
        return utility::conversions::utf16_to_utf8(wstr);
    }
#else
    namespace {
        jclass GetStringClass(JNIEnv* env) {
            static jclass stringClass = nullptr;
            if (stringClass) return stringClass;

            jclass localClass = env->FindClass("java/lang/String");
            if (!localClass) return nullptr;
            stringClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass));
            env->DeleteLocalRef(localClass);
            return stringClass;
        }

        jstring GetUtf8CharsetName(JNIEnv* env) {
            static jstring utf8Charset = nullptr;
            if (utf8Charset) return utf8Charset;

            jstring localUtf8 = env->NewStringUTF("UTF-8");
            if (!localUtf8) return nullptr;
            utf8Charset = reinterpret_cast<jstring>(env->NewGlobalRef(localUtf8));
            env->DeleteLocalRef(localUtf8);
            return utf8Charset;
        }
    }

    std::u16string ToUTF16(const std::string_view& str) {
#if defined(__ANDROID__) && __ANDROID_API__ >= 31
        UErrorCode status = U_ZERO_ERROR;
        int32_t outLen = 0;
        u_strFromUTF8(nullptr, 0, &outLen, str.data(), static_cast<int32_t>(str.size()), &status);
        if (status != U_BUFFER_OVERFLOW_ERROR && U_FAILURE(status)) return {};

        status = U_ZERO_ERROR;
        std::u16string out(outLen, u'\0');
        u_strFromUTF8(
                reinterpret_cast<UChar*>(out.data()),
                outLen,
                &outLen,
                str.data(),
                static_cast<int32_t>(str.size()),
                &status);
        if (U_FAILURE(status)) return {};
        out.resize(outLen);
        return out;
#else
        JNIEnv* env = GetJNIEnv();
        if (!env) return {};

        jclass stringClass = GetStringClass(env);
        jstring utf8Charset = GetUtf8CharsetName(env);
        if (!stringClass || !utf8Charset) return {};

        jmethodID ctor = env->GetMethodID(stringClass, "<init>", "([BLjava/lang/String;)V");
        if (!ctor) return {};

        jbyteArray bytes = env->NewByteArray(static_cast<jsize>(str.size()));
        if (!bytes) return {};
        env->SetByteArrayRegion(bytes, 0, static_cast<jsize>(str.size()), reinterpret_cast<const jbyte*>(str.data()));

        jstring jstr = reinterpret_cast<jstring>(env->NewObject(stringClass, ctor, bytes, utf8Charset));
        env->DeleteLocalRef(bytes);
        if (!jstr || env->ExceptionCheck()) {
            env->ExceptionClear();
            if (jstr) env->DeleteLocalRef(jstr);
            return {};
        }

        const jsize len = env->GetStringLength(jstr);
        const jchar* chars = env->GetStringChars(jstr, nullptr);
        if (!chars) {
            env->DeleteLocalRef(jstr);
            return {};
        }

        std::u16string out(reinterpret_cast<const char16_t*>(chars), reinterpret_cast<const char16_t*>(chars) + len);
        env->ReleaseStringChars(jstr, chars);
        env->DeleteLocalRef(jstr);
        return out;
#endif
    }

    std::string ToUTF8(const std::u16string_view& str) {
#if defined(__ANDROID__) && __ANDROID_API__ >= 31
        UErrorCode status = U_ZERO_ERROR;
        int32_t outLen = 0;
        u_strToUTF8(nullptr, 0, &outLen, reinterpret_cast<const UChar*>(str.data()), static_cast<int32_t>(str.size()), &status);
        if (status != U_BUFFER_OVERFLOW_ERROR && U_FAILURE(status)) return {};

        status = U_ZERO_ERROR;
        std::string out(outLen, '\0');
        u_strToUTF8(
                out.data(),
                outLen,
                &outLen,
                reinterpret_cast<const UChar*>(str.data()),
                static_cast<int32_t>(str.size()),
                &status);
        if (U_FAILURE(status)) return {};
        out.resize(outLen);
        return out;
#else
        JNIEnv* env = GetJNIEnv();
        if (!env) return {};

        jclass stringClass = GetStringClass(env);
        jstring utf8Charset = GetUtf8CharsetName(env);
        if (!stringClass || !utf8Charset) return {};

        jstring jstr = env->NewString(reinterpret_cast<const jchar*>(str.data()), static_cast<jsize>(str.size()));
        if (!jstr) return {};

        jmethodID getBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B");
        if (!getBytes) {
            env->DeleteLocalRef(jstr);
            return {};
        }

        jbyteArray bytes = reinterpret_cast<jbyteArray>(env->CallObjectMethod(jstr, getBytes, utf8Charset));
        env->DeleteLocalRef(jstr);
        if (!bytes || env->ExceptionCheck()) {
            env->ExceptionClear();
            if (bytes) env->DeleteLocalRef(bytes);
            return {};
        }

        const jsize len = env->GetArrayLength(bytes);
        std::string out(static_cast<size_t>(len), '\0');
        env->GetByteArrayRegion(bytes, 0, len, reinterpret_cast<jbyte*>(out.data()));
        env->DeleteLocalRef(bytes);
        return out;
#endif
    }
#endif

#ifndef GKMS_WINDOWS
    JNIEnv* GetJNIEnv() {
        if (!g_javaVM) return nullptr;
        JNIEnv* env = nullptr;
        if (g_javaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
            int status = g_javaVM->AttachCurrentThread(&env, nullptr);
            if (status < 0) {
                return nullptr;
            }
        }
        return env;
    }
#endif

    CSEnum::CSEnum(const std::string& name, const int value) {
        this->Add(name, value);
    }

    CSEnum::CSEnum(const std::vector<std::string>& names, const std::vector<int>& values) {
        if (names.size() != values.size()) return;
        this->names = names;
        this->values = values;
    }

    int CSEnum::GetIndex() {
        return currIndex;
    }

    void CSEnum::SetIndex(int index) {
        if (index < 0) return;
        if (index + 1 >= values.size()) return;
        currIndex = index;
    }

    int CSEnum::GetTotalLength() {
        return values.size();
    }

    void CSEnum::Add(const std::string &name, const int value) {
        this->names.push_back(name);
        this->values.push_back(value);
    }

    std::pair<std::string, int> CSEnum::GetCurrent() {
        return std::make_pair(names[currIndex], values[currIndex]);
    }

    std::pair<std::string, int> CSEnum::Last() {
        const auto maxIndex = this->GetTotalLength() - 1;
        if (currIndex <= 0) {
            currIndex = maxIndex;
        }
        else {
            currIndex--;
        }
        return this->GetCurrent();
    }

    std::pair<std::string, int> CSEnum::Next() {
        const auto maxIndex = this->GetTotalLength() - 1;
        if (currIndex >= maxIndex) {
            currIndex = 0;
        }
        else {
            currIndex++;
        }
        return this->GetCurrent();
    }

    int CSEnum::GetValueByName(const std::string &name) {
        for (int i = 0; i < names.size(); i++) {
            if (names[i] == name) {
                return values[i];
            }
        }
        return values[0];
    }


    namespace StringFormat {
        template<typename... Args>
        std::string string_format(const std::string& fmt, Args&&... args) {
            // return std::vformat(fmt, std::make_format_args(std::forward<Args>(args)...));
            return fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...);
        }


        template <std::size_t N, std::size_t... Indices, typename T>
        auto vectorToTupleImpl(const std::vector<T>& vec, std::index_sequence<Indices...>) {
            if (vec.size() != N) {
                // printf("vec.size: %zu, N: %zu\n", vec.size(), N);
                throw std::out_of_range("Vector size does not match tuple size.");
            }
            return std::make_tuple(vec[Indices]...);
        }

        template <std::size_t N, typename T>
        auto vectorToTuple(const std::vector<T>& vec) {
            return vectorToTupleImpl<N>(vec, std::make_index_sequence<N>{});
        }


        template <typename T>
        std::string stringFormat(const std::string& fmt, const std::vector<T>& vec) {
            std::string ret = fmt;

#define CASE_ARG_COUNT(N) \
    case N: {\
        auto tp = vectorToTuple<N>(vec); \
        std::apply([&](auto&&... args) { \
            ret = string_format(fmt, args...); \
        }, tp); } break;

            switch (vec.size()) {
                CASE_ARG_COUNT(1)
                CASE_ARG_COUNT(2)
                CASE_ARG_COUNT(3)
                CASE_ARG_COUNT(4)
                CASE_ARG_COUNT(5)
                CASE_ARG_COUNT(6)
                CASE_ARG_COUNT(7)
                CASE_ARG_COUNT(8)
                CASE_ARG_COUNT(9)
                CASE_ARG_COUNT(10)
                CASE_ARG_COUNT(11)
                CASE_ARG_COUNT(12)
                CASE_ARG_COUNT(13)
                CASE_ARG_COUNT(14)
                CASE_ARG_COUNT(15)
                CASE_ARG_COUNT(16)
                CASE_ARG_COUNT(17)
                CASE_ARG_COUNT(18)
                CASE_ARG_COUNT(19)
                CASE_ARG_COUNT(20)
                CASE_ARG_COUNT(21)
                CASE_ARG_COUNT(22)
                CASE_ARG_COUNT(23)
                CASE_ARG_COUNT(24)
            }
            return ret;
        }

        std::string stringFormatString(const std::string& fmt, const std::vector<std::string>& vec) {
            try {
                return stringFormat(fmt, vec);
            }
            catch (std::exception& e) {
                return fmt;
            }
        }

        std::vector<std::string> split(const std::string& str, char delimiter) {
            std::vector<std::string> result;
            std::string current;
            for (char c : str) {
                if (c == delimiter) {
                    if (!current.empty()) {
                        result.push_back(current);
                    }
                    current.clear();
                } else {
                    current += c;
                }
            }
            if (!current.empty()) {
                result.push_back(current);
            }
            return result;
        }

        std::pair<std::string, std::string> split_once(const std::string& str, const std::string& delimiter) {
            size_t pos = str.find(delimiter);
            if (pos != std::string::npos) {
                return {str.substr(0, pos), str.substr(pos + delimiter.size())};
            }
            return {str, ""};
        }
    }

}
