// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default

#include "xt9cpime.h"
#include "xt9languagemap.h"
#include "xt9dbfile.h"
#include "xt9callbacks.h"
#include <QStandardPaths>
#include <QLoggingCategory>

QT_BEGIN_NAMESPACE
namespace QtVirtualKeyboard {

static const ET9SYMB CANGJIE_MAP[] = {
    0x65E5, 0x6708, 0x91D1, 0x6728, 0x6C34, 0x706B, 0x571F, 0x7AF9, 0x6208,
    0x5341, 0x5927, 0x4E2D, 0x4E00, 0x5F13, 0x4EBA, 0x5FC3, 0x624B, 0x53E3,
    0x5C38, 0x5EFF, 0x5C71, 0x5973, 0x7530, 0x96E3, 0x535C };
static const size_t CANGJIE_MAP_SIZE = sizeof(CANGJIE_MAP) / sizeof(ET9SYMB);
static const ET9SYMB CANGJIE_WILDCARD_SYMB = 0x91CD;

static ET9SYMB GetCangjieMappingSymb(ET9SYMB symb)
{
    if (symb == CANGJIE_WILDCARD_SYMB)
        return ET9CPCANGJIEWILDCARD;

    for (size_t i = 0; i < CANGJIE_MAP_SIZE; i++) {
        if (CANGJIE_MAP[i] == symb)
            return static_cast<ET9SYMB>(i + 'a');
    }

    return symb;
}

static ET9SYMB GetCangjieSymb(ET9SYMB symb)
{
    if (symb == ET9CPCANGJIEWILDCARD)
        return CANGJIE_WILDCARD_SYMB;

    if (ET9CPIsCangJieLowerSymbol(symb))
        return CANGJIE_MAP[symb - 'a'];

    if (ET9CPIsCangJieUpperSymbol(symb))
        return CANGJIE_MAP[symb - 'A'];

    return symb;
}

class CangjieConverter : public Xt9KeyboardGenerator::CodeConverter {
public:
    QString convertTo(const QString &codes) const override
    {
        QVector<ushort> cangjieBuf(codes.size());
        for (int i = 0; i < codes.size(); ++i) {
            cangjieBuf[i] = GetCangjieSymb(codes.at(i).unicode());
        }
        return QString::fromUtf16(reinterpret_cast<const char16_t *>(cangjieBuf.constData()), cangjieBuf.size());
    }

