// Copyright (c) 2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <qt/walletcontroller.h>

#include <interfaces/handler.h>
#include <interfaces/node.h>

#include <algorithm>

#include <QApplication>
#include <QMessageBox>
#include <QMutexLocker>
#include <QThread>
#include <QWindow>

WalletController::WalletController(interfaces::Node& node, const PlatformStyle* platform_style, OptionsModel* options_model, QObject* parent)
    : QObject(parent)
    , m_node(node)
    , m_platform_style(platform_style)
    , m_options_model(options_model)
{
    m_handler_load_wallet = m_node.handleLoadWallet([this](std::unique_ptr<interfaces::Wallet> wallet) {
        getOrCreateWallet(std::move(wallet));
    });

    for (std::unique_ptr<interfaces::Wallet>& wallet : m_node.getWallets()) {
        getOrCreateWallet(std::move(wallet));
    }

    m_activity_thread.start();
}

// Not using the default destructor because not all member types definitions are
// available in the header, just forward declared.
WalletController::~WalletController()
{
    m_activity_thread.quit();
    m_activity_thread.wait();
}

std::vector<WalletModel*> WalletController::getWallets() const
{
    QMutexLocker locker(&m_mutex);
    return m_wallets;
}

std::vector<std::string> WalletController::getWalletsAvailableToOpen() const
{
    QMutexLocker locker(&m_mutex);
    std::vector<std::string> wallets = m_node.listWalletDir();
    for (WalletModel* wallet_model : m_wallets) {
        auto it = std::remove(wallets.begin(), wallets.end(), wallet_model->wallet().getWalletName());
        if (it != wallets.end()) wallets.erase(it);
    }
    return wallets;
}

OpenWalletActivity* WalletController::openWallet(const std::string& name, QWidget* parent)
{
    OpenWalletActivity* activity = new OpenWalletActivity(this, name);
    activity->moveToThread(&m_activity_thread);
    return activity;
}

void WalletController::closeWallet(WalletModel* wallet_model, QWidget* parent)
{
    QMessageBox box(parent);
    box.setWindowTitle(tr("Close wallet"));
    box.setText(tr("Are you sure you wish to close wallet <i>%1</i>?").arg(wallet_model->getDisplayName()));
    box.setInformativeText(tr("Closing the wallet for too long can result in having to resync the entire chain if pruning is enabled."));
    box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel);
    box.setDefaultButton(QMessageBox::Yes);
    if (box.exec() != QMessageBox::Yes) return;

    // First remove wallet from node.
    wallet_model->wallet().remove();
    // Now release the model.
    removeAndDeleteWallet(wallet_model);
}

WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wallet> wallet)
{
    QMutexLocker locker(&m_mutex);

    // Return model instance if exists.
    if (!m_wallets.empty()) {
        std::string name = wallet->getWalletName();
        for (WalletModel* wallet_model : m_wallets) {
            if (wallet_model->wallet().getWalletName() == name) {
                return wallet_model;
            }
        }
    }

    // Instantiate model and register it.
    WalletModel* wallet_model = new WalletModel(std::move(wallet), m_node, m_platform_style, m_options_model, nullptr);
    m_wallets.push_back(wallet_model);

    connect(wallet_model, &WalletModel::unload, [this, wallet_model] {
        // Defer removeAndDeleteWallet when no modal widget is active.
        // TODO: remove this workaround by removing usage of QDiallog::exec.
        if (QApplication::activeModalWidget()) {
            connect(qApp, &QApplication::focusWindowChanged, wallet_model, [this, wallet_model]() {
                if (!QApplication::activeModalWidget()) {
                    removeAndDeleteWallet(wallet_model);
                }
            }, Qt::QueuedConnection);
        } else {
            removeAndDeleteWallet(wallet_model);
        }
    });

    // Re-emit coinsSent signal from wallet model.
    connect(wallet_model, &WalletModel::coinsSent, this, &WalletController::coinsSent);

    // Notify walletAdded signal on the GUI thread.
    if (QThread::currentThread() == thread()) {
        addWallet(wallet_model);
    } else {
        // Handler callback runs in a different thread so fix wallet model thread affinity.
        wallet_model->moveToThread(thread());
        bool invoked = QMetaObject::invokeMethod(this, "addWallet", Qt::QueuedConnection, Q_ARG(WalletModel*, wallet_model));
        assert(invoked);
    }

    return wallet_model;
}

void WalletController::addWallet(WalletModel* wallet_model)
{
    // Take ownership of the wallet model and register it.
    wallet_model->setParent(this);
    Q_EMIT walletAdded(wallet_model);
}

void WalletController::removeAndDeleteWallet(WalletModel* wallet_model)
{
    // Unregister wallet model.
    {
        QMutexLocker locker(&m_mutex);
        m_wallets.erase(std::remove(m_wallets.begin(), m_wallets.end(), wallet_model));
    }
    Q_EMIT walletRemoved(wallet_model);
    // Currently this can trigger the unload since the model can hold the last
    // CWallet shared pointer.
    delete wallet_model;
}


OpenWalletActivity::OpenWalletActivity(WalletController* wallet_controller, const std::string& name)
    : m_wallet_controller(wallet_controller)
    , m_name(name)
{}

void OpenWalletActivity::open()
{
    std::string error, warning;
    std::unique_ptr<interfaces::Wallet> wallet = m_wallet_controller->m_node.loadWallet(m_name, error, warning);
    if (!warning.empty()) {
        Q_EMIT message(QMessageBox::Warning, QString::fromStdString(warning));
    }
    if (wallet) {
        Q_EMIT opened(m_wallet_controller->getOrCreateWallet(std::move(wallet)));
    } else {
        Q_EMIT message(QMessageBox::Critical, QString::fromStdString(error));
    }
    Q_EMIT finished();
}
