/*
    SPDX-FileCopyrightText: 2000-2004 Michael Edwardes <mte@users.sourceforge.net>
    SPDX-FileCopyrightText: 2000-2004 Javier Campos Morales <javi_c@users.sourceforge.net>
    SPDX-FileCopyrightText: 2000-2004 Felix Rodriguez <frodriguez@users.sourceforge.net>
    SPDX-FileCopyrightText: 2000-2004 John C <thetacoturtle@users.sourceforge.net>
    SPDX-FileCopyrightText: 2000-2004 Thomas Baumgart <ipwizard@users.sourceforge.net>
    SPDX-FileCopyrightText: 2000-2004 Kevin Tambascio <ktambascio@users.sourceforge.net>
    SPDX-FileCopyrightText: 2000-2004 Ace Jones <ace.j@hotpop.com>
    SPDX-FileCopyrightText: 2017 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "kreportsview_p.h"

#include <typeinfo>

// ----------------------------------------------------------------------------
// QT Includes

#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QFile>
#include <QTimer>
#include <QClipboard>
#include <QList>
#include <QVBoxLayout>
#include <QMimeData>
#include <QIcon>
#include <QUrlQuery>
#include <QFileInfo>
#include <QFileDialog>
#include <QLocale>
#include <QTextCodec>
#include <QMenu>
#ifdef ENABLE_WEBENGINE
#include <QWebEngineView>
#else
#include <KWebView>
#endif

// ----------------------------------------------------------------------------
// KDE Includes

#include <KMessageBox>
#include <KRecentDirs>
#include <KLocalizedString>

// ----------------------------------------------------------------------------
// Project Includes

#include "ui_reportcontrol.h"

#include "mymoneyfile.h"
#include "mymoneyreport.h"
#include "mymoneyexception.h"
#include "kmymoneysettings.h"
#include "querytable.h"
#include "objectinfotable.h"
#include "kreportconfigurationfilterdlg.h"
#include "icons/icons.h"
#include "kbalancechartdlg.h"
#include <kmymoneywebpage.h>
#include "tocitem.h"
#include "tocitemgroup.h"
#include "tocitemreport.h"
#include "kreportchartview.h"
#include "pivottable.h"
#include "reporttable.h"
#include "reportcontrolimpl.h"
#include "mymoneyenums.h"
#include "menuenums.h"

using namespace reports;
using namespace eMyMoney;
using namespace Icons;

#define VIEW_LEDGER         "ledger"
#define VIEW_SCHEDULE       "schedule"
#define VIEW_WELCOME        "welcome"
#define VIEW_HOME           "home"
#define VIEW_REPORTS        "reports"

/**
  * KReportsView Implementation
  */

KReportsView::KReportsView(QWidget *parent) :
    KMyMoneyViewBase(*new KReportsViewPrivate(this), parent)
{
    connect(pActions[eMenu::Action::ReportAccountTransactions], &QAction::triggered, this, &KReportsView::slotReportAccountTransactions);
}

KReportsView::~KReportsView()
{
}

void KReportsView::executeCustomAction(eView::Action action)
{
    Q_D(KReportsView);
    switch(action) {
    case eView::Action::Refresh:
        refresh();
        break;

    case eView::Action::SetDefaultFocus:
        QTimer::singleShot(0, d->m_tocTreeWidget, SLOT(setFocus()));
        break;

    case eView::Action::Print:
        slotPrintView();
        break;

    case eView::Action::CleanupBeforeFileClose:
        slotCloseAll();
        break;

    case eView::Action::ShowBalanceChart:
    {
        QPointer<KBalanceChartDlg> dlg = new KBalanceChartDlg(d->m_currentAccount, this);
        dlg->exec();
        delete dlg;
    }
    break;

    default:
        break;
    }
}

void KReportsView::refresh()
{
    Q_D(KReportsView);
    if (isVisible()) {
        d->loadView();
        d->m_needsRefresh = false;
    } else {
        d->m_needsRefresh = true;
    }
}

