/*
    This file is part of Kiten, a KDE Japanese Reference Tool
    SPDX-FileCopyrightText: 2006 Joseph Kerian <jkerian@gmail.com>
    SPDX-FileCopyrightText: 2011 Daniel E. Moctezuma <democtezuma@gmail.com>

    SPDX-License-Identifier: LGPL-2.0-or-later
*/

#include "dictionarymanager.h"

#include "dictfile.h"
#include "dictionarypreferencedialog.h"
#include "dictquery.h"
#include "entry.h"
#include "entrylist.h"
#include "kitenmacros.h"

#include <KConfig>
#include <KConfigSkeleton>

#include <QString>

/* Includes to handle various types of dictionaries
IMPORTANT: To add a dictionary type, add the header file here and add it to the
 if statement under addDictionary() */
#include "DictEdict/dictfileedict.h"
#include "DictKanjidic/dictfilekanjidic.h"

using namespace Qt::StringLiterals;

class DictionaryManager::Private
{
public:
    /**
     * List of dictionaries, indexed by name
     */
    QHash<QString, DictFile *> dictManagers;
};

#if 0
class debug_entry : public Entry
{
  public:
    debug_entry(QString word) : Entry( QString( "libkiten" ), word
                              , QStringList(), QStringList() ), count( counter++ )
                              { }
    virtual Entry * clone() const { return new debug_entry( *this ); }
    virtual bool loadEntry( const QString& ) { return false; }
    virtual QString dumpEntry() const { return ""; }
    virtual bool sort( const debug_entry &that, const QStringList &dicts,
                    const QStringList &fields )
                    { return this->count < that.count; }

    int count;
    static int counter;
};
int debug_entry::counter = 0;
#endif

/**
 * The constructor. Set autodelete on our dictionary list
 */
DictionaryManager::DictionaryManager()
    : d(new Private)
{
}

/**
 * Delete everything in our hash
 */
DictionaryManager::~DictionaryManager()
{
    {
        QMutableHashIterator<QString, DictFile *> it(d->dictManagers);
        while (it.hasNext()) {
            it.next();
            delete it.value();
            it.remove();
        }
    }

    delete d;
}

/**
 * Given a named Dict file/name/type... create and add the object if it
 * seems to work properly on creation.
 */
bool DictionaryManager::addDictionary(const QString &file, const QString &name, const QString &type)
{
    if (d->dictManagers.contains(name)) // This name already exists in the list!
    {
        return false;
    }

    DictFile *newDict = makeDictFile(type);
    if (newDict == nullptr) {
        return false;
    }

    if (!newDict->loadDictionary(file, name)) {
        qDebug() << "Dictionary load FAILED: " << newDict->getName();
        delete newDict;
        return false;
    }

    qDebug() << "Dictionary Loaded : " << newDict->getName();
    d->dictManagers.insert(name, newDict);
    return true;
}

/**
 * Examine the DictQuery and farm out the search to the specialized dict
 * managers. Note that a global search limit will probably be implemented
 * either here or in the DictFile implementations... probably both
 *
 * @param query the query, see DictQuery documentation
 */
EntryList *DictionaryManager::doSearch(const DictQuery &query) const
{
    auto ret = new EntryList();
#if 0
  if( query.getMeaning() == "(libkiten)" )
  {
    ret->append( new debug_entry( "Summary of libkiten data" ) );
    foreach( const QString &dict, listDictionaries() )
    {
      ret->append( new debug_entry( dict ) );
    }
    return ret;
  }
#endif

    // There are two basic modes.... one in which the query
    // specifies the dictionary list, one in which it does not
    QStringList dictsFromQuery = query.getDictionaries();
    if (dictsFromQuery.isEmpty()) {
        // None specified, search all
        for (DictFile *it : d->dictManagers) {
            qDebug() << "Searching in " << it->getName() << "dictionary.";
            EntryList *temp = it->doSearch(query);
            if (temp) {
                ret->appendList(temp);
            }
            delete temp;
        }
    } else {
        for (const QString &target : dictsFromQuery) {
            DictFile *newestFound = d->dictManagers.find(target).value();
            if (newestFound != nullptr) {
                EntryList *temp = newestFound->doSearch(query);
                if (temp) {
                    ret->appendList(temp);
                }
                delete temp;
            }
        }
    }

    ret->setQuery(query); // Store the query for later use.
    qDebug() << "From query: '" << query.toString() << "' Found " << ret->count() << " results";
    qDebug() << "Incoming match type: " << query.getMatchType() << " Outgoing: " << ret->getQuery().getMatchType();
    return ret;
}

/**
 * For this case, we let polymorphism do most of the work. We assume that the user wants
 * to pare down the results, so we let the individual entry matching methods run over the
 * new query and accept (and copy) any of those that pass.
 */
