#pragma once
//////////////////////////////////////////////////////
// For Internationalization
//
// [Usage - Translation]
//
//  Translation::load("plugins/xxx/lang.json");
//  ...
//  tr("There are {0} days before {1} to come back", 3, "alex");          // return translated string [std::string]
//  trc("There are {0} days before {1} to come back", 3, "alex");         // return translated string [const char*]
//
//  ** In Translation File: plugins/xxx/lang.json
//  {
//      "zh_CN": {
//          "There are {0} days before {1} to come back": "在{1}回来前还剩{0}天",
//          "...": "...",
//          "...": "..."
//      }
//  }
//
//
// [Usage - Text Encoding]
//
//  Encoding local = TextEncoding::getLocalEncoding();                      // Get local encoding
//  Encoding code = TextEncoding::detectEncoding("你好吗?");                // Detect the encoding of text
//
//  string hello = TextEncoding::fromUnicode(L"Hello");                     // Convert Unicode wstring -> MBCS string
//  wstring world = TextEncoding::toUnicode("World");                       // Convert MBCS string -> Unicode wstring
//
//  string tomorrow = TextEncoding::toUTF8("明天");                          // Convert MBCS string of any encoding to UTF8 string
//  string tonight = TextEncoding::convert("今天晚上", Encoding::CHINESE_GB, Encoding::UTF8);
//                                                                          // Convert from one MBCS encoding to another
//
//////////////////////////////////////////////////////

#include "Global.h"
#include "LLAPI.h"
#include "Utils/FileHelper.h"
#include "Utils/PluginOwnData.h"
#include "third-party/Nlohmann/json.hpp"
#include "third-party/FMT/core.h"
#include "third-party/FMT/os.h"
#include <string>
#include "Utils/StringHelper.h"

/**
 * @brief I18nBase API class.
 *
 */
class I18nBase {

public:
    using SubLangData = std::unordered_map<std::string, std::string>;
    using LangData = std::map<std::string, SubLangData>;

    enum class Type : char
    {
        None,
        SingleFile,
        MultiFile,
        Custom,
    };

    Type type = Type::None;
    LangData langData;
    LangData defaultLangData;
    std::string defaultLocaleName = "en_US";

    virtual ~I18nBase() = default;

    /**
     * @brief Get the translation of the specified key.
     *
     * @param  key          The language key
     * @param  localeName   The language code like en_US,zh_CN("" => this->defaultLocaleName)
     * @return std::string  The translation
     * @see    I18nBase::defaultLocaleName
     */
    LIAPI virtual std::string get(const std::string& key, const std::string& localeName = "");

    /**
     * @breif Get the type of the i18n object.
     *
     * @return  The type of the i18n object
     */
    LIAPI virtual Type getType() = 0;

    /**
     * @brief Get the default language code of the i18n object.
     *
     * @return  The default language code of the i18n object
     */
    LIAPI virtual std::string getDefaultLocaleName();

    /**
     * @brief Clone a new i18n object.
     *
     * @return  The new i18n object.
     */
    LIAPI virtual I18nBase* clone();
    
    static const constexpr char* POD_KEY = "ll_plugin_i18n"; ///< PluginOwnData key
};

/**
 * @brief Lightweight and simple I18nBase support.
 *
 * @note  Use this, all the language data will be saved in a single JSON file.
 *        So it is not recommended to use it in large plugins(that have a lot of strings to translate)
 */
class SingleFileI18N : public I18nBase {

public:
    std::string filePath;

    LIAPI void load(const std::string& fileName);
    LIAPI void save();

    SingleFileI18N() {
        this->type = Type::SingleFile;
    }
    /**
     * @brief Construct a SingleFileI18N object.
     *
     * @param filePath         The path to the i18n file(json)
     * @param pattern          The i18n file pattern(SingleFile I18nBase supports `Mode::Normal` and `Mode::Normal`
     * @param defaultLocaleName  The default language code(if empty, default the system default language)
     * @param defaultLangData  The default translation data
     */
    SingleFileI18N(const std::string& filePath, const std::string& defaultLocaleName = "",
               const LangData& defaultLangData = {})
    : filePath(filePath) {
        this->type = Type::SingleFile;
        this->defaultLangData = defaultLangData;
        if (defaultLocaleName.empty()) {
            this->defaultLocaleName = GetSystemLocaleName();
        } else {
            this->defaultLocaleName = defaultLocaleName;
        }
        load(filePath);
    }
    /// Copy constructor
    SingleFileI18N(const SingleFileI18N& other) {
        *this = other;
    }
    ~SingleFileI18N() = default;