void KReportsView::showEvent(QShowEvent * event)
{
    if (MyMoneyFile::instance()->storageAttached()) {
        Q_D(KReportsView);
        if (d->m_needLoad)
            d->init();

        emit customActionRequested(View::Reports, eView::Action::AboutToShow);

        if (d->m_needsRefresh)
            refresh();

        if (auto tab = dynamic_cast<KReportTab*>(d->m_reportTabWidget->currentWidget()))
            emit reportSelected(tab->report());
        else
            emit reportSelected(MyMoneyReport());
    }
    // don't forget base class implementation
    QWidget::showEvent(event);
}

void KReportsView::updateActions(const MyMoneyObject& obj)
{
    Q_D(KReportsView);
    if (typeid(obj) != typeid(MyMoneyAccount) &&
            (obj.id().isEmpty() && d->m_currentAccount.id().isEmpty())) // do not disable actions that were already disabled)))
        return;

    const auto& acc = static_cast<const MyMoneyAccount&>(obj);

    bool b;
    if (MyMoneyFile::instance()->isStandardAccount(acc.id())) {
        b = false;
    } else {
        switch (acc.accountType()) {
        case eMyMoney::Account::Type::Asset:
        case eMyMoney::Account::Type::Liability:
        case eMyMoney::Account::Type::Equity:
        case eMyMoney::Account::Type::Checkings:
            b = true;
            break;
        default:
            b = false;
            break;
        }
    }
    pActions[eMenu::Action::ReportAccountTransactions]->setEnabled(b);

    d->m_currentAccount = acc;
}

void KReportsView::slotOpenUrl(const QUrl &url)
{
    QString view = url.fileName();
    if (view.isEmpty())
        return;
    QString command = QUrlQuery(url).queryItemValue("command");
    QString id = QUrlQuery(url).queryItemValue("id");
    QString tid = QUrlQuery(url).queryItemValue("tid");

    if (view == VIEW_REPORTS) {

        if (command.isEmpty()) {
            // slotRefreshView();
        } else if (command == QLatin1String("print"))
            slotPrintView();
        else if (command == QLatin1String("copy"))
            slotCopyView();
        else if (command == QLatin1String("save"))
            slotExportView();
        else if (command == QLatin1String("configure"))
            slotConfigure();
        else if (command == QLatin1String("duplicate"))
            slotDuplicate();
        else if (command == QLatin1String("close"))
            slotCloseCurrent();
        else if (command == QLatin1String("delete"))
            slotDelete();
        else
            qWarning() << i18n("Unknown command '%1' in KReportsView::slotOpenUrl()", qPrintable(command));

    } else if (view == VIEW_LEDGER) {
        emit selectByVariant(QVariantList {QVariant(id), QVariant(tid)}, eView::Intent::ShowTransaction);
    } else {
        qWarning() << i18n("Unknown view '%1' in KReportsView::slotOpenUrl()", qPrintable(view));
    }
}

void KReportsView::slotPrintView()
{
    Q_D(KReportsView);
    if (auto tab = dynamic_cast<KReportTab*>(d->m_reportTabWidget->currentWidget()))
        tab->print();
}

void KReportsView::slotCopyView()
{
    Q_D(KReportsView);
    if (auto tab = dynamic_cast<KReportTab*>(d->m_reportTabWidget->currentWidget()))
        tab->copyToClipboard();
}

