/*****************************************************************************
 * kiwix.cpp - QStarDict, a dictionary for learning foreign languages        *
 * Copyright (C) 2023 Alexander Rodin                                        *
 *                                                                           *
 * This program is free software; you can redistribute it and/or modify      *
 * it under the terms of the GNU General Public License as published by      *
 * the Free Software Foundation; either version 2 of the License, or         *
 * (at your option) any later version.                                       *
 *                                                                           *
 * This program is distributed in the hope that it will be useful,           *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of            *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
 * GNU General Public License for more details.                              *
 *                                                                           *
 * You should have received a copy of the GNU General Public License along   *
 * with this program; if not, write to the Free Software Foundation, Inc.,   *
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.               *
 *****************************************************************************/

#include "kiwix.h"

#include <algorithm>
#include <list>
#include <map>
#include <string>
#include <utility>
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QMessageBox>
#include <QRegularExpression>
#include <QSettings>
#include <QStack>
#include <zim/archive.h>
#include <zim/entry.h>
#include <zim/error.h>
#include <zim/item.h>
#include <zim/zim.h>
#include "htmlparser/html.hpp"
#include "../pluginserver.h"
#include "adddialog.h"
#include "grayscale.h"
#include "settingsdialog.h"

Kiwix::Kiwix(QObject *parent)
	: QObject(parent)
{
    QDir dataDir(dataDirPath());
    if (! dataDir.exists())
        dataDir.mkpath(".");

    QSettings settings("qstardict", "qstardict");
    setRemoveColors(settings.value("Kiwix/removeColors", true).toBool());
}

void Kiwix::saveSettings()
{
    QSettings settings("qstardict", "qstardict");
    settings.setValue("Kiwix/removeColors", removeColors());
}

Kiwix::~Kiwix()
{
    for (auto archive: m_archives)
        delete archive;
    m_archives.clear();

    saveSettings();
}

QIcon Kiwix::pluginIcon() const
{
    return QIcon(":/pics/logo.svg");
}

void Kiwix::setLoadedDicts(const QStringList &loadedDicts)
{
    for (zim::Archive *archive: m_archives)
        delete archive;
    m_archives.clear();

    for (const QString &dict: loadedDicts)
    {
        try
        {
            m_archives[dict] = new zim::Archive((dataDirPath() + "/" + dict + ".zim").toStdString());
        }
        catch (std::exception &e)
        {
            // do nothing
        }
    }
}

QStringList Kiwix::loadedDicts() const
{
    return m_archives.keys();
}

QStringList Kiwix::availableDicts() const
{
    QDir dataDir(dataDirPath());
    QStringList zimFiles = dataDir.entryList({ "*.zim" });
    QStringList dicts;
    for (const QString &zimFile: zimFiles)
        dicts.push_back(QFileInfo(zimFile).completeBaseName());
    return dicts;
}

Kiwix::DictInfo Kiwix::dictInfo(const QString &dict)
{
    QString pluginId = qsd->idForPlugin(this);
	DictInfo result(pluginId, dict);
    if (m_archives.contains(dict))
    {
        zim::Archive *archive = m_archives[dict];
        try
        {
            result.setAuthor(archive->getMetadata("Creator").c_str());
        }
        catch (const zim::EntryNotFound &)
        {
        }
        try
        {
            result.setDescription(archive->getMetadata("Description").c_str());
        }
        catch (const zim::EntryNotFound &)
        {
        }

        result.setWordsCount(archive->getEntryCount());
        result.setFilename(dataDirPath() + "/" + dict + ".zim");
    }
    return result;
}

bool Kiwix::isTranslatable(const QString &dict, const QString &word)
{
    if (! m_archives.contains(dict))
        return false;
    if (word.isEmpty())
        return false;
    zim::Archive *archive = m_archives[dict];
    try
    {
        zim::Entry entry = archive->getEntryByTitle(word.toStdString());
    }
    catch (const zim::EntryNotFound &e)
    {
        return false;
    }
    return true;
}

Kiwix::Translation Kiwix::translate(const QString &dict, const QString &word)
{
    if (! m_archives.contains(dict))
        return Translation();
    if (word.isEmpty())
        return Translation();
    zim::Archive *archive = m_archives[dict];
    zim::Entry entry = archive->getEntryByTitle(word.toStdString());
    std::string data;
    if (entry.isRedirect())
    {
        zim::Item item = entry.getRedirect();
        zim::Blob blob = item.getData();
        data = blob.data();
    }
    else
    {
        zim::Item item = entry.getItem();
        zim::Blob blob = item.getData();
        data = blob.data();
    }

    html::parser parser;
    html::node_ptr root = parser.parse(data);
    QString translation = root->to_html().c_str();

    // remove hyperlinks
    static const QRegularExpression aOpenRegExp("<a[^>]*>",
        QRegularExpression::CaseInsensitiveOption |
        QRegularExpression::MultilineOption);
    translation.replace(aOpenRegExp, " ");

    static const QRegularExpression aCloseRegExp("</a>",
        QRegularExpression::CaseInsensitiveOption);
    translation.replace(aCloseRegExp, " ");

    translation.replace(" ,", ",");
    translation.replace(" .", ".");
    translation.replace(" ;", ";");


    // remove colors
    if (m_removeColors)
        translation = htmlToGrayscale(translation);

#if 0
    for (auto languageNode: root->select("details[data-level='2']"))
    {
        auto h2Node = languageNode->select("h2").at(0)->at(0);
        std::string language = h2Node->to_text();

        if (language != "English" && language != "Russian" && language != "Английский" && language != "Русский") // FIXME
            continue;

        translation = languageNode->to_html().c_str();
        translation = translation.replace(QRegularExpression("<h2[ >].*</h2>"), "");
        break;
    }
#endif

    return Translation(word, dict, translation, true);
}

QStringList Kiwix::findSimilarWords(const QString &dict, const QString &word)
{
    QStringList result;

    if (! m_archives.contains(dict))
        return result;
    if (word.isEmpty())
        return result;

    zim::Archive *archive = m_archives[dict];
    int count = 0;
    for (auto entry: archive->findByTitle(word.toStdString()))
    {
        result << QString(entry.getTitle().c_str()).replace(QRegularExpression("^A/"), "");
        if (++count >= 100)
            break;
    }

    return result;
}

std::optional<QString> Kiwix::addDictionary(QWidget *parent, const QString &fileName)
{
    AddDialog addDialog(parent, fileName);
    int result = addDialog.exec();
    if (result == QDialog::Accepted)
        return QFileInfo(fileName).completeBaseName();
    else
        return std::nullopt;
}

bool Kiwix::removeDictionary(const QString &dict)
{
    return QFile::remove(dataDirPath() + "/" + dict + ".zim");
}

int Kiwix::execSettingsDialog(QWidget *parent)
{
    SettingsDialog dialog(this, parent);
    return dialog.exec();
}

// vim: tabstop=4 softtabstop=4 shiftwidth=4 expandtab cindent textwidth=120 formatoptions=tc