EntryList *DictionaryManager::doSearchInList(const DictQuery &query, const EntryList *list) const
{
    auto ret = new EntryList();

    for (Entry *it : *list) {
        if (it->matchesQuery(query)) {
            Entry *x = it->clone();
            ret->append(x);
        }
    }

    ret->setQuery(query + list->getQuery());
    return ret;
}

QMap<QString, QString> DictionaryManager::generateExtendedFieldsList()
{
    QMap<QString, QString> result;
    const QStringList dictTypes = listDictFileTypes();
    for (const QString &dictType : dictTypes) {
        DictFile *tempDictFile = makeDictFile(dictType);
        QMap<QString, QString> tempList = tempDictFile->getSearchableAttributes();
        QMap<QString, QString>::const_iterator it = tempList.constBegin();
        while (it != tempList.constEnd()) {
            if (!result.contains(it.key())) {
                result.insert(it.key(), it.value());
            }
            ++it;
        }
        delete tempDictFile;
    }

    return result;
}

QMap<QString, DictionaryPreferenceDialog *> DictionaryManager::generatePreferenceDialogs(KConfigSkeleton *config, QWidget *parent)
{
    QMap<QString, DictionaryPreferenceDialog *> result;
    const QStringList dictTypes = listDictFileTypes();
    for (const QString &dictType : dictTypes) {
        DictFile *tempDictFile = makeDictFile(dictType);
        DictionaryPreferenceDialog *newDialog = tempDictFile->preferencesWidget(config, parent);

        if (newDialog == nullptr) {
            delete tempDictFile;
            continue;
        }

        result.insert(dictType, newDialog);
        delete tempDictFile;
    }

    return result;
}

/**
 * Return a list of the dictionaries by their name (our key)
 * Note that this dictionary name does not necessarily have to have anything
 * to do with the actual dictionary name...
 */
QStringList DictionaryManager::listDictionaries() const
{
    QStringList ret;
    for (DictFile *it : d->dictManagers) {
        ret.append(it->getName());
    }

    return ret;
}

/**
 * IMPORTANT: To add a dictionary type, you have to manually add the creation
 * step here, the prev method, and \#include your header file above. If you have
 * fully implemented the interface in DictionaryManager.h, It should simply work.
 */
QStringList DictionaryManager::listDictFileTypes()
{
    QStringList list;
    list.append(EDICT);
    list.append(KANJIDIC);

    // Add your dictionary type here!

    return list;
}

/**
 * Return the dictionary type and file used by a named dictionary.
 * returns a pair of empty QStrings if you specify an invalid name
 *
 * @param name the name of the dictionary, as given in the addDictionary method
 */
QPair<QString, QString> DictionaryManager::listDictionaryInfo(const QString &name) const
{
    if (!d->dictManagers.contains(name)) // This name not in list!
    {
        return qMakePair(QString(), QString());
    }

    return qMakePair(d->dictManagers[name]->getName(), d->dictManagers[name]->getFile());
}

/**
 * Return a list of the names of each dictionary of a given type.
 *
 * @param type the type of the dictionary we want a list of
 */
QStringList DictionaryManager::listDictionariesOfType(const QString &type) const
{
    QStringList ret;
    QHash<QString, DictFile *>::const_iterator it = d->dictManagers.constBegin();
    while (it != d->dictManagers.constEnd()) {
        if (it.value()->getType() == type) {
            ret.append(it.key());
        }

        ++it;
    }

    return ret;
}

/**
 * Load preference settings for a particular dictionary
 */
void DictionaryManager::loadDictSettings(const QString &dictName, KConfigSkeleton *config)
{
    DictFile *dict = this->makeDictFile(dictName);
    if (dict != nullptr) {
        config->setCurrentGroup("dicts_"_L1 + dictName.toLower());
        dict->loadSettings(config);
        delete dict;
    }
}

void DictionaryManager::loadSettings(const KConfig &config)
{
    Q_UNUSED(config)
    // TODO
}

/**
 * IMPORTANT: To add a dictionary type, you have to manually add the creation
 * step here, the next method, and \#include your header file above. If you have
 * fully implemented the interface in dictionarymanager.h, It should simply work.
 */
DictFile *DictionaryManager::makeDictFile(const QString &type)
{
    if (type == EDICT) {
        return new DictFileEdict();
    } else if (type == KANJIDIC) {
        return new DictFileKanjidic();
    }

    // Add new dictionary types here!!!

    return nullptr;
}

void DictionaryManager::removeAllDictionaries()
{
    qDeleteAll(d->dictManagers);
    d->dictManagers.clear();
}

/**
 * Remove a dictionary from the list, and delete the dictionary object
 * (it should close files, deallocate memory, etc).
 *
 * @param name the name of the dictionary, as given in the addDictionary method
 */
bool DictionaryManager::removeDictionary(const QString &name)
{
    DictFile *file = d->dictManagers.take(name);
    delete file;
    return true;
}