void KReportsView::slotExportView()
{
    Q_D(KReportsView);
    if (auto tab = dynamic_cast<KReportTab*>(d->m_reportTabWidget->currentWidget())) {
        QPointer<QFileDialog> dialog = new QFileDialog(this, i18n("Export as"), KRecentDirs::dir(":kmymoney-export"));
        dialog->setMimeTypeFilters({QStringLiteral("text/csv"), QStringLiteral("text/html")});
        dialog->setFileMode(QFileDialog::AnyFile);
        dialog->setAcceptMode(QFileDialog::AcceptSave);
        dialog->selectFile(tab->report().name());

        QUrl newURL;
        QString selectedMimeType;
        if (dialog->exec() == QDialog::Accepted) {
            newURL = dialog->selectedUrls().first();
            selectedMimeType = dialog->selectedMimeTypeFilter();
        }
        delete dialog;

        if (!newURL.isEmpty()) {
            KRecentDirs::add(":kmymoney-export", newURL.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path());
            QString newName = newURL.toDisplayString(QUrl::PreferLocalFile);

            try {
                tab->saveAs(newName, selectedMimeType, true);
            } catch (const MyMoneyException &e) {
                KMessageBox::error(this, i18n("Failed to save: %1", QString::fromLatin1(e.what())));
            }
        }
    }
}

void KReportsView::slotConfigure()
{
    Q_D(KReportsView);
    QString cm = "KReportsView::slotConfigure";

    auto tab = dynamic_cast<KReportTab*>(d->m_reportTabWidget->currentWidget());

    if (!tab) // nothing to do
        return;
    int tabNr = d->m_reportTabWidget->currentIndex();

    tab->updateDataRange(); // range will be needed during configuration, but cannot be obtained earlier

    MyMoneyReport report = tab->report();
    if (report.comment() == i18n("Default Report") || report.comment() == i18n("Generated Report")) {
        report.setComment(i18n("Custom Report"));
        report.setName(i18n("%1 (Customized)", report.name()));
    }

    QPointer<KReportConfigurationFilterDlg> dlg = new KReportConfigurationFilterDlg(report);

    if (dlg->exec()) {
        MyMoneyReport newreport = dlg->getConfig();

        // If this report has an ID, then MODIFY it, otherwise ADD it
        MyMoneyFileTransaction ft;
        try {
            if (! newreport.id().isEmpty()) {
                MyMoneyFile::instance()->modifyReport(newreport);
                ft.commit();
                tab->modifyReport(newreport);

                d->m_reportTabWidget->setTabText(tabNr, newreport.name());
                d->m_reportTabWidget->setCurrentIndex(tabNr) ;
            } else {
                MyMoneyFile::instance()->addReport(newreport);
                ft.commit();

                QString reportGroupName = newreport.group();

                // find report group
                TocItemGroup* tocItemGroup = d->m_allTocItemGroups[reportGroupName];
                if (!tocItemGroup) {
                    QString error = i18n("Could not find reportgroup \"%1\" for report \"%2\".\nPlease report this error to the developer's list: kmymoney-devel@kde.org", reportGroupName, newreport.name());

                    // write to messagehandler
                    qWarning() << cm << error;

                    // also inform user
                    KMessageBox::error(d->m_reportTabWidget, error, i18n("Critical Error"));

                    // cleanup
                    delete dlg;

                    return;
                }

                // do not add TocItemReport to TocItemGroup here,
                // this is done in loadView

                d->addReportTab(newreport);
            }
        } catch (const MyMoneyException &e) {
            KMessageBox::error(this, i18n("Failed to configure report: %1", QString::fromLatin1(e.what())));
        }
    }
    delete dlg;
}

