/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Virtual Keyboard module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "xt9ime.h"
#include "xt9callbacks.h"
#include "xt9languagemap.h"
#include "xt9dbfile.h"
#include <QStandardPaths>
#include <QDir>
#include <QLoggingCategory>
#include <QtVirtualKeyboard/qvirtualkeyboarddictionarymanager.h>

QT_BEGIN_NAMESPACE
namespace QtVirtualKeyboard {

Q_LOGGING_CATEGORY(lcXT9, "qt.virtualkeyboard.xt9")

Xt9Ime::Xt9Ime(Xt9RequestCallback *requestCallback, Xt9KeyboardGenerator::CodeConverter *codeConverter) :
    _requestCallback(requestCallback),
    codeConverter(codeConverter)
{
}

void Xt9Ime::sysInit()
{
    if (!ldbManager)
        ldbManager.reset(new Xt9LdbManager());
    memset(&sWordSymbInfo, 0, sizeof(sWordSymbInfo));
    memset(&sKdbInfo, 0, sizeof(sKdbInfo));
    memset(&sSearchEngine, 0, sizeof(sSearchEngine));
    XT9_API(ET9WordSymbInit, &sWordSymbInfo, 1, ET9Request, this);
    XT9_API(ET9NAV_Init, &sSearchEngine, &sWordSymbInfo);
}

bool Xt9Ime::kdbInit(const QVariantMap &vkbLayout)
{
    ET9STATUS eStatus;

    keyboardGenerator.reset(new Xt9KeyboardGenerator(vkbLayout, codeConverter));

    const ET9U32 dwFirstKdbNum = static_cast<ET9U32>(vkbLayout[Xt9KeyboardGenerator::PRIMARY_ID].toInt());

    eStatus = XT9_API(ET9KDB_Init,
                &sKdbInfo,
                &sWordSymbInfo,
                dwFirstKdbNum,
                0,
                &ET9KDBLoad,
                &ET9KDBRequest,
                this);

    return !eStatus;
}

void Xt9Ime::uninit()
{
    clearInput();
    keyboardGenerator.reset();
    ldbManager->closeAll();
    sysInit();
}

QString Xt9Ime::exactWord(int *wordCompLen)
{
    ET9SimpleWord simpleWord;
    ET9STATUS eStatus;

    eStatus = XT9_API(ET9GetExactWord, &sWordSymbInfo, &simpleWord);
    if (eStatus) {
        if (wordCompLen)
            *wordCompLen = 0;
        return QString();
    }

    if (wordCompLen)
        *wordCompLen = simpleWord.wCompLen;

    return QString::fromUtf16(reinterpret_cast<const char16_t *>(simpleWord.sString), simpleWord.wLen);
}

bool Xt9Ime::hasActiveInput() const
{
    return ET9HasActiveInput(&sWordSymbInfo);
}

QStringList Xt9Ime::buildSelectionList(int *defaultListIndex, unsigned short *gestureValue)
{
    ET9STATUS eStatus = ET9STATUS_NONE;
    return buildSelectionList(defaultListIndex, gestureValue, eStatus);
}

void Xt9Ime::cursorMoved()
{
    XT9_API(ET9CursorMoved, &sWordSymbInfo, 1);
}

void Xt9Ime::clearInput()
{
    XT9_API(ET9ClearAllSymbs, &sWordSymbInfo);
}

void Xt9Ime::setCapsLock()
{
    XT9_API(ET9SetCapsLock, &sWordSymbInfo);
}

void Xt9Ime::setShift()
{
    XT9_API(ET9SetShift, &sWordSymbInfo);
}

void Xt9Ime::setUnShift()
{
    XT9_API(ET9SetUnShift, &sWordSymbInfo);
}

void Xt9Ime::setWorkingDirectory(const QString &workingDirectory)
{
    QString dir = workingDirectory;
    if (!workingDirectory.isEmpty() && !dir.endsWith(QLatin1Char('/')))
        dir.append(QLatin1Char('/'));
    dir = QDir::toNativeSeparators(dir);
    XT9_API(ET9NAVCore_SetWorkingDirectory, &sSearchEngine, dir.toUtf8().constData());
}

bool Xt9Ime::indexExists(quint16 id)
{
    ET9BOOL exists = 0;

    XT9_API(ET9NAVCore_IndexExists, &sSearchEngine, id, &exists);

    return exists != 0;
}

bool Xt9Ime::createIndex(quint16 id, quint32 contentInfo)
{
    ET9STATUS eStatus;
    ET9NAVTypeInfo sTypeInfo;

    if (indexExists(id)) {
        removeIndex(id);
    }

    eStatus = XT9_API(ET9NAVTypeInfo_Init, &sTypeInfo, id, ET9NAVNO_RECORD_KEY_LENGTH, 0, ET9NAVSTORE_DISPLAY_STR_IN_INDEX);
    if (!eStatus) {
        eStatus = XT9_API(ET9NAVCore_CreateIndex, &sSearchEngine, id, &sTypeInfo, 1, contentInfo, 0);
    }

    return !eStatus;
}

bool Xt9Ime::insertRecord(quint16 id, const QString &phrase, const QString &tokens)
{
    ET9STATUS eStatus;
    ET9NAVRecord sRecord;

    eStatus = XT9_API(ET9NAVRecord_Init_NoKey, &sSearchEngine, &sRecord, id);
    if (!eStatus) {
        if (tokens.length() > 0) {
            eStatus = XT9_API(ET9NAVRecord_AddField_Conversion, &sRecord,
                               static_cast<const ET9SYMB *>(tokens.utf16()),
                               static_cast<quint16>(tokens.length()),
                               static_cast<const ET9SYMB *>(phrase.utf16()),
                               static_cast<quint16>(phrase.length()),
                               ET9NAVMATCHLOGIC_ANY);
        } else {
            eStatus = XT9_API(ET9NAVRecord_AddField_16BIT, &sRecord,
                               static_cast<const ET9SYMB *>(phrase.utf16()),
                               static_cast<quint16>(phrase.length()),
                               ET9NAVMATCHLOGIC_ANY);
        }

        if (!eStatus) {
            eStatus = XT9_API(ET9NAVCore_InsertRecord, &sSearchEngine, &sRecord);
        }
    }

    return !eStatus;
}

void Xt9Ime::finalizeIndex(quint16 id)
{
    XT9_API(ET9NAVCore_FinalizeIndex, &sSearchEngine, id);
}

void Xt9Ime::updateIndex(quint16 id, const QStringList &wordList)
{
    createIndex(id);
    for (const QString &word : wordList) {
        insertRecord(id, word);
    }
    finalizeIndex(id);
}

void Xt9Ime::removeIndex(quint16 id)
{
    XT9_API(ET9NAVCore_RemoveIndex, &sSearchEngine, id);
}

void Xt9Ime::removeAllIndexes()
{
    XT9_API(ET9NAVCore_RemoveAllIndexes, &sSearchEngine);
}

void Xt9Ime::mountIndex(quint16 id)
{
    XT9_API(ET9NAVCore_MountIndex, &sSearchEngine, id, ET9NAVMountProperty_ReadOnlyNoValidation);
}

void Xt9Ime::unmountIndex(quint16 id)
{
    XT9_API(ET9NAVCore_UnmountIndex, &sSearchEngine, id);
}

ET9STATUS Xt9Ime::ldbReadData(ET9U32 dwLdbNum, ET9U8 *ET9FARDATA *ppbSrc, ET9U32 *pdwSizeInBytes)
{
    const QLocale locale = Xt9LanguageMap::locale(dwLdbNum);
    if (locale.language() == QLocale::AnyLanguage) {
        return ET9STATUS_READ_DB_FAIL;
    }

    const void *data;
    qint64 size;
    if (!ldbManager->loadDictionary(locale, data, size)) {
        return ET9STATUS_READ_DB_FAIL;
    }

    *ppbSrc = static_cast<ET9U8 *>(const_cast<void *>(data));
    *pdwSizeInBytes = static_cast<ET9U32>(size);

    return ET9STATUS_NONE;
}

ET9STATUS Xt9Ime::ET9Request(ET9WordSymbInfo *const pWordSymbInfo, ET9_Request *const pRequest)
{
    return reinterpret_cast<Xt9Ime *>(pWordSymbInfo->pPublicExtension)->request(pRequest);
}

ET9STATUS Xt9Ime::request(ET9_Request *const pRequest)
{
#ifdef XT9_DEBUG
    qCDebug(lcXT9) << "ET9Request, type =" << pRequest->eType;
#endif
    return _requestCallback->request(pRequest);
}

ET9STATUS Xt9Ime::ET9KDBLoad(ET9KDBInfo *const pKdbInfo, const ET9U32 dwKdbNum, const ET9U16 wPageNum)
{
    return reinterpret_cast<Xt9Ime *>(pKdbInfo->pPublicExtension)->kdbLoad(dwKdbNum, wPageNum);
}

ET9STATUS Xt9Ime::kdbLoad(const ET9U32 dwKdbNum, const ET9U16 wPageNum)
{
    Q_UNUSED(dwKdbNum)

    ET9STATUS eStatus;
    ET9UINT nErrorLine = 0;

    if (keyboardGenerator->vkbLayout.isEmpty()) {
        qCWarning(lcXT9) << "Keyboard layout is not set.";
        return ET9STATUS_READ_DB_FAIL;
    }

    QByteArray xmlData = keyboardGenerator->createXmlLayout();
    if (xmlData.isEmpty()) {
        qCWarning(lcXT9) << "Failed to create xml layout.";
        return ET9STATUS_READ_DB_FAIL;
    }

    eStatus = XT9_API(ET9KDB_Load_XmlKDB,
                &sKdbInfo,
                static_cast<ET9U16>(qRound(keyboardGenerator->layoutWidth)),
                static_cast<ET9U16>(qRound(keyboardGenerator->layoutHeight)),
                wPageNum,
                reinterpret_cast<const ET9U8 *>(xmlData.constData()),
                static_cast<ET9U32>(xmlData.size()),
                nullptr,
                nullptr,
                &nErrorLine);

    if (eStatus) {
        qCWarning(lcXT9).nospace() << "Failed to load xml layout, status: " << eStatus << ", line: " << nErrorLine;
        qCWarning(lcXT9).nospace().noquote() << xmlData.constData();
    }

    return eStatus;
}

ET9STATUS Xt9Ime::ET9KDBRequest(ET9KDBInfo *const pKDBInfo, ET9WordSymbInfo *const pWordSymbInfo, ET9KDB_Request *const pET9KDB_Request)
{
    Q_UNUSED(pWordSymbInfo)
    return reinterpret_cast<Xt9Ime *>(pKDBInfo->pPublicExtension)->kdbRequest(pET9KDB_Request);
}

ET9STATUS Xt9Ime::kdbRequest(ET9KDB_Request *const pET9KDB_Request)
{
#ifdef XT9_DEBUG
    qCDebug(lcXT9) << "ET9KDB_Request, type =" << pET9KDB_Request->eType;
#endif
    return ET9STATUS_NONE;
}

} // namespace QtVirtualKeyboard
QT_END_NAMESPACE
