/****************************************************************************
 **
 ** Copyright (C) 2008-2010 Nokia Corporation and/or its subsidiary(-ies).
 ** Contact: Nokia Corporation (qt-info@nokia.com)
 **
 ** This file is part of the doxygen2qthelp project on Trolltech Labs.
 **
 ** This file may be used under the terms of the GNU General Public
 ** License version 2.0 or 3.0 as published by the Free Software Foundation
 ** and appearing in the file LICENSE.GPL included in the packaging of
 ** this file.  Please review the following information to ensure GNU
 ** General Public Licensing requirements will be met:
 ** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
 ** http://www.gnu.org/copyleft/gpl.html.
 **
 ** If you are unsure which license is appropriate for your use, please
 ** contact the sales department at qt-sales@nokia.com.
 **
 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 **
 ****************************************************************************/

#include "config.h"
#include "htmlhelpdatainterface_p.h"
#include "qhelpgenerator_p.h"
#include "qthelpprojectwriter_p.h"

#include <QtCore/QtGlobal>
#include <QtCore/QCoreApplication>
#include <QtCore/QString>
#include <QtCore/QFile>
#include <QtCore/QSettings>
#include <QtCore/QUrl>
#include <QtCore/QVariant>

#if (QT_VERSION <= 0x040400)
# error Please compile doxygen2qthelp against a Qt release _after_ 4.4.0, e.g. a recent snapshot
#endif

QT_BEGIN_NAMESPACE

const char* const FILE_MISSING_FORMAT = "Error: File '%s' does not exist.\n";

enum Action
{
    ACTION_USAGE,
    ACTION_RUN,
    ACTION_QUIT,
    ACTION_VERSION
};

QString virtualFolder;
QString nameSpace;
QString outputFileName;
QString inputFileName;
bool verbose = false;
bool forceOverwrite = false;
bool helpProjectMode = false;
bool error = false;

void printUsage()
{
    printf(
        "Usage:  doxygen2qthelp OPTIONS <infile> [<outfile>]\n"
        "        doxygen2qthelp --config=<file>\n"
        "\n"
        "Parameters           Description\n"
        " <infile>            Read input from <infile>\n"
        "                     (InputFile=<infile>)\n"
        "\n"
        " <outfile>           Write output to <outfile>\n"
        "                     (OutputFile=<outfile>)\n"
        "\n"
        " --config=<file>     Read config from <file>\n"
        "\n"
        " --folder=<str>      Set virtual folder [required]\n"
        "                     (VirtualFolder=<str>)\n"
        "\n"
        " --force             Overwrite existing files\n"
        "                     (ForceOverwrite=true)\n"
        "\n"
        " --help, -h          Print help and exit\n"
        "\n"
        " --namespace=<str>   Set namespace to <str> [required]\n"
        "                     (Namespace=<str>)\n"
        "\n"
        " --qch               Output as Qt Compressed Help file (*.qch) [default]\n"
        "                     (QhpMode=false)\n"
        "\n"
        " --qhp               Output as Qt Help Project (*.qhp)\n"
        "                     (QhpMode=true)\n"
        "\n"
        " --verbose, -v       Report in more detail\n"
        "                     (Verbose=true)\n"
        "\n"
        " --version, -V       Print version information and exit\n"
        "\n"
    );
}

void printVersion()
{
    printf(
        "doxygen2qthelp " VERSION " of Qt " QT_VERSION_STR " by Trolltech ASA "
        "(compiled " __TIME__ ", " __DATE__ ")\n"
    );
}

void printSettings()
{
    printf(
        "Settings\n"
        " Input filename ....... %s\n"
        " Output filename ...... %s\n"
        "\n"
        " QtHelpProject mode ... %s\n"
        " Force overwrite ...... %s\n"
        "\n"
        " Namespace ............ %s\n"
        " Virtual folder ....... %s\n"
        "\n",
        qPrintable(inputFileName),
        qPrintable(outputFileName),
        helpProjectMode ? "yes" : "no",
        forceOverwrite ? "yes" : "no",
        qPrintable(nameSpace),
        qPrintable(virtualFolder)
    );
}