void KReportsView::slotDuplicate()
{
    Q_D(KReportsView);
    QString cm = "KReportsView::slotDuplicate";

    auto tab = dynamic_cast<KReportTab*>(d->m_reportTabWidget->currentWidget());

    if (!tab) {
        // nothing to do
        return;
    }

    MyMoneyReport dupe = tab->report();
    dupe.setName(i18n("Copy of %1", dupe.name()));
    if (dupe.comment() == i18n("Default Report"))
        dupe.setComment(i18n("Custom Report"));
    dupe.clearId();

    QPointer<KReportConfigurationFilterDlg> dlg = new KReportConfigurationFilterDlg(dupe);
    if (dlg->exec()) {
        MyMoneyReport newReport = dlg->getConfig();
        MyMoneyFileTransaction ft;
        try {
            MyMoneyFile::instance()->addReport(newReport);
            ft.commit();

            QString reportGroupName = newReport.group();

            // find report group
            TocItemGroup* tocItemGroup = d->m_allTocItemGroups[reportGroupName];
            if (!tocItemGroup) {
                QString error = i18n("Could not find reportgroup \"%1\" for report \"%2\".\nPlease report this error to the developer's list: kmymoney-devel@kde.org", reportGroupName, newReport.name());

                // write to messagehandler
                qWarning() << cm << error;

                // also inform user
                KMessageBox::error(d->m_reportTabWidget, error, i18n("Critical Error"));

                // cleanup
                delete dlg;

                return;
            }

            // do not add TocItemReport to TocItemGroup here,
            // this is done in loadView

            d->addReportTab(newReport);
        } catch (const MyMoneyException &e) {
            QString error = i18n("Cannot add report, reason: \"%1\"", e.what());

            // write to messagehandler
            qWarning() << cm << error;

            // also inform user
            KMessageBox::error(d->m_reportTabWidget, error, i18n("Critical Error"));
        }
    }
    delete dlg;
}

void KReportsView::slotDelete()
{
    Q_D(KReportsView);
    auto tab = dynamic_cast<KReportTab*>(d->m_reportTabWidget->currentWidget());
    if (!tab) {
        // nothing to do
        return;
    }

    MyMoneyReport report = tab->report();
    if (! report.id().isEmpty()) {
        if (KMessageBox::Continue == d->deleteReportDialog(report.name())) {
            // close the tab and then remove the report so that it is not
            // generated again during the following loadView() call
            slotClose(d->m_reportTabWidget->currentIndex());

            MyMoneyFileTransaction ft;
            MyMoneyFile::instance()->removeReport(report);
            ft.commit();
        }
    } else {
        KMessageBox::information(this,
                                 QString("<qt>") +
                                 i18n("<b>%1</b> is a default report, so it cannot be deleted.",
                                      report.name()) + QString("</qt>"), i18n("Delete Report?"));
    }
}

void KReportsView::slotOpenReport(const QString& id)
{
    Q_D(KReportsView);
    if (id.isEmpty()) {
        // nothing to  do
        return;
    }

    KReportTab* page = 0;

    // Find the tab which contains the report
    int index = 1;
    while (index < d->m_reportTabWidget->count()) {
        auto current = dynamic_cast<KReportTab*>(d->m_reportTabWidget->widget(index));

        if (current && current->report().id() == id) {
            page = current;
            break;
        }

        ++index;
    }

    // Show the tab, or create a new one, as needed
    if (page)
        d->m_reportTabWidget->setCurrentIndex(index);
    else
        d->addReportTab(MyMoneyFile::instance()->report(id));
}

void KReportsView::slotOpenReport(const MyMoneyReport& report)
{
    Q_D(KReportsView);
    if (d->m_needLoad)
        d->init();
    qDebug() << Q_FUNC_INFO << " " << report.name();
    KReportTab* page = 0;

    // Find the tab which contains the report indicated by this list item
    int index = 1;
    while (index < d->m_reportTabWidget->count()) {
        auto current = dynamic_cast<KReportTab*>(d->m_reportTabWidget->widget(index));

        if (current && current->report().name() == report.name()) {
            page = current;
            break;
        }

        ++index;
    }

    // Show the tab, or create a new one, as needed
    if (page)
        d->m_reportTabWidget->setCurrentIndex(index);
    else
        d->addReportTab(report);

    if (!isVisible())
        emit switchViewRequested(View::Reports);
}

