/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/

#include "autotesticons.h"
#include "testresultdelegate.h"
#include "testresultmodel.h"

#include <utils/qtcassert.h>

#include <QFontMetrics>
#include <QIcon>

namespace Autotest {
namespace Internal {

/********************************* TestResultItem ******************************************/

TestResultItem::TestResultItem(const TestResultPtr &testResult)
    : m_testResult(testResult)
{
}

static QIcon testResultIcon(Result::Type result) {
    const static QIcon icons[] = {
        Icons::RESULT_PASS.icon(),
        Icons::RESULT_FAIL.icon(),
        Icons::RESULT_XFAIL.icon(),
        Icons::RESULT_XPASS.icon(),
        Icons::RESULT_SKIP.icon(),
        Icons::RESULT_BLACKLISTEDPASS.icon(),
        Icons::RESULT_BLACKLISTEDFAIL.icon(),
        Icons::RESULT_BENCHMARK.icon(),
        Icons::RESULT_MESSAGEDEBUG.icon(),
        Icons::RESULT_MESSAGEDEBUG.icon(), // Info gets the same handling as Debug for now
        Icons::RESULT_MESSAGEWARN.icon(),
        Icons::RESULT_MESSAGEFATAL.icon(),
        Icons::RESULT_MESSAGEFATAL.icon(), // System gets same handling as Fatal for now
        QIcon(),
        Icons::RESULT_MESSAGEPASSWARN.icon(),
        Icons::RESULT_MESSAGEFAILWARN.icon(),
    }; // provide an icon for unknown??

    if (result < 0 || result >= Result::MessageInternal) {
        switch (result) {
        case Result::MessageTestCaseSuccess:
            return icons[Result::Pass];
        case Result::MessageTestCaseFail:
            return icons[Result::Fail];
        case Result::MessageTestCaseSuccessWarn:
            return icons[14];
        case Result::MessageTestCaseFailWarn:
            return icons[15];
        default:
            return QIcon();
        }
    }
    return icons[result];
}

QVariant TestResultItem::data(int column, int role) const
{
    switch (role) {
    case Qt::DecorationRole: {
        if (!m_testResult)
            return QVariant();
        const Result::Type result = m_testResult->result();
        if (result == Result::MessageLocation && parent())
            return parent()->data(column, role);
        return testResultIcon(result);
    }
    case Qt::DisplayRole:
        return m_testResult ? m_testResult->outputString(true) : QVariant();
    default:
        return Utils::TreeItem::data(column, role);
    }
}

void TestResultItem::updateDescription(const QString &description)
{
    QTC_ASSERT(m_testResult, return);

    m_testResult->setDescription(description);
}

void TestResultItem::updateResult(bool &changed, Result::Type addedChildType)
{
    changed = false;
    const Result::Type old = m_testResult->result();
    if (old == Result::MessageTestCaseFailWarn) // can't become worse
        return;
    if (!TestResult::isMessageCaseStart(old))
        return;

    Result::Type newResult = Result::MessageTestCaseSuccess;
    switch (addedChildType) {
    case Result::Fail:
    case Result::MessageFatal:
    case Result::UnexpectedPass:
    case Result::MessageTestCaseFail:
        newResult = (old == Result::MessageTestCaseSuccessWarn) ? Result::MessageTestCaseFailWarn
                                                                : Result::MessageTestCaseFail;
        break;
    case Result::MessageTestCaseFailWarn:
        newResult = Result::MessageTestCaseFailWarn;
        break;
    case Result::ExpectedFail:
    case Result::MessageWarn:
    case Result::MessageSystem:
    case Result::Skip:
    case Result::BlacklistedFail:
    case Result::BlacklistedPass:
    case Result::MessageTestCaseSuccessWarn:
        newResult = (old == Result::MessageTestCaseFail) ? Result::MessageTestCaseFailWarn
                                                         : Result::MessageTestCaseSuccessWarn;
        break;
    case Result::Pass:
    case Result::MessageTestCaseSuccess:
        newResult = (old == Result::MessageIntermediate || old == Result::MessageTestCaseStart)
                ? Result::MessageTestCaseSuccess : old;
        break;
    default:
        break;
    }
    changed = old != newResult;
    if (changed)
        m_testResult->setResult(newResult);
}

TestResultItem *TestResultItem::intermediateFor(const TestResultItem *item) const
{
    QTC_ASSERT(item, return nullptr);
    const TestResult *otherResult = item->testResult();
    for (int row = childCount() - 1; row >= 0; --row) {
        TestResultItem *child = static_cast<TestResultItem *>(childAt(row));
        const TestResult *testResult = child->testResult();
        if (testResult->result() != Result::MessageIntermediate)
            continue;
        if (testResult->isIntermediateFor(otherResult))
            return child;
    }
    return nullptr;
}

TestResultItem *TestResultItem::createAndAddIntermediateFor(const TestResultItem *child)
{
    TestResultPtr result(m_testResult->createIntermediateResultFor(child->testResult()));
    QTC_ASSERT(!result.isNull(), return nullptr);
    result->setResult(Result::MessageIntermediate);
    TestResultItem *intermediate = new TestResultItem(result);
    appendChild(intermediate);
    return intermediate;
}

/********************************* TestResultModel *****************************************/

TestResultModel::TestResultModel(QObject *parent)
    : Utils::TreeModel<>(parent)
{
}

void TestResultModel::updateParent(const TestResultItem *item)
{
    QTC_ASSERT(item, return);
    QTC_ASSERT(item->testResult(), return);
    Utils::TreeItem *parentItem = item->parent();
    if (parentItem == rootItem()) // do not update invisible root item
        return;
    bool changed = false;
    TestResultItem *parentResultItem = static_cast<TestResultItem *>(parentItem);
    parentResultItem->updateResult(changed, item->testResult()->result());
    if (!changed)
        return;
    emit dataChanged(parentItem->index(), parentItem->index());
    updateParent(parentResultItem);
}

void TestResultModel::addTestResult(const TestResultPtr &testResult, bool autoExpand)
{
    const int lastRow = rootItem()->childCount() - 1;
    if (testResult->result() == Result::MessageCurrentTest) {
        // MessageCurrentTest should always be the last top level item
        if (lastRow >= 0) {
            TestResultItem *current = static_cast<TestResultItem *>(rootItem()->childAt(lastRow));
            const TestResult *result = current->testResult();
            if (result && result->result() == Result::MessageCurrentTest) {
                current->updateDescription(testResult->description());
                emit dataChanged(current->index(), current->index());
                return;
            }
        }

        rootItem()->appendChild(new TestResultItem(testResult));
        return;
    }

    if (testResult->result() == Result::MessageDisabledTests)
        m_disabled += testResult->line();
    m_testResultCount[testResult->result()]++;

    TestResultItem *newItem = new TestResultItem(testResult);
    TestResultItem *parentItem = findParentItemFor(newItem);
    addFileName(testResult->fileName()); // ensure we calculate the results pane correctly
    if (parentItem) {
        parentItem->appendChild(newItem);
        if (autoExpand)
            parentItem->expand();
        updateParent(newItem);
    } else {
        if (lastRow >= 0) {
            TestResultItem *current = static_cast<TestResultItem *>(rootItem()->childAt(lastRow));
            const TestResult *result = current->testResult();
            if (result && result->result() == Result::MessageCurrentTest) {
                rootItem()->insertChild(current->index().row(), newItem);
                return;
            }
        }
        // there is no MessageCurrentTest at the last row, but we have a toplevel item - just add it
        rootItem()->appendChild(newItem);
    }
}

void TestResultModel::removeCurrentTestMessage()
{
    std::vector<Utils::TreeItem *> topLevelItems(rootItem()->begin(), rootItem()->end());
    auto end = topLevelItems.rend();
    for (auto it = topLevelItems.rbegin(); it != end; ++it) {
        TestResultItem *current = static_cast<TestResultItem *>(*it);
        if (current->testResult()->result() == Result::MessageCurrentTest) {
            destroyItem(current);
            break;
        }
    }
}

void TestResultModel::clearTestResults()
{
    clear();
    m_testResultCount.clear();
    m_disabled = 0;
    m_fileNames.clear();
    m_maxWidthOfFileName = 0;
    m_widthOfLineNumber = 0;
}

const TestResult *TestResultModel::testResult(const QModelIndex &idx)
{
    if (idx.isValid())
        return static_cast<TestResultItem *>(itemForIndex(idx))->testResult();

    return nullptr;
}

void TestResultModel::recalculateMaxWidthOfFileName(const QFont &font)
{
    const QFontMetrics fm(font);
    m_maxWidthOfFileName = 0;
    for (const QString &fileName : m_fileNames) {
        int pos = fileName.lastIndexOf('/');
        m_maxWidthOfFileName = qMax(m_maxWidthOfFileName, fm.width(fileName.mid(pos + 1)));
    }
}

void TestResultModel::addFileName(const QString &fileName)
{
    const QFontMetrics fm(m_measurementFont);
    int pos = fileName.lastIndexOf('/');
    m_maxWidthOfFileName = qMax(m_maxWidthOfFileName, fm.width(fileName.mid(pos + 1)));
    m_fileNames.insert(fileName);
}

int TestResultModel::maxWidthOfFileName(const QFont &font)
{
    if (font != m_measurementFont)
        recalculateMaxWidthOfFileName(font);
    return m_maxWidthOfFileName;
}

int TestResultModel::maxWidthOfLineNumber(const QFont &font)
{
    if (m_widthOfLineNumber == 0 || font != m_measurementFont) {
        QFontMetrics fm(font);
        m_measurementFont = font;
        m_widthOfLineNumber = fm.width("88888");
    }
    return m_widthOfLineNumber;
}

TestResultItem *TestResultModel::findParentItemFor(const TestResultItem *item,
                                                   const TestResultItem *startItem) const
{
    QTC_ASSERT(item, return nullptr);
    TestResultItem *root = startItem ? const_cast<TestResultItem *>(startItem) : nullptr;
    const TestResult *result = item->testResult();
    const QString &name = result->name();
    const QString &id = result->id();

    if (root == nullptr && !name.isEmpty()) {
        for (int row = rootItem()->childCount() - 1; row >= 0; --row) {
            TestResultItem *tmp = static_cast<TestResultItem *>(rootItem()->childAt(row));
            auto tmpTestResult = tmp->testResult();
            if (tmpTestResult->id() == id && tmpTestResult->name() == name) {
                root = tmp;
                break;
            }
        }
    }
    if (root == nullptr)
        return root;

    bool needsIntermediate = false;
    auto predicate = [result, &needsIntermediate](Utils::TreeItem *it) {
        TestResultItem *currentItem = static_cast<TestResultItem *>(it);
        return currentItem->testResult()->isDirectParentOf(result, &needsIntermediate);
    };
    TestResultItem *parent = static_cast<TestResultItem *>(root->reverseFindAnyChild(predicate));
    if (parent) {
        if (needsIntermediate) {
            // check if the intermediate is present already
            if (TestResultItem *intermediate = parent->intermediateFor(item))
                return intermediate;
            return parent->createAndAddIntermediateFor(item);
        }
        return parent;
    }
    return root;
}

/********************************** Filter Model **********************************/

TestResultFilterModel::TestResultFilterModel(TestResultModel *sourceModel, QObject *parent)
    : QSortFilterProxyModel(parent),
      m_sourceModel(sourceModel)
{
    setSourceModel(sourceModel);
    enableAllResultTypes();
}

void TestResultFilterModel::enableAllResultTypes()
{
    m_enabled << Result::Pass << Result::Fail << Result::ExpectedFail
              << Result::UnexpectedPass << Result::Skip << Result::MessageDebug
              << Result::MessageWarn << Result::MessageInternal << Result::MessageLocation
              << Result::MessageFatal << Result::Invalid << Result::BlacklistedPass
              << Result::BlacklistedFail << Result::Benchmark << Result::MessageIntermediate
              << Result::MessageCurrentTest << Result::MessageTestCaseStart
              << Result::MessageTestCaseSuccess << Result::MessageTestCaseSuccessWarn
              << Result::MessageTestCaseFail << Result::MessageTestCaseFailWarn
              << Result::MessageTestCaseEnd
              << Result::MessageInfo << Result::MessageSystem;
    invalidateFilter();
}

void TestResultFilterModel::toggleTestResultType(Result::Type type)
{
    if (m_enabled.contains(type)) {
        m_enabled.remove(type);
        if (type == Result::MessageInternal)
            m_enabled.remove(Result::MessageTestCaseEnd);
        if (type == Result::MessageDebug)
            m_enabled.remove(Result::MessageInfo);
        if (type == Result::MessageWarn)
            m_enabled.remove(Result::MessageSystem);
    } else {
        m_enabled.insert(type);
        if (type == Result::MessageInternal)
            m_enabled.insert(Result::MessageTestCaseEnd);
        if (type == Result::MessageDebug)
            m_enabled.insert(Result::MessageInfo);
        if (type == Result::MessageWarn)
            m_enabled.insert(Result::MessageSystem);
    }
    invalidateFilter();
}

void TestResultFilterModel::clearTestResults()
{
    m_sourceModel->clearTestResults();
}

bool TestResultFilterModel::hasResults()
{
    return rowCount(QModelIndex());
}

const TestResult *TestResultFilterModel::testResult(const QModelIndex &index) const
{
    return m_sourceModel->testResult(mapToSource(index));
}

bool TestResultFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
    QModelIndex index = m_sourceModel->index(sourceRow, 0, sourceParent);
    if (!index.isValid())
        return false;
    Result::Type resultType = m_sourceModel->testResult(index)->result();
    switch (resultType) {
    case Result::MessageTestCaseSuccess:
        return m_enabled.contains(Result::Pass);
    case Result::MessageTestCaseFail:
    case Result::MessageTestCaseSuccessWarn:
    case Result::MessageTestCaseFailWarn:
        return acceptTestCaseResult(index);
    default:
        return m_enabled.contains(resultType);
    }
}

bool TestResultFilterModel::acceptTestCaseResult(const QModelIndex &srcIndex) const
{
    for (int row = 0, count = m_sourceModel->rowCount(srcIndex); row < count; ++row) {
        const QModelIndex &child = m_sourceModel->index(row, 0, srcIndex);
        Result::Type type = m_sourceModel->testResult(child)->result();
        if (type == Result::MessageTestCaseSuccess)
            type = Result::Pass;
        if (type == Result::MessageTestCaseFail || type == Result::MessageTestCaseFailWarn
                || type == Result::MessageTestCaseSuccessWarn) {
            if (acceptTestCaseResult(child))
                return true;
        } else if (m_enabled.contains(type)) {
            return true;
        }
    }
    return false;
}

} // namespace Internal
} // namespace Autotest