    QString convertFrom(const QString &codes) const override
    {
        QVector<ushort> cangjieBuf(codes.size());
        for (int i = 0; i < codes.size(); ++i) {
            cangjieBuf[i] = GetCangjieMappingSymb(codes.at(i).unicode());
        }
        return QString::fromUtf16(reinterpret_cast<const char16_t *>(cangjieBuf.constData()), cangjieBuf.size());
    }
};

Q_GLOBAL_STATIC(CangjieConverter, cangjieConverter)

Xt9CpIme::Xt9CpIme(Xt9RequestCallback *requestCallback) :
    Xt9Ime(requestCallback)
{
}

Xt9KeyboardGenerator::CodeConverter *Xt9CpIme::getCangjieConverter()
{
    return cangjieConverter;
}

void Xt9CpIme::sysInit()
{
    Xt9Ime::sysInit();
    memset(&sLingInfo, 0, sizeof(sLingInfo));
    XT9_API(ET9CPSysInit, &sLingInfo, &sWordSymbInfo, this);
}

bool Xt9CpIme::ldbInit(ET9U32 dwFirstLdbNum, ET9U32 dwSecondLdbNum, ET9U32 eInputMode)
{
    ET9STATUS eStatus;
    Q_UNUSED(dwSecondLdbNum)

    eStatus = XT9_API(ET9CPLdbInit, &sLingInfo, dwFirstLdbNum, &ET9CPLdbReadData);
    if (!eStatus) {
        XT9_API(ET9CPSetInputMode, &sLingInfo, static_cast<ET9CPMode>(eInputMode));
        XT9_API(ET9CPClearComponent, &sLingInfo);
        XT9_API(ET9CPSetBilingual, &sLingInfo);
    }

    return !eStatus;
}

qint64 Xt9CpIme::dlmPreferredSize() const
{
    return ET9CPDLM_SIZE_NORMAL;
}

bool Xt9CpIme::dlmInit(void *data, qint64 size)
{
    ET9STATUS eStatus;

    eStatus = XT9_API(ET9CPDLMInit, &sLingInfo, static_cast<ET9CPDLM_info *>(data), static_cast<ET9U32>(size), nullptr);

    return !eStatus;
}

QString Xt9CpIme::exactWord(int *wordCompLen)
{
    QString exactWord = Xt9Ime::exactWord(wordCompLen);

    ET9CPPhrase phrase;
    ET9CPSpell spell;
    ET9U8 selSymbCount = 0;
    if (!XT9_API(ET9CPGetSelection, &sLingInfo, &phrase, &spell, &selSymbCount) && selSymbCount)
        exactWord.remove(0, static_cast<int>(selSymbCount));

    replaceSpecialSymbol(exactWord);

    return exactWord;
}

void Xt9CpIme::replaceSpecialSymbol(QString &exactWord) const
{
    for (int i = 0; i < exactWord.length(); ++i) {
        ET9SYMB symb = exactWord.at(i).unicode();
        if (ET9CPSymToCPTone(symb)) {
            exactWord.replace(i, 1, QChar(symb - ET9CPTONE1 + '1'));
        } else if (ET9CPIsStrokeSymbol(symb)) {
            int strokeIndex = symb - ET9CPSTROKE1;
            static const ushort STROKE_TABLE[ET9CPSTROKEWILDCARD - ET9CPSTROKE1 + 1] = {
                0x4E00,
                0x4E28,
                0x4E3F,
                0x4E36,
                0x4E5B,
                '*'
            };
            exactWord.replace(i, 1, QChar(STROKE_TABLE[strokeIndex]));
        }
    }
}

QString Xt9CpIme::spell()
{
    ET9STATUS eStatus;
    ET9CPSpell spell;

    eStatus = XT9_API(ET9CPGetSpell, &sLingInfo, &spell);
    if (eStatus == ET9STATUS_NEED_SELLIST_BUILD) {
        ET9U16 gestureValue;
        eStatus = XT9_API(ET9CPBuildSelectionList, &sLingInfo, &gestureValue);
        if (eStatus)
            return QString();

        eStatus = XT9_API(ET9CPGetSpell, &sLingInfo, &spell);
        if (eStatus)
            return QString();
    } else if (eStatus) {
        return QString();
    }

    QString result = QString::fromUtf16(reinterpret_cast<const char16_t *>(spell.pSymbs), spell.bLen);

    replaceSpecialSymbol(result);

    return result;
}

QStringList Xt9CpIme::buildSelectionList(int *defaultListIndex, ET9U16 *gestureValue, ET9STATUS &eStatus)
{
    ET9U16 totalWords;

    eStatus = XT9_API(ET9CPBuildSelectionList, &sLingInfo, gestureValue);

    if (defaultListIndex)
        *defaultListIndex = -1;

    if (eStatus)
        return QStringList();

    eStatus = XT9_API(ET9CPGetPhraseCount, &sLingInfo, &totalWords);

    if (defaultListIndex && totalWords > 0 && !exactWord().isEmpty())
        *defaultListIndex = 0;

    QStringList list;
    for (ET9U16 i = 0; i < totalWords; ++i) {
        ET9CPPhrase phrase;
        ET9CPSpell spell;
        ET9CPPhraseSource phraseSource;
        eStatus = ET9CPGetPhrase(&sLingInfo, i, &phrase, &spell, &phraseSource);
        if (eStatus)
            return QStringList();

        const QString word = QString::fromUtf16(reinterpret_cast<const char16_t *>(phrase.pSymbs), phrase.bLen);

        list.append(word);
    }

    return list;
}

ET9STATUS Xt9CpIme::selectWord(int index)
{
    ET9CPPhrase phrase;
    ET9CPSpell spell;
    ET9CPPhraseSource phraseSource;
    ET9STATUS eStatus;

    qCDebug(lcXT9) << "selectWord" << index;

    eStatus = XT9_API(ET9CPGetPhrase, &sLingInfo, static_cast<ET9U16>(index), &phrase, &spell, &phraseSource);
    if (!eStatus) {
        eStatus = XT9_API(ET9CPSelectPhrase, &sLingInfo, static_cast<ET9U16>(index), &spell);
    }

    return eStatus;
}

void Xt9CpIme::cursorMoved()
{
    const ET9U32 maxBufLen = 456;
    ET9SYMB buffer[maxBufLen];
    ET9_Request request;

    request.eType = ET9_REQ_BufferContext;
    request.data.sBufferContextInfo.psBuf = buffer;
    request.data.sBufferContextInfo.dwMaxBufLen = maxBufLen;
    request.data.sBufferContextInfo.dwBufLen = static_cast<ET9U32>(-1);

    _requestCallback->request(&request);

    if (request.data.sBufferContextInfo.dwBufLen != static_cast<ET9U32>(-1))
        ET9CPSetContext(&sLingInfo, request.data.sBufferContextInfo.psBuf, request.data.sBufferContextInfo.dwBufLen);
    else
        ET9CPSetContext(&sLingInfo, request.data.sBufferContextInfo.psBuf, 0);
}

void Xt9CpIme::commitSelection()
{
    XT9_API(ET9CPCommitSelection, &sLingInfo);
}

ET9SYMB Xt9CpIme::lastSymb()
{
    const QString word = Xt9Ime::exactWord();
    const int wordLength = word.length();
    return wordLength > 0 ? word.at(wordLength - 1).unicode() : 0;
}

bool Xt9CpIme::addTone(ET9CPSYMB symb)
{
    ET9STATUS eStatus;

    eStatus = XT9_API(ET9AddExplicitSymb, sLingInfo.Base.pWordSymbInfo, symb, 0, ET9NOSHIFT, ET9_NO_ACTIVE_INDEX);
    if (eStatus)
        return false;

    eStatus = XT9_API(ET9CPBuildSelectionList, &sLingInfo, nullptr);
    if (eStatus) {
        XT9_API(ET9ClearOneSymb, sLingInfo.Base.pWordSymbInfo);
        return false;
    }

    return true;
}

ET9STATUS Xt9CpIme::ET9CPLdbReadData(ET9CPLingInfo *pLingInfo, ET9U8 *ET9FARDATA *ppbSrc, ET9U32 *pdwSizeInBytes)
{
    return reinterpret_cast<Xt9CpIme *>(pLingInfo->pPublicExtension)->ldbReadData(pLingInfo->dwLdbNum, ppbSrc, pdwSizeInBytes);
}

} // namespace QtVirtualKeyboard
QT_END_NAMESPACE
