#include "drw_textcodec.h"
#include "../drw_base.h"
#include <cstring>
#include <QCoreApplication>
#include <QDebug>
#include <QSet>
#include <QString>

#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
#include <qtpreprocessorsupport.h>
#endif

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#ifdef WITH_TEXTCODEC
#include "../vmisc/codecs/qtextcodec.h"
#else
#include "vtextcodec.h"
using QTextCodec = VTextCodec;
#endif // WITH_TEXTCODEC
#else
#include <QTextCodec>
#endif // QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)

#include "../vmisc/exception/vexception.h"
#include "../vmisc/vabstractapplication.h"

DRW_TextCodec::DRW_TextCodec()
  : version(DRW::AC1021)
{
}

void DRW_TextCodec::setVersion(DRW::Version v, bool dxfFormat)
{
    switch (v)
    {
        case DRW::UNKNOWNV:
        case DRW::MC00:
        case DRW::AC12:
        case DRW::AC14:
        case DRW::AC150:
        case DRW::AC210:
        case DRW::AC1002:
        case DRW::AC1003:
        case DRW::AC1004:
            // unhandled?
            break;
        case DRW::AC1006:
        case DRW::AC1009:
        {
            version = DRW::AC1009;
            cp = "ANSI_1252";
            setCodePage(cp, dxfFormat);
            break;
        }
        case DRW::AC1012:
        case DRW::AC1014:
        case DRW::AC1015:
        case DRW::AC1018:
        {
            version = DRW::AC1015;
            //            if (cp.empty()) { //codepage not set, initialize
            cp = "ANSI_1252";
            setCodePage(cp, dxfFormat);
            //            }
            break;
        }
        case DRW::AC1021:
        case DRW::AC1024:
        case DRW::AC1027:
        case DRW::AC1032:
        {
            version = DRW::AC1021;
            if (dxfFormat)
            {
                cp = "UTF-8"; // RLZ: can be UCS2 or UTF-16 16bits per char
            }
            else
            {
                cp = "UTF-16"; // RLZ: can be UCS2 or UTF-16 16bits per char
            }
            setCodePage(cp, dxfFormat);
            break;
        }
        default:
            break;
    }
}

void DRW_TextCodec::setVersion(const std::string &v, bool dxfFormat)
{
    version = DRW::UNKNOWNV;
    for (auto [first, second] : DRW::dwgVersionStrings)
    {
        if (std::strncmp(v.c_str(), first, 32) == 0)
        {
            version = second;
            setVersion(second, dxfFormat);
            break;
        }
    }
}

void DRW_TextCodec::setCodePage(const std::string &c, bool dxfFormat)
{
    cp = correctCodePage(c);
    if (version < DRW::AC1021)
    {
        if (cp == "UTF-8")
        { // DXF older than 2007 are write in win codepages
            cp = "ANSI_1252";
        }
        conv = DRW_TextCodec::CodecForName(QString::fromStdString(cp));
    }
    else
    {
        conv = DRW_TextCodec::CodecForName(dxfFormat ? QStringLiteral("UTF-8") : QStringLiteral("UTF-16"));
    }

    if (conv == nullptr)
    {
        const QString errorMsg = QCoreApplication::translate("DRW_TextCodec", "No available codec for code page '%1'.")
                                     .arg(cp.c_str());
        VAbstractApplication::VApp()->IsPedantic()
            ? throw VException(errorMsg)
            : qWarning() << VAbstractApplication::warningMessageSignature + errorMsg;

        if (version < DRW::AC1021 && cp == "UTF-8")
        {
            cp = "ANSI_1252";
            conv = DRW_TextCodec::CodecForName(QString::fromStdString(cp)); // Fallback to latin
        }
    }
}

