/*
    SPDX-FileCopyrightText: 2007 Thomas Baumgart <ipwizard@users.sourceforge.net>
    SPDX-FileCopyrightText: 2017, 2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "kaccountsview_p.h"

#include <typeinfo>

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

#include <QTimer>
#include <QMenu>
#include <QAction>
#include <QBitArray>

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

#include <KMessageBox>

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

#include "onlinejobadministration.h"
#include "knewaccountwizard.h"
#include "kmymoneyutils.h"
#include "kmymoneysettings.h"
#include "storageenums.h"
#include "menuenums.h"

using namespace Icons;

KAccountsView::KAccountsView(QWidget *parent) :
    KMyMoneyAccountsViewBase(*new KAccountsViewPrivate(this), parent)
{
    Q_D(KAccountsView);
    d->ui->setupUi(this);

    connect(pActions[eMenu::Action::NewAccount],          &QAction::triggered, this, &KAccountsView::slotNewAccount);
    connect(pActions[eMenu::Action::EditAccount],         &QAction::triggered, this, &KAccountsView::slotEditAccount);
    connect(pActions[eMenu::Action::DeleteAccount],       &QAction::triggered, this, &KAccountsView::slotDeleteAccount);
    connect(pActions[eMenu::Action::CloseAccount],        &QAction::triggered, this, &KAccountsView::slotCloseAccount);
    connect(pActions[eMenu::Action::ReopenAccount],       &QAction::triggered, this, &KAccountsView::slotReopenAccount);
    connect(pActions[eMenu::Action::ChartAccountBalance], &QAction::triggered, this, &KAccountsView::slotChartAccountBalance);
    connect(pActions[eMenu::Action::MapOnlineAccount],    &QAction::triggered, this, &KAccountsView::slotAccountMapOnline);
    connect(pActions[eMenu::Action::UnmapOnlineAccount],  &QAction::triggered, this, &KAccountsView::slotAccountUnmapOnline);
    connect(pActions[eMenu::Action::UpdateAccount],       &QAction::triggered, this, &KAccountsView::slotAccountUpdateOnline);
    connect(pActions[eMenu::Action::UpdateAllAccounts],   &QAction::triggered, this, &KAccountsView::slotAccountUpdateOnlineAll);
}

KAccountsView::~KAccountsView()
{
}

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

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

    default:
        break;
    }
}

void KAccountsView::refresh()
{
    Q_D(KAccountsView);
    if (!isVisible()) {
        d->m_needsRefresh = true;
        return;
    }
    d->m_needsRefresh = false;
    // TODO: check why the invalidate is needed here
    d->m_proxyModel->invalidate();
    d->m_proxyModel->setHideClosedAccounts(!KMyMoneySettings::showAllAccounts());
    d->m_proxyModel->setHideEquityAccounts(!KMyMoneySettings::expertMode());
    if (KMyMoneySettings::showCategoriesInAccountsView()) {
        d->m_proxyModel->addAccountGroup(QVector<eMyMoney::Account::Type> {eMyMoney::Account::Type::Income, eMyMoney::Account::Type::Expense});
    } else {
        d->m_proxyModel->removeAccountType(eMyMoney::Account::Type::Income);
        d->m_proxyModel->removeAccountType(eMyMoney::Account::Type::Expense);
    }

    // reinitialize the default state of the hidden categories label
    d->m_haveUnusedCategories = false;
    d->ui->m_hiddenCategories->hide();  // hides label
    d->m_proxyModel->setHideUnusedIncomeExpenseAccounts(KMyMoneySettings::hideUnusedCategory());
}

void KAccountsView::showEvent(QShowEvent * event)
{
    Q_D(KAccountsView);
    if (!d->m_proxyModel)
        d->init();

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

    if (d->m_needsRefresh)
        refresh();

    // don't forget base class implementation
    QWidget::showEvent(event);
}

void KAccountsView::updateActions(const MyMoneyObject& obj)
{
    Q_D(KAccountsView);

    const auto file = MyMoneyFile::instance();

    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);

    const QVector<eMenu::Action> actionsToBeDisabled {
        eMenu::Action::NewAccount, eMenu::Action::EditAccount, eMenu::Action::DeleteAccount,
        eMenu::Action::CloseAccount, eMenu::Action::ReopenAccount,
        eMenu::Action::ChartAccountBalance,
        eMenu::Action::UnmapOnlineAccount, eMenu::Action::MapOnlineAccount,
        eMenu::Action::UpdateAccount,
    };

    for (const auto& a : actionsToBeDisabled)
        pActions[a]->setEnabled(false);

    pActions[eMenu::Action::NewAccount]->setEnabled(true);
    pActions[eMenu::Action::UpdateAllAccounts]->setEnabled(KMyMoneyUtils::canUpdateAllAccounts());

    if (acc.id().isEmpty()) {
        d->m_currentAccount = MyMoneyAccount();
        return;
    } else if (file->isStandardAccount(acc.id())) {
        d->m_currentAccount = acc;
        return;
    }
    d->m_currentAccount = acc;

    switch (acc.accountGroup()) {
    case eMyMoney::Account::Type::Asset:
    case eMyMoney::Account::Type::Liability:
    case eMyMoney::Account::Type::Equity:
    {
        pActions[eMenu::Action::EditAccount]->setEnabled(true);
        pActions[eMenu::Action::DeleteAccount]->setEnabled(!file->isReferenced(acc));

        auto b = acc.isClosed() ? true : false;
        pActions[eMenu::Action::ReopenAccount]->setEnabled(b);
        pActions[eMenu::Action::CloseAccount]->setEnabled(!b);

        if (!acc.isClosed()) {
            b = (d->canCloseAccount(acc) == KAccountsViewPrivate::AccountCanClose) ? true : false;
            pActions[eMenu::Action::CloseAccount]->setEnabled(b);
            d->hintCloseAccountAction(acc, pActions[eMenu::Action::CloseAccount]);
        }

        pActions[eMenu::Action::ChartAccountBalance]->setEnabled(true);

        if (d->m_currentAccount.hasOnlineMapping()) {
            pActions[eMenu::Action::UnmapOnlineAccount]->setEnabled(true);

            if (d->m_onlinePlugins) {
                // check if provider is available
                QMap<QString, KMyMoneyPlugin::OnlinePlugin*>::const_iterator it_p;
                it_p = d->m_onlinePlugins->constFind(d->m_currentAccount.onlineBankingSettings().value(QLatin1String("provider")).toLower());
                if (it_p != d->m_onlinePlugins->constEnd()) {
                    QStringList protocols;
                    (*it_p)->protocols(protocols);
                    if (protocols.count() > 0) {
                        pActions[eMenu::Action::UpdateAccount]->setEnabled(true);
                    }
                }
            }

        } else {
            pActions[eMenu::Action::MapOnlineAccount]->setEnabled(!acc.isClosed() && d->m_onlinePlugins && !d->m_onlinePlugins->isEmpty());
        }

        break;
    }
    default:
        break;
    }

#if 0
    // It is unclear to me, what the following code should do
    // as it just does nothing
    QBitArray skip((int)eStorage::Reference::Count);
    if (!d->m_currentAccount.id().isEmpty()) {
        if (!file->isStandardAccount(d->m_currentAccount.id())) {
            switch (d->m_currentAccount.accountGroup()) {
            case eMyMoney::Account::Type::Asset:
            case eMyMoney::Account::Type::Liability:
            case eMyMoney::Account::Type::Equity:

                break;

            default:
                break;
            }
        }
    }
#endif

}

/**
  * The view is notified that an unused income expense account has been hidden.
  */
