/*
 *  Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
 *
 *  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 or (at your option)
 *  version 3 of the License.
 *
 *  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, see <http://www.gnu.org/licenses/>.
 */

#include "Add.h"
#include "AddGroup.h"
#include "Analyze.h"
#include "AttachmentExport.h"
#include "AttachmentImport.h"
#include "AttachmentRemove.h"
#include "Clip.h"
#include "Close.h"
#include "DatabaseCreate.h"
#include "DatabaseEdit.h"
#include "DatabaseInfo.h"
#include "Diceware.h"
#include "Edit.h"
#include "Estimate.h"
#include "Exit.h"
#include "Export.h"
#include "Generate.h"
#include "Help.h"
#include "Import.h"
#include "List.h"
#include "Merge.h"
#include "Move.h"
#include "Open.h"
#include "Remove.h"
#include "RemoveGroup.h"
#include "Search.h"
#include "Show.h"
#include "Utils.h"

#include <QCommandLineParser>
#include <QFileInfo>
#include <QRegularExpression>

const QCommandLineOption Command::HelpOption = QCommandLineOption(QStringList()
#ifdef Q_OS_WIN
                                                                      << QStringLiteral("?")
#endif
                                                                      << QStringLiteral("h") << QStringLiteral("help"),
                                                                  QObject::tr("Display this help."));

const QCommandLineOption Command::QuietOption =
    QCommandLineOption(QStringList() << "q" << "quiet",
                       QObject::tr("Silence password prompt and other secondary outputs."));

const QCommandLineOption Command::KeyFileOption = QCommandLineOption(QStringList() << "k" << "key-file",
                                                                     QObject::tr("Key file of the database."),
                                                                     QObject::tr("path"));

const QCommandLineOption Command::NoPasswordOption =
    QCommandLineOption(QStringList() << "no-password", QObject::tr("Deactivate password key for the database."));

const QCommandLineOption Command::YubiKeyOption =
    QCommandLineOption(QStringList() << "y" << "yubikey",
                       QObject::tr("Yubikey slot and optional serial used to access the database (e.g., 1:7370001)."),
                       QObject::tr("slot[:serial]"));

namespace
{

    QSharedPointer<QCommandLineParser> buildParser(Command* command)
    {
        auto parser = QSharedPointer<QCommandLineParser>(new QCommandLineParser());
        parser->setApplicationDescription(command->description);
        for (const CommandLineArgument& positionalArgument : command->positionalArguments) {
            parser->addPositionalArgument(
                positionalArgument.name, positionalArgument.description, positionalArgument.syntax);
        }
        for (const CommandLineArgument& optionalArgument : command->optionalArguments) {
            parser->addPositionalArgument(optionalArgument.name, optionalArgument.description, optionalArgument.syntax);
        }
        for (const QCommandLineOption& option : command->options) {
            parser->addOption(option);
        }
        parser->addOption(Command::HelpOption);
        return parser;
    }

} // namespace

Command::Command()
    : currentDatabase(nullptr)
{
    options.append(Command::QuietOption);
}

Command::~Command() = default;

QString Command::getDescriptionLine()
{
    QString response = name;
    QString space(" ");
    QString spaces = space.repeated(20 - name.length());
    response = response.append(spaces);
    response = response.append(description);
    response = response.append("\n");
    return response;
}

QString Command::getHelpText()
{
    auto help = buildParser(this)->helpText();
    // Fix spacing of options parameter
    help.replace(QStringLiteral("[options]"), name + QStringLiteral(" [options]"));
    // Remove application directory from command line example
    auto appname = QFileInfo(QCoreApplication::applicationFilePath()).fileName();
    auto regex = QRegularExpression(QStringLiteral(" .*%1").arg(QRegularExpression::escape(appname)));
    help.replace(regex, appname.prepend(" "));

    return help;
}

