// SPDX-FileCopyrightText: 2022 KeePassXC Team <team@keepassxc.org>
// SPDX-License-Identifier: GPL-2.0-only or GPL-3.0-only

#include "passphrasegenerator.h"

#include <QDebug>
#include <QFile>
#include <QRandomGenerator>
#include <QRegularExpression>
#include <QTextStream>
#include <cmath>

const QLatin1String PassphraseGenerator::DefaultSeparator(" ");
const QLatin1String PassphraseGenerator::DefaultWordList(":/data/wordlists/en.wordlist");

PassphraseGenerator::PassphraseGenerator()
    : m_wordCount(DefaultWordCount)
    , m_wordCase(LOWERCASE)
    , m_separator(DefaultSeparator)
{
    setDefaultWordList();
}

double PassphraseGenerator::estimateEntropy(int wordCount)
{
    if (m_wordlist.isEmpty()) {
        return 0.0;
    }
    if (wordCount < 1) {
        wordCount = m_wordCount;
    }

    return std::log2(m_wordlist.size()) * wordCount;
}

void PassphraseGenerator::setWordCount(int wordCount)
{
    m_wordCount = qMax(1, wordCount);
}

void PassphraseGenerator::setWordCase(PassphraseWordCase wordCase)
{
    m_wordCase = wordCase;
}

void PassphraseGenerator::setWordList(const QString &path)
{
    m_wordlist.clear();

    QFile file(path);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qWarning() << "Couldn't load passphrase wordlist.";
        return;
    }

    QTextStream in(&file);
    QString line = in.readLine();
    bool isSigned = line.startsWith(QLatin1String("-----BEGIN PGP SIGNED MESSAGE-----"));
    if (isSigned) {
        while (!line.isNull() && !line.trimmed().isEmpty()) {
            line = in.readLine();
        }
    }
    static const QRegularExpression rx(QLatin1String("^[0-9]+(-[0-9]+)*\\s+([^\\s]+)$"));
    while (!line.isNull()) {
        if (isSigned && line.startsWith(QLatin1String("-----BEGIN PGP SIGNATURE-----"))) {
            break;
        }
        // Handle dash-escaped lines (if the wordlist is signed)
        if (isSigned && line.startsWith(QLatin1String("- "))) {
            line.remove(0, 2);
        }
        line = line.trimmed();
        line.replace(rx, QLatin1String("\\2"));
        if (!line.isEmpty()) {
            m_wordlist.append(line);
        }
        line = in.readLine();
    }

    if (m_wordlist.size() < 4000) {
        qWarning("Wordlist too short!");
        return;
    }
}

void PassphraseGenerator::setDefaultWordList()
{
    setWordList(PassphraseGenerator::DefaultWordList);
}

void PassphraseGenerator::setWordSeparator(const QString &separator)
{
    m_separator = separator;
}

QString PassphraseGenerator::generatePassphrase() const
{
    QString tmpWord;
    Q_ASSERT(isValid());

    // In case there was an error loading the wordlist
    if (m_wordlist.length() == 0) {
        return {};
    }

    QStringList words;
    for (int i = 0; i < m_wordCount; ++i) {
        int wordIndex = QRandomGenerator::global()->bounded(static_cast<quint32>(m_wordlist.length()));
        tmpWord = m_wordlist.at(wordIndex);

        // convert case
        switch (m_wordCase) {
        case UPPERCASE:
            tmpWord = tmpWord.toUpper();
            break;
        case TITLECASE:
            tmpWord = tmpWord.replace(0, 1, tmpWord.left(1).toUpper());
            break;
        case LOWERCASE:
        default:
            tmpWord = tmpWord.toLower();
            break;
        }
        words.append(tmpWord);
    }

    return words.join(m_separator);
}

bool PassphraseGenerator::isValid() const
{
    if (m_wordCount == 0) {
        return false;
    }

    return m_wordlist.size() >= 1000;
}
