/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
**
** GNU Lesser General Public License Usage
**
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**************************************************************************/

#include "sbsv2parser.h"

#include <extensionsystem/pluginmanager.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/taskhub.h>

using namespace Qt4ProjectManager;
using namespace ProjectExplorer;
using namespace ProjectExplorer::Constants;

/*
 * This parser takes a somewhat unusal approach of just eating most of its
 * input :-)
 *
 * Only when the XML-based log file generated by SBSv2 is announced it will
 * open that file and parse that. Tasks will then get generated by passing
 * any CDATA found in the XML file on to its child parsers (using STDERR).
 *
 * In additon <error> and <warning> tags are reported, too.
 */

SbsV2Parser::SbsV2Parser() :
    m_hub(0)
{
    setObjectName(QLatin1String("SbsV2Parser"));
    ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
    m_hub = pm->getObject<TaskHub>();
}

void SbsV2Parser::stdOutput(const QString &line)
{
    // Eat most output!
    if (line.startsWith(QLatin1String("sbs: build log in "))) {
        QString logfile = QDir::fromNativeSeparators(line.mid(18).trimmed());
        parseLogFile(logfile);
        addTask(ProjectExplorer::Task(Task::Unknown, tr("SBSv2 build log"),
                                      logfile, -1,
                                      QLatin1String(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM)));
    }
}

void SbsV2Parser::stdError(const QString &line)
{
    // Eat all output!
    Q_UNUSED(line);
}

void SbsV2Parser::taskAdded(const ProjectExplorer::Task &task)
{
    // Fix pathes:
    ProjectExplorer::Task tmp(task);

    if (!tmp.file.isEmpty()) {
        QFileInfo fi(tmp.file);
        if (!fi.isAbsolute()) {
            if (m_currentSource.exists(tmp.file))
                tmp.file = m_currentSource.absoluteFilePath(tmp.file);
            else if (m_currentTarget.exists(tmp.file))
                tmp.file = m_currentTarget.absoluteFilePath(tmp.file);
        }
    }

    // Do not report tasks from our children via the normal channel:
    // We do not want them get registered with the Compile output window!
    m_hub->addTask(tmp);
}

void SbsV2Parser::parseLogFile(const QString &file)
{
    QFile logFile(file);
    logFile.open(QIODevice::ReadOnly);
    m_log.setDevice(&logFile);

    if (m_log.readNextStartElement()) {
         if (m_log.name() == QLatin1String("buildlog"))
             readBuildLog();
         else
             m_log.raiseError(tr("The file '%1' is not a SBSv2 log file.").arg(file));
     }
}

void SbsV2Parser::readBuildLog()
{
    Q_ASSERT(m_log.isStartElement() && m_log.name() == QLatin1String("buildlog"));

     while (m_log.readNextStartElement()) {
         if (m_log.name() == QLatin1String("error"))
             readError();
         else if (m_log.name() == QLatin1String("warning"))
             readWarning();
         else if (m_log.name() == QLatin1String("recipe"))
             readRecipe();
         else
             m_log.skipCurrentElement();
     }
}

void SbsV2Parser::readError()
{
    Q_ASSERT(m_log.isStartElement() && m_log.name() == QLatin1String("error"));

    QString error = m_log.readElementText();
    addTask(Task(Task::Error, error, QString(), -1, Constants::TASK_CATEGORY_BUILDSYSTEM));
}

void SbsV2Parser::readWarning()
{
    Q_ASSERT(m_log.isStartElement() && m_log.name() == QLatin1String("warning"));

    QString warning = m_log.readElementText();
    addTask(Task(Task::Warning, warning, QString(), -1, Constants::TASK_CATEGORY_BUILDSYSTEM));
}

