/*******************************************************************
* duplicatefinderjob.cpp
* Copyright 2011    Matthias Fuchs <mat69@gmx.net>
* Copyright 2019    Harald Sitter <sitter@kde.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 of
* the License, or (at your option) any later version.
*
* 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 "duplicatefinderjob.h"

#include "drkonqi_debug.h"

#include "backtracegenerator.h"
#include "parser/backtraceparser.h"
#include "debuggermanager.h"
#include "drkonqi.h"
#include "parsebugbacktraces.h"

DuplicateFinderJob::DuplicateFinderJob(const QList<Bugzilla::Bug::Ptr> &bugs, BugzillaManager *manager, QObject *parent)
  : KJob(parent),
    m_manager(manager),
    m_bugs(bugs)
{
    qCDebug(DRKONQI_LOG) << "Possible duplicates:" << m_bugs.size();
    connect(m_manager, &BugzillaManager::bugReportFetched,
            this, &DuplicateFinderJob::slotBugReportFetched);
    connect(m_manager, &BugzillaManager::bugReportError,
            this, &DuplicateFinderJob::slotError);

    connect(m_manager, &BugzillaManager::commentsFetched,
            this, &DuplicateFinderJob::slotCommentsFetched);
    connect(m_manager, &BugzillaManager::commentsError,
            this, &DuplicateFinderJob::slotError);
}

DuplicateFinderJob::~DuplicateFinderJob()
{
}

void DuplicateFinderJob::start()
{
    analyzeNextBug();
}

DuplicateFinderJob::Result DuplicateFinderJob::result() const
{
    return m_result;
}

void DuplicateFinderJob::analyzeNextBug()
{
    if (m_bugs.isEmpty()) {
        emitResult();
        return;
    }

    m_bug = m_bugs.takeFirst();
    qCDebug(DRKONQI_LOG) << "Fetching:" << m_bug->id();
    m_manager->fetchComments(m_bug, this);
}

void DuplicateFinderJob::fetchBug(int bugId)
{
    if (bugId > 0) {
        qCDebug(DRKONQI_LOG) << "Fetching:" << bugId;
        m_manager->fetchBugReport(bugId, this);
    } else {
        qCDebug(DRKONQI_LOG) << "Bug id not valid:" << bugId;
        analyzeNextBug();
    }
}

void DuplicateFinderJob::slotBugReportFetched(const Bugzilla::Bug::Ptr &bug, QObject *owner)
{
    if (this != owner) {
        return;
    }

    m_bug = bug;
    qCDebug(DRKONQI_LOG) << "Fetching:" << m_bug->id();
    m_manager->fetchComments(m_bug, this);
}

void DuplicateFinderJob::slotCommentsFetched(const QList<Bugzilla::Comment::Ptr> &comments, QObject *owner)
{
    if (this != owner) {
        return;
    }

    // NOTE: we do not hold the comments in our bug object, once they go out
    //   of scope they are gone again. We have no use for keeping them in memory
    //   a user might look at 3 out of 20 bugs, and for those we can simply
    //   request the comments again instead of holding the potentially very large
    //   comments in memory.

    ParseBugBacktraces parse(comments, this);
    parse.parse();

    BacktraceGenerator *btGenerator = DrKonqi::debuggerManager()->backtraceGenerator();
    const ParseBugBacktraces::DuplicateRating rating = parse.findDuplicate(btGenerator->parser()->parsedBacktraceLines());
    qCDebug(DRKONQI_LOG) << "Duplicate rating:" << rating;

    //TODO handle more cases here
    if (rating != ParseBugBacktraces::PerfectDuplicate) {
        qCDebug(DRKONQI_LOG) << "Bug" << m_bug->id() << "most likely not a duplicate:" << rating;
        analyzeNextBug();
        return;
    }

    bool unknownStatus = (m_bug->status() == Bugzilla::Bug::Status::Unknown);
    bool unknownResolution = (m_bug->resolution() == Bugzilla::Bug::Resolution::Unknown);

    //The Bug is a duplicate, now find out the status and resolution of the existing report
    if (m_bug->resolution() == Bugzilla::Bug::Resolution::DUPLICATE) {
        qCDebug(DRKONQI_LOG) << "Found duplicate is a duplicate itself.";
        if (!m_result.duplicate) {
            m_result.duplicate = m_bug->id();
        }
        fetchBug(m_bug->dupe_of());
    } else if (unknownStatus || unknownResolution) {
        // A resolution is unknown when the bug is unresolved.
        // Status generally is never unknown.
        qCDebug(DRKONQI_LOG) << "Either the status or the resolution is unknown.";
        qCDebug(DRKONQI_LOG) << "Status \"" << m_bug->status() << "\" known:" << !unknownStatus;
        qCDebug(DRKONQI_LOG) << "Resolution \"" << m_bug->resolution() << "\" known:" << !unknownResolution;
        analyzeNextBug();
    } else {
        if (!m_result.duplicate) {
            m_result.duplicate = m_bug->id();
        }
        m_result.parentDuplicate = m_bug->id();
        m_result.status = m_bug->status();
        m_result.resolution = m_bug->resolution();
        qCDebug(DRKONQI_LOG) << "Found duplicate information (id/status/resolution):"
                             << m_bug->id() << m_bug->status() << m_bug->resolution();
        emitResult();
    }
}

void DuplicateFinderJob::slotError(const QString &message, QObject *owner)
{
    if (this != owner) {
        return;
    }
    qCDebug(DRKONQI_LOG) << "Error fetching bug:" << message;
    analyzeNextBug();
}

