/*******************************************************************
* reportassistantdialog.cpp
* Copyright 2009,2010    Dario Andres Rodriguez <andresbajotierra@gmail.com>
* 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 "reportassistantdialog.h"

#include <QCloseEvent>

#include <KMessageBox>
#include <KLocalizedString>
#include <KConfigGroup>

#include "drkonqi.h"

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

#include "assistantpage_bugzilla_version.h"
#include "crashedapplication.h"
#include "aboutbugreportingdialog.h"
#include "reportassistantpages_base.h"
#include "reportassistantpages_bugzilla.h"
#include "reportassistantpages_bugzilla_duplicates.h"
#include "reportinterface.h"

static const char KDE_BUGZILLA_DESCRIPTION[] = I18N_NOOP("the KDE Bug Tracking System");

ReportAssistantDialog::ReportAssistantDialog(QWidget * parent) :
        KAssistantDialog(parent),
        m_aboutBugReportingDialog(nullptr),
        m_reportInterface(new ReportInterface(this)),
        m_canClose(false)
{
    setAttribute(Qt::WA_DeleteOnClose, true);

    //Set window properties
    setWindowTitle(i18nc("@title:window","Crash Reporting Assistant"));
    setWindowIcon(QIcon::fromTheme(QStringLiteral("tools-report-bug")));

    connect(this, &ReportAssistantDialog::currentPageChanged, this, &ReportAssistantDialog::currentPageChanged_slot);
    connect(button(QDialogButtonBox::Help), &QPushButton::clicked, this, &ReportAssistantDialog::showHelp);

    //Create the assistant pages

    //-Introduction Page
    KConfigGroup group(KSharedConfig::openConfig(), "ReportAssistant");
    const bool skipIntroduction = group.readEntry("SkipIntroduction", false);

    if (!skipIntroduction) {
        IntroductionPage * m_introduction = new IntroductionPage(this);

        KPageWidgetItem * m_introductionPage = new KPageWidgetItem(m_introduction,
                QLatin1String(PAGE_INTRODUCTION_ID));
        m_pageWidgetMap.insert(QLatin1String(PAGE_INTRODUCTION_ID),m_introductionPage);
        m_introductionPage->setHeader(i18nc("@title","Welcome to the Reporting Assistant"));
        m_introductionPage->setIcon(QIcon::fromTheme(QStringLiteral("tools-report-bug")));

        addPage(m_introductionPage);
    }

    //-Bug Awareness Page
    BugAwarenessPage * m_awareness = new BugAwarenessPage(this);
    connectSignals(m_awareness);

    KPageWidgetItem * m_awarenessPage = new KPageWidgetItem(m_awareness,
                                                                QLatin1String(PAGE_AWARENESS_ID));
    m_pageWidgetMap.insert(QLatin1String(PAGE_AWARENESS_ID),m_awarenessPage);
    m_awarenessPage->setHeader(i18nc("@title","What do you know about the crash?"));
    m_awarenessPage->setIcon(QIcon::fromTheme(QStringLiteral("checkbox")));

    //-Crash Information Page
    CrashInformationPage * m_backtrace = new CrashInformationPage(this);
    connectSignals(m_backtrace);

    KPageWidgetItem * m_backtracePage = new KPageWidgetItem(m_backtrace,
                                                        QLatin1String(PAGE_CRASHINFORMATION_ID));
    m_pageWidgetMap.insert(QLatin1String(PAGE_CRASHINFORMATION_ID),m_backtracePage);
    m_backtracePage->setHeader(i18nc("@title","Fetching the Backtrace (Automatic Crash Information)"));
    m_backtracePage->setIcon(QIcon::fromTheme(QStringLiteral("run-build")));

    //-Results Page
    ConclusionPage * m_conclusions = new ConclusionPage(this);
    connectSignals(m_conclusions);

    KPageWidgetItem * m_conclusionsPage = new KPageWidgetItem(m_conclusions,
                                                                QLatin1String(PAGE_CONCLUSIONS_ID));
    m_pageWidgetMap.insert(QLatin1String(PAGE_CONCLUSIONS_ID),m_conclusionsPage);
    m_conclusionsPage->setHeader(i18nc("@title","Results of the Analyzed Crash Details"));
    m_conclusionsPage->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information")));
    connect(m_conclusions, &ConclusionPage::finished, this, &ReportAssistantDialog::assistantFinished);

    // Version check page
    BugzillaVersionPage *versionPage = new BugzillaVersionPage(this);
    m_pageWidgetMap.insert(QLatin1String(PAGE_BZVERSION_ID), versionPage->item());

    //-Bugzilla Login
    BugzillaLoginPage * m_bugzillaLogin =  new BugzillaLoginPage(this);
    connectSignals(m_bugzillaLogin);

    KPageWidgetItem * m_bugzillaLoginPage = new KPageWidgetItem(m_bugzillaLogin,
                                                                QLatin1String(PAGE_BZLOGIN_ID));
    m_pageWidgetMap.insert(QLatin1String(PAGE_BZLOGIN_ID),m_bugzillaLoginPage);
    m_bugzillaLoginPage->setHeader(i18nc("@title", "Login into %1", i18n(KDE_BUGZILLA_DESCRIPTION)));
    m_bugzillaLoginPage->setIcon(QIcon::fromTheme(QStringLiteral("user-identity")));
    connect(m_bugzillaLogin, &BugzillaLoginPage::loggedTurnToNextPage, this, &ReportAssistantDialog::loginFinished);

    //-Bugzilla duplicates
    BugzillaDuplicatesPage * m_bugzillaDuplicates =  new BugzillaDuplicatesPage(this);
    connectSignals(m_bugzillaDuplicates);

    KPageWidgetItem * m_bugzillaDuplicatesPage = new KPageWidgetItem(m_bugzillaDuplicates,
                                                            QLatin1String(PAGE_BZDUPLICATES_ID));
    m_pageWidgetMap.insert(QLatin1String(PAGE_BZDUPLICATES_ID),m_bugzillaDuplicatesPage);
    m_bugzillaDuplicatesPage->setHeader(i18nc("@title","Look for Possible Duplicate Reports"));
    m_bugzillaDuplicatesPage->setIcon(QIcon::fromTheme(QStringLiteral("repository")));

    //-Bugzilla information
    BugzillaInformationPage * m_bugzillaInformation =  new BugzillaInformationPage(this);
    connectSignals(m_bugzillaInformation);

    KPageWidgetItem * m_bugzillaInformationPage = new KPageWidgetItem(m_bugzillaInformation,
                                                                QLatin1String(PAGE_BZDETAILS_ID));
    m_pageWidgetMap.insert(QLatin1String(PAGE_BZDETAILS_ID),m_bugzillaInformationPage);
    m_bugzillaInformationPage->setHeader(i18nc("@title","Enter the Details about the Crash"));
    m_bugzillaInformationPage->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));

    //-Bugzilla Report Preview
    BugzillaPreviewPage * m_bugzillaPreview =  new BugzillaPreviewPage(this);

    KPageWidgetItem * m_bugzillaPreviewPage = new KPageWidgetItem(m_bugzillaPreview,
                                                                QLatin1String(PAGE_BZPREVIEW_ID));
    m_pageWidgetMap.insert(QLatin1String(PAGE_BZPREVIEW_ID),m_bugzillaPreviewPage);
    m_bugzillaPreviewPage->setHeader(i18nc("@title","Preview the Report"));
    m_bugzillaPreviewPage->setIcon(QIcon::fromTheme(QStringLiteral("document-preview")));

    //-Bugzilla commit
    BugzillaSendPage * m_bugzillaSend =  new BugzillaSendPage(this);

    KPageWidgetItem * m_bugzillaSendPage = new KPageWidgetItem(m_bugzillaSend,
                                                                    QLatin1String(PAGE_BZSEND_ID));
    m_pageWidgetMap.insert(QLatin1String(PAGE_BZSEND_ID),m_bugzillaSendPage);
    m_bugzillaSendPage->setHeader(i18nc("@title","Sending the Crash Report"));
    m_bugzillaSendPage->setIcon(QIcon::fromTheme(QStringLiteral("applications-internet")));
    connect(m_bugzillaSend, &BugzillaSendPage::finished, this, &ReportAssistantDialog::assistantFinished);

    //TODO Remember to keep the pages ordered
    addPage(m_awarenessPage);
    addPage(m_backtracePage);
    addPage(m_conclusionsPage);
    addPage(versionPage->item());
    addPage(m_bugzillaLoginPage);
    addPage(m_bugzillaDuplicatesPage);
    addPage(m_bugzillaInformationPage);
    addPage(m_bugzillaPreviewPage);
    addPage(m_bugzillaSendPage);

    // Force a 16:9 ratio for nice appearance by default.
    QSize aspect(16, 9);
    aspect.scale(sizeHint(), Qt::KeepAspectRatioByExpanding);
    resize(aspect);
}

ReportAssistantDialog::~ReportAssistantDialog()
{
}

void ReportAssistantDialog::setAboutToSend(bool aboutTo)
{
    if (aboutTo) {
        m_nextButtonIconCache = nextButton()->icon();
        m_nextButtonTextCache = nextButton()->text();
        nextButton()->setIcon(QIcon::fromTheme(QStringLiteral("document-send")));
        nextButton()->setText(i18nc("@action button to submit report", "Submit"));
        return;
    }
    nextButton()->setIcon(m_nextButtonIconCache);
    nextButton()->setText(m_nextButtonTextCache);
    m_nextButtonIconCache = QIcon();
    m_nextButtonTextCache = QString();
}

void ReportAssistantDialog::connectSignals(ReportAssistantPage * page)
{
    //React to the changes in the assistant pages
    connect(page, &ReportAssistantPage::completeChanged,
             this, &ReportAssistantDialog::completeChanged);
}

void ReportAssistantDialog::currentPageChanged_slot(KPageWidgetItem * current , KPageWidgetItem * before)
{
    //Page changed
    buttonBox()->button(QDialogButtonBox::Cancel)->setEnabled(true);
    m_canClose = false;

    //Save data of the previous page
    if (before) {
        ReportAssistantPage *beforePage = qobject_cast<ReportAssistantPage *>(before->widget());
        beforePage->aboutToHide();
    }

    //Load data of the current(new) page
    if (current) {
        ReportAssistantPage *currentPage = qobject_cast<ReportAssistantPage *>(current->widget());
        nextButton()->setEnabled(currentPage->isComplete());
        currentPage->aboutToShow();

        // If the current page is the last one, disable all the buttons until the bug is sent
        if (current->name() == QLatin1String(PAGE_BZSEND_ID)) {
            nextButton()->setEnabled(false);
            backButton()->setEnabled(false);
            finishButton()->setEnabled(false);
        }
    }
}

void ReportAssistantDialog::completeChanged(ReportAssistantPage* page, bool isComplete)
{
    if (page == qobject_cast<ReportAssistantPage *>(currentPage()->widget())) {
        nextButton()->setEnabled(isComplete);
    }
}

void ReportAssistantDialog::assistantFinished(bool showBack)
{
    //The assistant finished: allow the user to close the dialog normally

    nextButton()->setEnabled(false);
    backButton()->setEnabled(showBack);
    finishButton()->setEnabled(true);
    buttonBox()->button(QDialogButtonBox::Cancel)->setEnabled(false);

    m_canClose = true;
}

void ReportAssistantDialog::loginFinished()
{
    //Bugzilla login finished, go to the next page
    if (currentPage()->name() == QLatin1String(PAGE_BZLOGIN_ID)) {
        next();
    }
}

void ReportAssistantDialog::showHelp()
{
    //Show the bug reporting guide dialog
    if (!m_aboutBugReportingDialog) {
        m_aboutBugReportingDialog = new AboutBugReportingDialog();
    }
    m_aboutBugReportingDialog->show();
    m_aboutBugReportingDialog->raise();
    m_aboutBugReportingDialog->activateWindow();
    m_aboutBugReportingDialog->showSection(QLatin1String(PAGE_HELP_BEGIN_ID));
    m_aboutBugReportingDialog->showSection(currentPage()->name());
}

//Override KAssistantDialog "next" page implementation
void ReportAssistantDialog::next()
{
    // FIXME: this entire function is a bit weird. It'd likely make more sense to
    // use the page appropriateness more globally. i.e. mark pages inappropriate
    // when they are not applicable based on earlier settings done (e.g. put
    // a conclusion page under/after the awareness page but only mark it
    // appropriate if the data is not useful. that way kassistantdialog would
    // just skip over the page).

    //Allow the widget to Ask a question to the user before changing the page
    ReportAssistantPage *page = qobject_cast<ReportAssistantPage *>(currentPage()->widget());
    if (page && !page->showNextPage()) {
        return;
    }

    const QString name = currentPage()->name();

    //If the information the user can provide is not useful, skip the backtrace page
    if (name == QLatin1String(PAGE_AWARENESS_ID))
    {
        //Force save settings in the current page
        page->aboutToHide();

        if (!(m_reportInterface->isBugAwarenessPageDataUseful()))
        {
            setCurrentPage(m_pageWidgetMap.value(QLatin1String(PAGE_CONCLUSIONS_ID)));
            return;
        }
    } else if (name == QLatin1String(PAGE_CRASHINFORMATION_ID)){
        //Force save settings in current page
        page->aboutToHide();

        //If the crash is worth reporting and it is BKO, skip the Conclusions page
        if (m_reportInterface->isWorthReporting() &&
            DrKonqi::crashedApplication()->bugReportAddress().isKdeBugzilla())
        {
            // Depending on whether the page is appropriate either go to version
            // check page or login page.
            const auto versionPage = m_pageWidgetMap.value(QLatin1String(PAGE_BZVERSION_ID));
            const auto loginPage = m_pageWidgetMap.value(QLatin1String(PAGE_BZLOGIN_ID));
            setCurrentPage(isAppropriate(versionPage) ? versionPage : loginPage);
            return;
        }
    } else if (name == QLatin1String(PAGE_BZDUPLICATES_ID)) {
        //a duplicate has been found, yet the report is not being attached
        if (m_reportInterface->duplicateId() && !m_reportInterface->attachToBugNumber()) {
            setCurrentPage(m_pageWidgetMap.value(QLatin1String(PAGE_CONCLUSIONS_ID)));
            return;
        }
    }

    KAssistantDialog::next();
}

//Override KAssistantDialog "back"(previous) page implementation
//It has to mirror the custom next() implementation
void ReportAssistantDialog::back()
 {
    if (currentPage()->name() == QLatin1String(PAGE_CONCLUSIONS_ID))
    {
        if (m_reportInterface->duplicateId() && !m_reportInterface->attachToBugNumber()) {
            setCurrentPage(m_pageWidgetMap.value(QLatin1String(PAGE_BZDUPLICATES_ID)));
            return;
        }
        if (!(m_reportInterface->isBugAwarenessPageDataUseful()))
        {
            setCurrentPage(m_pageWidgetMap.value(QLatin1String(PAGE_AWARENESS_ID)));
            return;
        }
    }

    if (currentPage()->name() == QLatin1String(PAGE_BZLOGIN_ID))
    {
        if (m_reportInterface->isWorthReporting() &&
            DrKonqi::crashedApplication()->bugReportAddress().isKdeBugzilla())
        {
            setCurrentPage(m_pageWidgetMap.value(QLatin1String(PAGE_CRASHINFORMATION_ID)));
            return;
        }
    }

    KAssistantDialog::back();
}

void ReportAssistantDialog::reject()
{
    close();
}

void ReportAssistantDialog::closeEvent(QCloseEvent * event)
{
    //Handle the close event
    if (!m_canClose) {
        //If the assistant didn't finished yet, offer the user the possibilities to
        //Close, Cancel, or Save the bug report and Close"

        KGuiItem closeItem = KStandardGuiItem::close();
        closeItem.setText(i18nc("@action:button", "Close the assistant"));

        KGuiItem keepOpenItem = KStandardGuiItem::cancel();
        keepOpenItem.setText(i18nc("@action:button", "Cancel"));

        BacktraceParser::Usefulness use =
                DrKonqi::debuggerManager()->backtraceGenerator()->parser()->backtraceUsefulness();
        if (use == BacktraceParser::ReallyUseful || use == BacktraceParser::MayBeUseful) {
            //Backtrace is still useful, let the user save it.
            KGuiItem saveBacktraceItem = KStandardGuiItem::save();
            saveBacktraceItem.setText(i18nc("@action:button", "Save information and close"));

            int ret = KMessageBox::questionYesNoCancel(this,
                           xi18nc("@info","Do you really want to close the bug reporting assistant? "
                           "<note>The crash information is still valid, so "
                           "you can save the report before closing if you want.</note>"),
                           i18nc("@title:window","Close the Assistant"),
                           closeItem, saveBacktraceItem, keepOpenItem, QString(), KMessageBox::Dangerous);
            if(ret == KMessageBox::Yes)
            {
                event->accept();
            } else if (ret == KMessageBox::No) {
                //Save backtrace and accept event (dialog will be closed)
                DrKonqi::saveReport(reportInterface()->generateReportFullText(ReportInterface::DrKonqiStamp::Exclude,
                                                                              ReportInterface::Backtrace::Complete));
                event->accept();
            } else {
                event->ignore();
            }
        } else {
            if (KMessageBox::questionYesNo(this, i18nc("@info","Do you really want to close the bug "
                                                   "reporting assistant?"),
                                       i18nc("@title:window","Close the Assistant"),
                                           closeItem, keepOpenItem, QString(), KMessageBox::Dangerous)
                                        == KMessageBox::Yes) {
                event->accept();
            } else {
                event->ignore();
            }
        }
    } else {
        event->accept();
    }
}