void SbsV2Parser::readRecipe()
{
    Q_ASSERT(m_log.isStartElement() && m_log.name() == QLatin1String("recipe"));

    const QString name = m_log.attributes().value(QLatin1String("name")).toString();
    m_currentSource = QDir(m_log.attributes().value("source").toString()).absolutePath();
    m_currentTarget = QDir(m_log.attributes().value("target").toString()).absolutePath();

    int returnCode = 0;
    QString outputText;
    QXmlStreamReader::TokenType tokenType = QXmlStreamReader::Invalid;
    while ((tokenType = m_log.readNext()) != QXmlStreamReader::Invalid) {
        if (tokenType == QXmlStreamReader::Characters) {
            outputText.append(m_log.text());
        } else if (tokenType == QXmlStreamReader::StartElement) {
            if (m_log.name() == QLatin1String("status")) {
                if (m_log.attributes().value(QLatin1String("exit")) == QLatin1String("failed"))
                    returnCode = m_log.attributes().value(QLatin1String("code")).toString().toInt();
            }
        } else if (tokenType == QXmlStreamReader::EndElement) {
            if (m_log.name() == QLatin1String("recipe"))
                break;
        }
    }

    QStringList output = outputText.split(QChar('\n'));
    outputText.clear();
    foreach (const QString &line, output) {
        if (line.isEmpty())
            continue;
        if (line.startsWith(QChar('+'))) {
            outputText.append(tr("Running command: %1\n").arg(line.mid(2)));
            continue;
        }
        outputText.append(line);
        outputText.append(QChar('\n'));
        if (name == QLatin1String("compile") || name == QLatin1String("qmake_extra_pre_targetdep"))
            IOutputParser::stdError(line);
    }

    if (returnCode != 0) {
        //: %1 is the SBSv2 build recipe name, %2 the return code of the failed command
        QString description = tr("Recipe %1 failed with exit code %2.").arg(name).arg(returnCode);
        m_hub->addTask(Task(Task::Error, description, QString(), -1,
                            ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM));
        m_hub->addTask(Task(Task::Unknown, outputText, QString(), -1,
                            ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM));
    }
}

// Unit tests:

#ifdef WITH_TESTS
#   include <QTest>

#   include "qt4projectmanagerplugin.h"

#   include "projectexplorer/outputparser_test.h"

using namespace Qt4ProjectManager::Internal;

void Qt4ProjectManagerPlugin::testSbsV2OutputParsers_data()
{
    QTest::addColumn<QString>("input");
    QTest::addColumn<OutputParserTester::Channel>("inputChannel");
    QTest::addColumn<QString>("childStdOutLines");
    QTest::addColumn<QString>("childStdErrLines");
    QTest::addColumn<QList<ProjectExplorer::Task> >("tasks");
    QTest::addColumn<QString>("outputLines");


    QTest::newRow("eat stdout")
            << QString::fromLatin1("   Sometext") << OutputParserTester::STDOUT
            << QString() << QString()
            << QList<ProjectExplorer::Task>()
            << QString();
    QTest::newRow("eat stderr")
            << QString::fromLatin1("   Sometext") << OutputParserTester::STDERR
            << QString() << QString()
            << QList<ProjectExplorer::Task>()
            << QString();

    QTest::newRow("build log")
            << QString::fromLatin1("sbs: build log in X:/epoc32/build/Makefile.2010-08-10-15-25-52.log") << OutputParserTester::STDOUT
            << QString() << QString()
            << (QList<ProjectExplorer::Task>()
                    << ProjectExplorer::Task(Task::Unknown, QLatin1String("SBSv2 build log"),
                                             QLatin1String("X:/epoc32/build/Makefile.2010-08-10-15-25-52.log"), -1,
                                             QLatin1String(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM)))
            << QString();
}

void Qt4ProjectManagerPlugin::testSbsV2OutputParsers()
{
    OutputParserTester testbench;
    testbench.appendOutputParser(new SbsV2Parser);
    QFETCH(QString, input);
    QFETCH(OutputParserTester::Channel, inputChannel);
    QFETCH(QList<Task>, tasks);
    QFETCH(QString, childStdOutLines);
    QFETCH(QString, childStdErrLines);
    QFETCH(QString, outputLines);

    testbench.testParsing(input, inputChannel,
                          tasks, childStdOutLines, childStdErrLines,
                          outputLines);
}
#endif