void KAccountsView::slotUnusedIncomeExpenseAccountHidden()
{
    Q_D(KAccountsView);
    d->m_haveUnusedCategories = true;
    d->ui->m_hiddenCategories->setVisible(d->m_haveUnusedCategories);
}

void KAccountsView::slotNetWorthChanged(const MyMoneyMoney &netWorth)
{
    Q_D(KAccountsView);
    d->netBalProChanged(netWorth, d->ui->m_totalProfitsLabel, View::Accounts);
}

void KAccountsView::slotShowAccountMenu(const MyMoneyAccount& acc)
{
    Q_UNUSED(acc);
    pMenus[eMenu::Menu::Account]->exec(QCursor::pos());
}

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

    case eView::Intent::OpenContextMenu:
        slotShowAccountMenu(static_cast<const MyMoneyAccount&>(obj));
        break;

    default:
        break;
    }
}

void KAccountsView::slotSelectByVariant(const QVariantList& variant, eView::Intent intent)
{
    Q_D(KAccountsView);
    switch (intent) {
    case eView::Intent::UpdateNetWorth:
        if (variant.count() == 1)
            slotNetWorthChanged(variant.first().value<MyMoneyMoney>());
        break;

    case eView::Intent::SetOnlinePlugins:
        if (variant.count() == 1)
            d->m_onlinePlugins = static_cast<QMap<QString, KMyMoneyPlugin::OnlinePlugin*>*>(variant.first().value<void*>());
        break;

    default:
        break;
    }
}

void KAccountsView::slotNewAccount()
{
    Q_D(KAccountsView);
    MyMoneyAccount account;
    account.setOpeningDate(KMyMoneySettings::firstFiscalDate());
    account.setParentAccountId(d->m_currentAccount.id());
    account.setAccountType(d->m_currentAccount.accountType());
    NewAccountWizard::Wizard::newAccount(account);
}