    LIAPI Type getType();
};

class MultiFileI18N : public I18nBase {

public:
    std::string dirPath;

    LIAPI void load(const std::string& dirName);
    LIAPI void save(bool nested = false);

    MultiFileI18N() {
        this->type = Type::MultiFile;
    }
    /**
     * @brief Construct a heavy I18nBase object.
     *
     * @param dirPath          The path to the i18n dir
     * @param pattern          The i18n file pattern
     * @param defaultLocaleName  The default language code
     * @param defaultLangData  The default translation data
     */
    MultiFileI18N(const std::string& dirPath, const std::string& defaultLocaleName = "",
              const LangData& defaultLangData = {})
    : dirPath(dirPath) {
        this->type = Type::MultiFile;
        this->defaultLangData = defaultLangData;
        if (defaultLocaleName.empty()) {
            this->defaultLocaleName = GetSystemLocaleName();
        } else {
            this->defaultLocaleName = defaultLocaleName;
        }
        load(dirPath);
    }
    /// Copy constructor
    MultiFileI18N(const MultiFileI18N& other) {
        *this = other;
    }
    ~MultiFileI18N() = default;

    LIAPI Type getType();
};

#ifdef UNICODE
#include "third-party/compact_enc_det/compact_enc_det.h"
#define UNICODE
#else
#include "third-party/compact_enc_det/compact_enc_det.h"
#endif