QSharedPointer<QCommandLineParser> Command::getCommandLineParser(const QStringList& arguments)
{
    auto& err = Utils::STDERR;
    QSharedPointer<QCommandLineParser> parser = buildParser(this);

    if (!parser->parse(arguments)) {
        err << parser->errorText() << "\n\n";
        err << getHelpText();
        return {};
    }
    if (parser->positionalArguments().size() < positionalArguments.size()) {
        err << QObject::tr("Missing positional argument(s).") << "\n\n";
        err << getHelpText();
        return {};
    }
    if (parser->positionalArguments().size() > (positionalArguments.size() + optionalArguments.size())) {
        err << QObject::tr("Too many arguments provided.") << "\n\n";
        err << getHelpText();
        return {};
    }
    if (parser->isSet(HelpOption)) {
        err << getHelpText();
        return {};
    }
    return parser;
}

namespace Commands
{
    QMap<QString, QSharedPointer<Command>> s_commands;

    void setupCommands(bool interactive)
    {
        s_commands.clear();

        s_commands.insert(QStringLiteral("add"), QSharedPointer<Command>(new Add()));
        s_commands.insert(QStringLiteral("analyze"), QSharedPointer<Command>(new Analyze()));
        s_commands.insert(QStringLiteral("attachment-export"), QSharedPointer<Command>(new AttachmentExport()));
        s_commands.insert(QStringLiteral("attachment-import"), QSharedPointer<Command>(new AttachmentImport()));
        s_commands.insert(QStringLiteral("attachment-rm"), QSharedPointer<Command>(new AttachmentRemove()));
        s_commands.insert(QStringLiteral("clip"), QSharedPointer<Command>(new Clip()));
        s_commands.insert(QStringLiteral("close"), QSharedPointer<Command>(new Close()));
        s_commands.insert(QStringLiteral("db-create"), QSharedPointer<Command>(new DatabaseCreate()));
        s_commands.insert(QStringLiteral("db-edit"), QSharedPointer<Command>(new DatabaseEdit()));
        s_commands.insert(QStringLiteral("db-info"), QSharedPointer<Command>(new DatabaseInfo()));
        s_commands.insert(QStringLiteral("diceware"), QSharedPointer<Command>(new Diceware()));
        s_commands.insert(QStringLiteral("edit"), QSharedPointer<Command>(new Edit()));
        s_commands.insert(QStringLiteral("estimate"), QSharedPointer<Command>(new Estimate()));
        s_commands.insert(QStringLiteral("generate"), QSharedPointer<Command>(new Generate()));
        s_commands.insert(QStringLiteral("help"), QSharedPointer<Command>(new Help()));
        s_commands.insert(QStringLiteral("ls"), QSharedPointer<Command>(new List()));
        s_commands.insert(QStringLiteral("merge"), QSharedPointer<Command>(new Merge()));
        s_commands.insert(QStringLiteral("mkdir"), QSharedPointer<Command>(new AddGroup()));
        s_commands.insert(QStringLiteral("mv"), QSharedPointer<Command>(new Move()));
        s_commands.insert(QStringLiteral("open"), QSharedPointer<Command>(new Open()));
        s_commands.insert(QStringLiteral("rm"), QSharedPointer<Command>(new Remove()));
        s_commands.insert(QStringLiteral("rmdir"), QSharedPointer<Command>(new RemoveGroup()));
        s_commands.insert(QStringLiteral("search"), QSharedPointer<Command>(new Search()));
        s_commands.insert(QStringLiteral("show"), QSharedPointer<Command>(new Show()));

        if (interactive) {
            s_commands.insert(QStringLiteral("exit"), QSharedPointer<Command>(new Exit("exit")));
            s_commands.insert(QStringLiteral("quit"), QSharedPointer<Command>(new Exit("quit")));
        } else {
            s_commands.insert(QStringLiteral("export"), QSharedPointer<Command>(new Export()));
            s_commands.insert(QStringLiteral("import"), QSharedPointer<Command>(new Import()));
        }
    }

    QList<QSharedPointer<Command>> getCommands()
    {
        return s_commands.values();
    }

    QSharedPointer<Command> getCommand(const QString& commandName)
    {
        return s_commands.value(commandName);
    }
} // namespace Commands