bool checkNamespace()
{
    const QString uri(QLatin1String("http://") + nameSpace
            + QLatin1String("/"));
    const QUrl url = QUrl::fromEncoded(uri.toAscii());
    if (!url.isValid()) {
        printf("Error: Namespace does not conform to the rules "
                "for the host part of a URI.\n");
        return false;
    }
    return true;
}

bool checkVirtualFolder()
{
    const char * const VIRTUAL_FOLDER_ERROR = "Error: Virtual folder does not "
            "conform to the rules for a path segment of a URI.\n";
    if (virtualFolder.contains('/') || virtualFolder.contains('?')
            || virtualFolder.contains('#')) {
        printf(VIRTUAL_FOLDER_ERROR);
        return false;
    }

    const QString uri(QLatin1String("http://example.org/") + virtualFolder
            + QLatin1String("/"));
    const QUrl url = QUrl::fromEncoded(uri.toAscii());
    if (!url.isValid()) {
        printf(VIRTUAL_FOLDER_ERROR);
        return false;
    }
    return true;
}

bool checkFiles()
{
    if (inputFileName == outputFileName) {
        printf("Error: Input and output filename identical\n");
        return false;
    }

    if (!QFile(inputFileName).exists()) {
        printf(FILE_MISSING_FORMAT, qPrintable(inputFileName));
        return false;
    }

    if (!forceOverwrite && QFile(outputFileName).exists()) {
        printf("Error: File '%s' exists. Use --force to overwrite.\n",
                qPrintable(outputFileName));
        return false;
    }
    return true;
}

void guessOutputFilename()
{
    if (!outputFileName.isEmpty())
        return;

    const QString newSuffix(helpProjectMode ? ".qhp" : ".qch");
    if (inputFileName.endsWith(QLatin1String(".hhp"))) {
        outputFileName = inputFileName.left(inputFileName.count() - 4);
    } else {
        outputFileName = inputFileName;
    }
    outputFileName += newSuffix;
}

bool run()
{
    guessOutputFilename();
    Q_ASSERT(!inputFileName.isEmpty());
    Q_ASSERT(!outputFileName.isEmpty());

    if (verbose)
        printSettings();

    if (!checkNamespace() || !checkVirtualFolder() || !checkFiles())
        return false;

    HtmlHelpDataInterface helpData(inputFileName, nameSpace, virtualFolder);
    if (helpProjectMode) {
        // Create .qhp
        QtHelpProjectWriter writer;
        if (!writer.writeFile(&helpData, outputFileName)) {
            printf("Error: Could not write to file '%s'.\n",
                    qPrintable(outputFileName));
            return false;
        }
    } else {
        // Create .qch
        QHelpGenerator generator;
        if (!generator.generate(&helpData, outputFileName)) {
            printf("Error: %s\n", qPrintable(generator.error()));
            return false;
        }
    }

    printf("Done.\n");
    return true;
}

void setIfSpecified(const QSettings &iniReader, const QString &key,
        QString &output)
{
    const QString res = iniReader.value(key).toString();
    if (!res.isEmpty())
        output = res;
}

void setIfSpecified(const QSettings &iniReader, const QString &key,
        bool &output)
{
    const QVariant res = iniReader.value(key);
    if (!res.toString().isEmpty())
        output = res.toBool();
}

bool readConfigFile(const QString &fileName)
{
    if (!QFile(fileName).exists()) {
        printf(FILE_MISSING_FORMAT, qPrintable(fileName));
        return false;
    }

    QSettings iniReader(fileName, QSettings::IniFormat);
    setIfSpecified(iniReader, QLatin1String("ForceOverwrite"), forceOverwrite);
    setIfSpecified(iniReader, QLatin1String("InputFile"), inputFileName);
    setIfSpecified(iniReader, QLatin1String("Namespace"), nameSpace);
    setIfSpecified(iniReader, QLatin1String("OutputFile"), outputFileName);
    setIfSpecified(iniReader, QLatin1String("QhpMode"), helpProjectMode);
    setIfSpecified(iniReader, QLatin1String("Verbose"), verbose);
    setIfSpecified(iniReader, QLatin1String("VirtualFolder"), virtualFolder);
    return true;
}