void KAccountsView::slotEditAccount()
{
    Q_D(KAccountsView);

    switch (d->m_currentAccount.accountType()) {
    case eMyMoney::Account::Type::Loan:
    case eMyMoney::Account::Type::AssetLoan:
        d->editLoan();
        break;
    default:
        d->editAccount();
        break;
    }
    emit selectByObject(d->m_currentAccount, eView::Intent::None);
}

void KAccountsView::slotDeleteAccount()
{
    Q_D(KAccountsView);
    if (d->m_currentAccount.id().isEmpty())
        return;  // need an account ID

    const auto file = MyMoneyFile::instance();
    // can't delete standard accounts or account which still have transactions assigned
    if (file->isStandardAccount(d->m_currentAccount.id()))
        return;

    // check if the account is referenced by a transaction or schedule
    QBitArray skip((int)eStorage::Reference::Count);
    skip.fill(false);
    skip.setBit((int)eStorage::Reference::Account);
    skip.setBit((int)eStorage::Reference::Institution);
    skip.setBit((int)eStorage::Reference::Payee);
    skip.setBit((int)eStorage::Reference::Tag);
    skip.setBit((int)eStorage::Reference::Security);
    skip.setBit((int)eStorage::Reference::Currency);
    skip.setBit((int)eStorage::Reference::Price);
    if (file->isReferenced(d->m_currentAccount, skip))
        return;

    MyMoneyFileTransaction ft;

    // retain the account name for a possible later usage in the error message box
    // since the account removal notifies the views the selected account can be changed
    // so we make sure by doing this that we display the correct name in the error message
    auto selectedAccountName = d->m_currentAccount.name();

    try {
        file->removeAccount(d->m_currentAccount);
        d->m_currentAccount.clearId();
        emit selectByObject(MyMoneyAccount(), eView::Intent::None);
        ft.commit();
    } catch (const MyMoneyException &e) {
        KMessageBox::error(this, i18n("Unable to delete account '%1'. Cause: %2", selectedAccountName, QString::fromLatin1(e.what())));
    }
}

void KAccountsView::slotCloseAccount()
{
    Q_D(KAccountsView);
    MyMoneyFileTransaction ft;
    MyMoneyFile* file = MyMoneyFile::instance();

    try {
        // in case of investment, try to close the sub-accounts first
        if (d->m_currentAccount.accountType() == eMyMoney::Account::Type::Investment) {
            d->closeSubAccounts(d->m_currentAccount);
        }
        d->m_currentAccount.setClosed(true);
        file->modifyAccount(d->m_currentAccount);
        emit selectByObject(d->m_currentAccount, eView::Intent::None);
        ft.commit();
        if (!KMyMoneySettings::showAllAccounts())
            KMessageBox::information(this,
                                     i18n("<qt>You have closed this account. It remains in the system because you have transactions which still refer to it, "
                                          "but it is not shown in the views. You can make it visible again by going to the View menu and selecting <b>Show all "
                                          "accounts</b> or by deselecting the <b>Do not show closed accounts</b> setting.</qt>"),
                                     i18n("Information"),
                                     "CloseAccountInfo");
    } catch (const MyMoneyException& e) {
        qDebug() << e.what();
    }
}

void KAccountsView::slotReopenAccount()
{
    Q_D(KAccountsView);
    const auto file = MyMoneyFile::instance();
    MyMoneyFileTransaction ft;
    try {
        auto& acc = d->m_currentAccount;
        while (acc.isClosed()) {
            acc.setClosed(false);
            file->modifyAccount(acc);
            acc = file->account(acc.parentAccountId());
        }
        emit selectByObject(d->m_currentAccount, eView::Intent::None);
        ft.commit();
    } catch (const MyMoneyException &) {
    }
}

void KAccountsView::slotChartAccountBalance()
{
    Q_D(KAccountsView);
    if (!d->m_currentAccount.id().isEmpty()) {
        emit customActionRequested(View::Accounts, eView::Action::ShowBalanceChart);
    }
}

void KAccountsView::slotNewCategory()
{
    Q_D(KAccountsView);
    KNewAccountDlg::newCategory(d->m_currentAccount, MyMoneyAccount());
}

void KAccountsView::slotNewPayee(const QString& nameBase, QString& id)
{
    KMyMoneyUtils::newPayee(nameBase, id);
}