void KReportsView::slotItemDoubleClicked(QTreeWidgetItem* item, int)
{
    Q_D(KReportsView);
    auto tocItem = dynamic_cast<TocItem*>(item);
    if (tocItem && !tocItem->isReport()) {
        // toggle the expanded-state for reportgroup-items
        item->setExpanded(item->isExpanded() ? false : true);

        // nothing else to do for reportgroup-items
        return;
    }

    TocItemReport* reportTocItem = dynamic_cast<TocItemReport*>(tocItem);

    MyMoneyReport& report = reportTocItem->getReport();

    KReportTab* page = 0;

    // Find the tab which contains the report indicated by this list item
    int index = 1;
    while (index < d->m_reportTabWidget->count()) {
        auto current = dynamic_cast<KReportTab*>(d->m_reportTabWidget->widget(index));
        if (current) {
            // If this report has an ID, we'll use the ID to match
            if (! report.id().isEmpty()) {
                if (current->report().id() == report.id()) {
                    page = current;
                    break;
                }
            }
            // Otherwise, use the name to match.  THIS ASSUMES that no 2 default reports
            // have the same name...but that would be pretty a boneheaded thing to do.
            else {
                if (current->report().name() == report.name()) {
                    page = current;
                    break;
                }
            }
        }

        ++index;
    }

    // Show the tab, or create a new one, as needed
    if (page)
        d->m_reportTabWidget->setCurrentIndex(index);
    else
        d->addReportTab(report);
}

void KReportsView::slotToggleChart()
{
    Q_D(KReportsView);
    if (auto tab = dynamic_cast<KReportTab*>(d->m_reportTabWidget->currentWidget()))
        tab->toggleChart();
}

void KReportsView::slotCloseCurrent()
{
    Q_D(KReportsView);
    slotClose(d->m_reportTabWidget->currentIndex());
}

void KReportsView::slotClose(int index)
{
    Q_D(KReportsView);
    if (auto tab = dynamic_cast<KReportTab*>(d->m_reportTabWidget->widget(index))) {
        d->m_reportTabWidget->removeTab(index);
        tab->setReadyToDelete(true);
    }
}

void KReportsView::slotCloseAll()
{
    Q_D(KReportsView);
    if(!d->m_needLoad) {
        while (true) {
            if (auto tab = dynamic_cast<KReportTab*>(d->m_reportTabWidget->widget(1))) {
                d->m_reportTabWidget->removeTab(1);
                tab->setReadyToDelete(true);
            } else
                break;
        }
    }
}


void KReportsView::slotListContextMenu(const QPoint & p)
{
    Q_D(KReportsView);
    auto items = d->m_tocTreeWidget->selectedItems();

    if (items.isEmpty()) {
        return;
    }

    QList<TocItem*> tocItems;
    foreach(auto item, items) {
        auto tocItem = dynamic_cast<TocItem*>(item);
        if (tocItem && tocItem->isReport()) {
            tocItems.append(tocItem);
        }
    }

    if (tocItems.isEmpty()) {
        return;
    }

    auto contextmenu = new QMenu(this);

    contextmenu->addAction(i18nc("To open a new report", "&Open"),
                           this, SLOT(slotOpenFromList()));

    contextmenu->addAction(i18nc("To print a report", "&Print"),
                           this, SLOT(slotPrintFromList()));

    if (tocItems.count() == 1) {
        contextmenu->addAction(i18nc("Configure a report", "&Configure"),
                               this, SLOT(slotConfigureFromList()));

        contextmenu->addAction(i18n("&New report"),
                               this, SLOT(slotNewFromList()));

        // Only add this option if it's a custom report. Default reports cannot be deleted

        auto reportTocItem = dynamic_cast<TocItemReport*>(tocItems.at(0));

        if (reportTocItem) {
            MyMoneyReport& report = reportTocItem->getReport();
            if (! report.id().isEmpty()) {
                contextmenu->addAction(i18n("&Delete"),
                                       this, SLOT(slotDeleteFromList()));
            }
        }
    }

    contextmenu->popup(d->m_tocTreeWidget->mapToGlobal(p));
}

void KReportsView::slotOpenFromList()
{
    Q_D(KReportsView);

    auto items = d->m_tocTreeWidget->selectedItems();

    if (items.isEmpty()) {
        return;
    }

    foreach(auto item, items) {
        auto tocItem = dynamic_cast<TocItem*>(item);
        if (tocItem && tocItem->isReport()) {
            slotItemDoubleClicked(tocItem, 0);
        }
    }
}