auto DRW_TextCodec::DXFCodePageMap() -> QMap<QString, QStringList>
{
    static auto map = QMap<QString, QStringList>{
        {"ANSI_874", {"ANSI_874", "CP874", "ISO8859-11", "TIS-620"}}, // Latin/Thai
        {"ANSI_932",
         {"ANSI_932",
          "SHIFT-JIS",
          "SHIFT_JIS",
          "CSSHIFTJIS",
          "CSWINDOWS31J",
          "MS_KANJI",
          "X-MS-CP932",
          "X-SJIS",
          "EUCJP",
          "EUC-JP",
          "CSEUCPKDFMTJAPANESE",
          "X-EUC",
          "X-EUC-JP",
          "CP932",
          "JIS7"}}, // Japanese
        {"ANSI_936",
         {"ANSI_936",
          "GBK",
          "GB2312",
          "CHINESE",
          "CN-GB",
          "CSGB2312",
          "CSGB231280",
          "CSISO58BG231280",
          "GB_2312-80",
          "GB231280",
          "GB2312-80",
          "ISO-IR-58",
          "GB18030"}},                       // Chinese PRC GBK (XGB) simplified
        {"ANSI_949", {"ANSI_949", "EUCKR"}}, // Korean
        {"ANSI_950",
         {"ANSI_950", "BIG5", "CN-BIG5", "CSBIG5", "X-X-BIG5", "BIG5-HKSCS"}}, // Chinese Big5 (Taiwan, Hong Kong SAR)
        {"ANSI_1250", {"ANSI_1250", "CP1250", "ISO8859-2"}},                   //Central Europe and Eastern Europe
        {"ANSI_1251", {"ANSI_1251", "CP1251", "ISO8859-5", "KOI8-R", "KOI8-U", "IBM 866"}}, // Cyrillic script
        {"ANSI_1252",
         {"ANSI_1252",
          "CP1252",
          "LATIN1",
          "ISO-8859-1",
          "CP819",
          "CSISO",
          "IBM819",
          "L1",
          "ISO_8859-1",
          "APPLE ROMAN",
          "ISO8859-1",
          "ISO8859-15",
          "ISO-IR-100",
          "IBM 850"}},                                                                   // Western Europe
        {"ANSI_1253", {"ANSI_1253", "CP1253", "ISO8859-7"}},                             // Greek
        {"ANSI_1254", {"ANSI_1254", "CP1254", "ISO8859-9", "iso8859-3"}},                // Turkish
        {"ANSI_1255", {"ANSI_1255", "CP1255", "ISO8859-8"}},                             // Hebrew
        {"ANSI_1256", {"ANSI_1256", "CP1256", "ISO8859-6"}},                             // Arabic
        {"ANSI_1257", {"ANSI_1257", "CP1257", "ISO8859-4", "ISO8859-10", "ISO8859-13"}}, // Baltic
        {"ANSI_1258", {"ANSI_1258", "CP1258"}},                                          // Vietnamese
        {"UTF-8", {"UTF-8", "UTF8", "UTF8-BIT"}},
        {"UTF-16", {"UTF-16", "UTF16", "UTF16-BIT"}},
    };

    return map;
}

auto DRW_TextCodec::CodecForName(const QString &name) -> QTextCodec *
{
    if (QMap<QString, QStringList> const knownCodecs = DXFCodePageMap(); knownCodecs.contains(name))
    {
        QStringList const aliases = knownCodecs.value(name);
        for (const auto &alias : aliases)
        {
            if (QTextCodec *codec = QTextCodec::codecForName(alias.toLatin1()))
            {
                return codec;
            }
        }
    }

    return nullptr;
}

auto DRW_TextCodec::toUtf8(const std::string &s) -> std::string
{
    if (conv == nullptr)
    {
        return s;
    }

    const QString encodedString = conv->toUnicode(s.c_str());
    return encodedString.toUtf8().toStdString();
}

auto DRW_TextCodec::fromUtf8(const std::string &s) -> std::string
{
    if (conv == nullptr)
    {
        return s;
    }

    const QByteArray encodedString = conv->fromUnicode(QString::fromStdString(s));
    return {encodedString.constData(), static_cast<size_t>(encodedString.size())};
}

auto DRW_TextCodec::correctCodePage(const std::string &s) -> std::string
{
    //stringstream cause crash in OS/X, bug#3597944
    QString codePage = QString::fromStdString(s);
    codePage = codePage.toUpper();
    QMap<QString, QStringList> const codeMap = DRW_TextCodec::DXFCodePageMap();

    auto i = codeMap.constBegin();
    while (i != codeMap.constEnd())
    {
        if (i.value().contains(codePage))
        {
            return i.key().toStdString();
        }
        ++i;
    }

    return "ANSI_1252";
}