void KAccountsView::slotAccountUnmapOnline()
{
    Q_D(KAccountsView);
    // no account selected
    if (d->m_currentAccount.id().isEmpty())
        return;

    // not a mapped account
    if (!d->m_currentAccount.hasOnlineMapping())
        return;

    if (KMessageBox::warningYesNo(this, QString("<qt>%1</qt>").arg(i18n("Do you really want to remove the mapping of account <b>%1</b> to an online account? Depending on the details of the online banking method used, this action cannot be reverted.", d->m_currentAccount.name())), i18n("Remove mapping to online account")) == KMessageBox::Yes) {
        MyMoneyFileTransaction ft;
        try {
            d->m_currentAccount.setOnlineBankingSettings(MyMoneyKeyValueContainer());
            // Avoid showing an oline balance
            d->m_currentAccount.deletePair(QStringLiteral("lastStatementBalance"));
            // delete the kvp that is used in MyMoneyStatementReader too
            // we should really get rid of it, but since I don't know what it
            // is good for, I'll keep it around. (ipwizard)
            d->m_currentAccount.deletePair(QStringLiteral("StatementKey"));
            MyMoneyFile::instance()->modifyAccount(d->m_currentAccount);
            ft.commit();
            // The mapping could disable the online task system
            onlineJobAdministration::instance()->updateOnlineTaskProperties();
        } catch (const MyMoneyException &e) {
            KMessageBox::error(this, i18n("Unable to unmap account from online account: %1", QString::fromLatin1(e.what())));
        }
    }
    updateActions(d->m_currentAccount);
}

void KAccountsView::slotAccountMapOnline()
{
    Q_D(KAccountsView);
    // no account selected
    if (d->m_currentAccount.id().isEmpty())
        return;

    // already an account mapped
    if (d->m_currentAccount.hasOnlineMapping())
        return;

    // check if user tries to map a brokerageAccount
    if (d->m_currentAccount.name().contains(i18n(" (Brokerage)"))) {
        if (KMessageBox::warningContinueCancel(this, i18n("You try to map a brokerage account to an online account. This is usually not advisable. In general, the investment account should be mapped to the online account. Please cancel if you intended to map the investment account, continue otherwise"), i18n("Mapping brokerage account")) == KMessageBox::Cancel) {
            return;
        }
    }
    if (!d->m_onlinePlugins)
        return;

    // if we have more than one provider let the user select the current provider
    QString provider;
    QMap<QString, KMyMoneyPlugin::OnlinePlugin*>::const_iterator it_p;
    switch (d->m_onlinePlugins->count()) {
    case 0:
        break;
    case 1:
        provider = d->m_onlinePlugins->begin().key();
        break;
    default: {
        QMenu popup(this);
        popup.setTitle(i18n("Select online banking plugin"));

        // Populate the pick list with all the provider
        for (it_p = d->m_onlinePlugins->constBegin(); it_p != d->m_onlinePlugins->constEnd(); ++it_p) {
            popup.addAction(it_p.key())->setData(it_p.key());
        }

        QAction *item = popup.actions()[0];
        if (item) {
            popup.setActiveAction(item);
        }

        // cancelled
        if ((item = popup.exec(QCursor::pos(), item)) == 0) {
            return;
        }

        provider = item->data().toString();
    }
    break;
    }

    if (provider.isEmpty())
        return;

    // find the provider
    it_p = d->m_onlinePlugins->constFind(provider.toLower());
    if (it_p != d->m_onlinePlugins->constEnd()) {
        // plugin found, call it
        MyMoneyKeyValueContainer settings;
        if ((*it_p)->mapAccount(d->m_currentAccount, settings)) {
            settings["provider"] = provider.toLower();
            MyMoneyAccount acc(d->m_currentAccount);
            acc.setOnlineBankingSettings(settings);
            MyMoneyFileTransaction ft;
            try {
                MyMoneyFile::instance()->modifyAccount(acc);
                ft.commit();
                // The mapping could enable the online task system
                onlineJobAdministration::instance()->updateOnlineTaskProperties();
            } catch (const MyMoneyException &e) {
                KMessageBox::error(this, i18n("Unable to map account to online account: %1", QString::fromLatin1(e.what())));
            }
        }
    }
    updateActions(d->m_currentAccount);
}

void KAccountsView::slotAccountUpdateOnlineAll()
{
    Q_D(KAccountsView);

    QList<MyMoneyAccount> accList;
    MyMoneyFile::instance()->accountList(accList);

    QList<MyMoneyAccount> mappedAccList;
    Q_FOREACH(auto account, accList) {
        if (account.hasOnlineMapping())
            mappedAccList += account;
    }

    d->accountsUpdateOnline(mappedAccList);
}

void KAccountsView::slotAccountUpdateOnline()
{
    Q_D(KAccountsView);
    // no account selected
    if (d->m_currentAccount.id().isEmpty())
        return;

    // no online account mapped
    if (!d->m_currentAccount.hasOnlineMapping())
        return;

    d->accountsUpdateOnline(QList<MyMoneyAccount> { d->m_currentAccount } );
}