Action parseArgs()
{
    const char * const ILLEGAL_OPTION_FORMAT = "Error: Illegal option '%s'\n\n";
    const QStringList args = QCoreApplication::arguments();

    bool fileMode = false;
    for (int i = 1; i < args.count(); i++) {
        const QString &curArg = args.at(i);
        if (fileMode || (curArg[0] != '-')) {
            // Filenames only
            switch (args.count() - i) {
            case 2: // second last
                inputFileName = curArg;
                break;

            case 1: // last
                if (inputFileName.isEmpty()) {
                    inputFileName = curArg;
                } else {
                    outputFileName = curArg;
                }
                break;

            default:
                if (!fileMode)
                    printf(ILLEGAL_OPTION_FORMAT, qPrintable(curArg));

                error = true;
                return ACTION_USAGE;
            }
        } else {
            // Options or filenames
            QString opt;
            if (curArg[1] == '-') {
                // Long option
                opt = curArg.mid(2);
                if (opt.isEmpty()) {
                    fileMode = true;
                } else if (opt.startsWith(QLatin1String("config="))) {
                    if (!readConfigFile(opt.mid(6 + 1))) {
                        return ACTION_QUIT;
                    }
                } else if (opt.startsWith("folder=")) {
                    virtualFolder = opt.mid(6 + 1);
                } else if (opt == QLatin1String("force")) {
                    forceOverwrite = true;
                } else if (opt == QLatin1String("help")) {
                    return ACTION_USAGE;
                } else if (opt.startsWith("namespace=")) {
                    nameSpace = opt.mid(9 + 1);
                } else if (opt == QLatin1String("qch")) {
                    helpProjectMode = false;
                } else if (opt == QLatin1String("qhp")) {
                    helpProjectMode = true;
                } else if (opt == QLatin1String("verbose")) {
                    verbose = true;
                } else if (opt == QLatin1String("version")) {
                    return ACTION_VERSION;
                } else {
                    printf(ILLEGAL_OPTION_FORMAT, qPrintable(curArg));
                    error = true;
                    return ACTION_USAGE;
                }
            } else {
                // Short option
                opt = curArg.mid(1);
                if (opt.count() != 1) {
                    printf(ILLEGAL_OPTION_FORMAT, qPrintable(curArg));
                    error = true;
                    return ACTION_USAGE;
                }

                switch (opt.at(0).toAscii()) {
                case 'h':
                    return ACTION_USAGE;

                case 'v':
                    verbose = true;
                    break;

                case 'V':
                    return ACTION_VERSION;

                default:
                    printf(ILLEGAL_OPTION_FORMAT, qPrintable(curArg));
                    error = true;
                    return ACTION_USAGE;
                }
            }
        }
    }

    if (inputFileName.isEmpty()) {
        printf("Error: No input filename specified\n\n");
        error = true;
        return ACTION_USAGE;
    }

    if (nameSpace.isEmpty()) {
        printf("Error: Namespace must not be empty.\n\n");
        error = true;
        return ACTION_USAGE;
    }

    if (virtualFolder.isEmpty()) {
        printf("Error: Virtual folder must not be empty.\n\n");
        error = true;
        return ACTION_USAGE;
    }

    return ACTION_RUN;
}

QT_END_NAMESPACE

int main(int argc, char ** argv)
{
    QCoreApplication app(argc, argv);
    const Action action = parseArgs();

    switch (action) {
    case ACTION_RUN:
        error = ! run();
        break;

    case ACTION_VERSION:
        printVersion();
        break;

    case ACTION_QUIT:
        break;

    case ACTION_USAGE: // fall through
    default:
        printUsage();
        break;

    }

    return error ? 1 : 0;
}