namespace Translation {
template <bool B, class T = void>
using enable_if_t = typename std::enable_if<B, T>::type;

///////////////// tr Impl /////////////////
template <typename S, typename... Args, Translation::enable_if_t<(fmt::v8::detail::is_string<S>::value), int> = 0>
inline std::string trlImpl(HMODULE hPlugin, const std::string& localeName, const S& formatStr, Args&&... args) {
    std::string realFormatStr = formatStr;
    if (PluginOwnData::hasImpl(hPlugin, I18nBase::POD_KEY)) {
        auto& i18n = PluginOwnData::getImpl<I18nBase>(hPlugin, I18nBase::POD_KEY);
        realFormatStr = i18n.get(formatStr, localeName);
        if (realFormatStr == formatStr) {
            // If failed and the str dosn't match the args count, avoid fmt to avoid errors
            auto argSz = sizeof...(args);
            bool lastIsBracket = false;
            size_t cnt = 0;
            for (auto& c : formatStr) {
                if (c == '{') {
                    if (lastIsBracket) {
                        cnt--;
                        lastIsBracket = false;
                    } else {
                        cnt++;
                        lastIsBracket = true;
                        continue;
                    }
                }
                if (lastIsBracket) {
                    lastIsBracket = false;
                }
            }
            if (cnt != argSz) {
                return formatStr;
            }
        }
    }
    // realFormatStr = FixCurlyBracket(realFormatStr);
    if constexpr (0 == sizeof...(args)) {
        // Avoid fmt if only one argument
        return realFormatStr;
    } else {
        return fmt::format(realFormatStr, std::forward<Args>(args)...);
    }
}
template <typename S, typename... Args, Translation::enable_if_t<(fmt::v8::detail::is_string<S>::value), int> = 0>
inline std::string trImpl(HMODULE hPlugin, const S& formatStr, Args&&... args) {
    return trlImpl(hPlugin, "", formatStr, std::forward<Args>(args)...);
}

///////////////// trc Impl /////////////////
template <typename S, typename... Args, Translation::enable_if_t<(fmt::v8::detail::is_string<S>::value), int> = 0>
[[deprecated("Use trImpl(...).c_str() instead")]] inline const char* trcImpl(HMODULE hPlugin, const S& formatStr,
                                                                             const Args&... args) {
    std::string res = trImpl(hPlugin, formatStr, args...);
    std::string name =
        std::string(I18nBase::POD_KEY) + "_translation_" + fmt::v8::detail::to_string_view<S>(formatStr).data();
    auto& str = PluginOwnData::setImpl<std::string>(hPlugin, name, res);
    return str.c_str();
}

LIAPI I18nBase* loadI18nImpl(HMODULE hPlugin, const std::string& path, const std::string& defaultLocaleName,
               const I18nBase::LangData& defaultLangData);

LIAPI I18nBase* loadFromImpl(HMODULE hPlugin, HMODULE hTarget);

/**
 * @brief Load translation from a file or dir.
 *
 * @param  path               The path to the i18n file(json) or dir
 * @param  defaultLocaleName  The default language code(if no lang code is specified, it will use this)
 * @param  defaultLangData    The default translation data
 * @return I18nBase*              The pointer to the I18nBase object in PluginOwnData, null if failed
 * @par Example
 * 1. SingleFileI18N (1)
 * @code
 * // In the file plugins/xxx/language.json:
 * // {"zh_CN": {"text": "文本"}, "en_US": {"text", "text"}}
 * Translation::load("plugins/xxx/language.json");
 * tr("text");
 * @endcode
 * 2. SingleFileI18N (2)
 * @code
 * // In the file plugins/xxx/language.json:
 * // {"zh_CN": {"a.b.c.id.text": "文本"}, "en_US": {"a.b.c.id.text", "text"}}
 * Translation::load("plugins/xxx/language.json");
 * tr("a.b.c.d.id.text");
 * @endcode
 * 3. MultiFileI18N (1)
 * @code
 * // In the file plugins/xxx/LangPack/en.json:
 * // {"text": "text"}
 * // In the file plugins/xxx/LangPack/zh_CN.json:
 * // {"text": "文本"}
 * Translation::load("plugins/xxx/LangPack/");
 * tr("text");
 * @endcode
 * 4. MultiFileI18N (2)
 * @code
 * // In the file plugins/xxx/LangPack/en.json:
 * // {"a.b.c.d.text1": "text"}
 * // In the file plugins/xxx/LangPack/zh_CN.json:
 * // {"a.b.c.d.text1": "文本"}
 * Translation::load("plugins/xxx/LangPack/");
 * tr("a.b.c.d.text1");
 * @endcode
 * 5. MultiFileI18N Nested (3)
 * @code
 * // In the file plugins/xxx/LangPack/en.json:
 * // {"a": {"b": {"c": {"d": {"text1": "text"}}}}}
 * // In the file plugins/xxx/LangPack/zh_CN.json:
 * // {"a": {"b": {"c": {"d": {"text1": "文本"}}}}}
 * Translation::load("plugins/xxx/LangPack/");
 * tr("a.b.c.d.text1");
 * @endcode
 */
inline I18nBase* load(const std::string& path,
                  const std::string& defaultLocaleName = "",
                  const I18nBase::LangData& defaultLangData = {}) {
    return loadI18nImpl(GetCurrentModule(), path, defaultLocaleName, defaultLangData);
}

/**
 * Load i18n with custom i18n type.
 * 
 * @param args...  The args to pass to the i18n type constructor
 * @return I18nBase*   The pointer to the I18nBase object in PluginOwnData, null if failed
 */
template <typename T, typename... Args>
inline I18nBase* load(Args&&... args) {
    try {
        I18nBase* res = new T(std::forward<Args>(args)...);
        return &PluginOwnData::setWithoutNewImpl<I18nBase>(GetCurrentModule(), I18nBase::POD_KEY, res);
    } catch (...) {}
    return nullptr;
}

/**
 * @brief Load translation from another plugin.
 *
 * @param  plugin  The plugin name.
 * @return I18nBase*   The pointer to the I18nBase object in PluginOwnData, null if failed
 */
inline I18nBase* loadFrom(const std::string& plugin) {
    if (LL::hasPlugin(plugin)) {
        auto p = LL::getPlugin(plugin);
        if (p) {
            return loadFromImpl(GetCurrentModule(), p->handle);
        }
    }
    return nullptr;
}

/**
 * @brief Get the I18nBase object of a certain plugin.
 *
 * @param  hPlugin  The plugin handle(nullptr -> GetCurrentModule())
 * @return I18nBase*    The I18nBase pointer
 */
inline I18nBase* getI18N(HMODULE hPlugin = nullptr) {
    auto handle = (hPlugin == nullptr ? GetCurrentModule() : hPlugin);
    if (handle && PluginOwnData::hasImpl(handle, I18nBase::POD_KEY)) {
        return &PluginOwnData::getImpl<I18nBase>(handle, I18nBase::POD_KEY);
    }
    return nullptr;
}

}; // namespace Translation

/**
 * @brief Translate a str.
 *
 * @tparam S            The string type
 * @tparam Args         ...
 * @param  formatStr    The str to translate and format
 * @param  args         The format arguments
 * @return std::string  The translated str
 * @see    fmt::format
 * @see    https://fmt.dev/latest/index.html
 * @par Example
 * @code
 * tr(std::string("There are {0} days before {1} to come back"), 3, "alex");
 * @endcode
 */