void KReportsView::slotPrintFromList()
{
    Q_D(KReportsView);

    auto items = d->m_tocTreeWidget->selectedItems();

    if (items.isEmpty()) {
        return;
    }

    foreach(auto item, items) {
        auto tocItem = dynamic_cast<TocItem*>(item);
        if (tocItem && tocItem->isReport()) {
            slotItemDoubleClicked(tocItem, 0);
            slotPrintView();
        }
    }
}

void KReportsView::slotConfigureFromList()
{
    Q_D(KReportsView);
    if (auto tocItem = dynamic_cast<TocItem*>(d->m_tocTreeWidget->currentItem())) {
        slotItemDoubleClicked(tocItem, 0);
        slotConfigure();
    }
}

void KReportsView::slotNewFromList()
{
    Q_D(KReportsView);
    if (auto tocItem = dynamic_cast<TocItem*>(d->m_tocTreeWidget->currentItem())) {
        slotItemDoubleClicked(tocItem, 0);
        slotDuplicate();
    }
}

void KReportsView::slotDeleteFromList()
{
    Q_D(KReportsView);
    if (auto tocItem = dynamic_cast<TocItem*>(d->m_tocTreeWidget->currentItem())) {
        if (auto reportTocItem = dynamic_cast<TocItemReport*>(tocItem)) {
            MyMoneyReport& report = reportTocItem->getReport();

            // If this report does not have an ID, it's a default report and cannot be deleted
            if (! report.id().isEmpty() &&
                    KMessageBox::Continue == d->deleteReportDialog(report.name())) {
                // check if report's tab is open; start from 1 because 0 is toc tab
                for (int i = 1; i < d->m_reportTabWidget->count(); ++i) {
                    auto tab = dynamic_cast<KReportTab*>(d->m_reportTabWidget->widget(i));
                    if (tab && tab->report().id() == report.id()) {
                        slotClose(i); // if open, close it, so no crash when switching to it
                        break;
                    }
                }
                MyMoneyFileTransaction ft;
                MyMoneyFile::instance()->removeReport(report);
                ft.commit();
            }
        }
    }
}

void KReportsView::slotSelectByObject(const MyMoneyObject& obj, eView::Intent intent)
{
    switch(intent) {
    case eView::Intent::UpdateActions:
        updateActions(obj);
        break;

    case eView::Intent::OpenObject:
        slotOpenReport(static_cast<const MyMoneyReport&>(obj));
    default:
        break;
    }
}

void KReportsView::slotReportAccountTransactions()
{
    Q_D(KReportsView);
    // Generate a transaction report that contains transactions for only the
    // currently selected account.
    if (!d->m_currentAccount.id().isEmpty()) {
        MyMoneyReport report(
            eMyMoney::Report::RowType::Account,
            eMyMoney::Report::QueryColumn::Number | eMyMoney::Report::QueryColumn::Payee | eMyMoney::Report::QueryColumn::Category,
            eMyMoney::TransactionFilter::Date::YearToDate,
            eMyMoney::Report::DetailLevel::All,
            i18n("%1 YTD Account Transactions", d->m_currentAccount.name()),
            i18n("Generated Report")
        );
        report.setGroup(i18n("Transactions"));
        report.addAccount(d->m_currentAccount.id());
        emit customActionRequested(View::Reports, eView::Action::SwitchView);
        slotOpenReport(report);
    }
}

// Make sure, that these definitions are only used within this file
// this does not seem to be necessary, but when building RPMs the
// build option 'final' is used and all CPP files are concatenated.
// So it could well be, that in another CPP file these definitions
// are also used.
#undef VIEW_LEDGER
#undef VIEW_SCHEDULE
#undef VIEW_WELCOME
#undef VIEW_HOME
#undef VIEW_REPORTS