template <typename S, typename... Args, Translation::enable_if_t<(fmt::v8::detail::is_string<S>::value), int> = 0>
inline std::string tr(const S& formatStr, Args&&... args) {
    return Translation::trImpl(GetCurrentModule(), formatStr, std::forward<Args>(args)...);
}

/**
 * @brief Translate a str.
 *
 * @tparam Args         ...
 * @param  formatStr    The str to translate and format
 * @param  args         The format arguments
 * @return std::string  The translated str
 * @see    fmt::format
 * @see    https://fmt.dev/latest/index.html
 * @par Example
 * @code
 * tr("There are {0} days before {1} to come back", 3, "alex");
 * @endcode
 */
template <typename... Args>
inline std::string tr(const char* formatStr, Args&&... args) {
    return tr(std::string(formatStr), std::forward<Args>(args)...);
}

/**
 * @brief Translate a str(c-style str).
 *
 * @tparam S            The string type
 * @tparam Args         ...
 * @param  formatStr    The str to translate and format
 * @param  args         The format arguments
 * @return const char*  The translated str(c-style str)
 * @see    fmt::format
 * @see    https://fmt.dev/latest/index.html
 * @par Example
 * @code
 * trc(std::string("There are {0} days before {1} to come back"), 3, "alex");
 * @endcode
 */
template <typename S, typename... Args, Translation::enable_if_t<(fmt::v8::detail::is_string<S>::value), int> = 0>
inline const char* trc(const S& formatStr, Args&&... args) {
    return Translation::trcImpl(GetCurrentModule(), formatStr, std::forward<Args>(args)...);
}

/**
 * @brief Translate a str(c-style str).
 *
 * @tparam Args         ...
 * @param  formatStr    The str to translate and format
 * @param  args         The format arguments
 * @return const char*  The translated str(c-style str)
 * @see    fmt::format
 * @see    https://fmt.dev/latest/index.html
 * @par Example
 * @code
 * trc("There are {0} days before {1} to come back", 3, "alex");
 * @endcode
 */
template <typename... Args>
inline const char* trc(const char* formatStr, Args&&... args) {
    return trc(std::string(formatStr), std::forward<Args>(args)...);
}

/**
 * @brief Translate a str to the specified language.
 *
 * @tparam S            The string type
 * @tparam Args         ...
 * @param  localeName   The language code like en_US
 * @param  formatStr    The str to translate and format
 * @param  args         The format arguments
 * @return std::string  The translated str
 * @see    fmt::format
 * @see    https://fmt.dev/latest/index.html
 * @par Example
 * @code
 * trl("zh_CN", "There are {0} days before {1} to come back", 3, "alex");
 * @endcode
 */
template <typename S, typename... Args>
inline std::string trl(const std::string& localeName, const S& formatStr, Args&&... args) {
    return Translation::trlImpl(GetCurrentModule(), localeName, formatStr, std::forward<Args>(args)...);
}

/**
 * @brief Translate a str to the specified language.
 *
 * @tparam Args         ...
 * @param  localeName   The language code like en_US
 * @param  formatStr    The str to translate and format(c-style)
 * @param  args         The format arguments
 * @return std::string  The translated str
 * @see    fmt::format
 * @see    https://fmt.dev/latest/index.html
 * @par Example
 * @code
 * trl("zh_CN", "There are {0} days before {1} to come back", 3, "alex");
 * @endcode
 */
template <typename... Args>
inline std::string trl(const std::string& localeName, const char* formatStr, Args&&... args) {
    return trl(localeName, std::string(formatStr), std::forward<Args>(args)...);
}

namespace Translation {
namespace literals {

inline std::string operator""_tr(const char* str, size_t) {
    return tr(str);
}

} // namespace literals
} // namespace Translation

// For text encoding
namespace TextEncoding {
LIAPI Encoding getLocalEncoding();
LIAPI Encoding detectEncoding(const std::string& text, bool* isReliable = nullptr);

LIAPI std::string fromUnicode(const std::wstring& text, Encoding to = Encoding::UTF8);
LIAPI std::wstring toUnicode(const std::string& text, Encoding from = Encoding::UTF8);
LIAPI std::string toUTF8(const std::string& text);
LIAPI std::string toUTF8(const std::string& text, Encoding from);

LIAPI std::string convert(const std::string& text, Encoding from, Encoding to);
} // namespace TextEncoding