// Copyright (c) 2014-2019, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
//    conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
//    of conditions and the following disclaimer in the documentation and/or other
//    materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
//    used to endorse or promote products derived from this software without specific
//    prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#ifndef WALLET_H
#define WALLET_H

#include <atomic>

#include <QElapsedTimer>
#include <QObject>
#include <QMutex>
#include <QList>
#include <QJSValue>
#include <QtConcurrent/QtConcurrent>

#include "wallet/api/wallet2_api.h" // we need to have an access to the Monero::Wallet::Status enum here;
#include "qt/FutureScheduler.h"
#include "PendingTransaction.h" // we need to have an access to the PendingTransaction::Priority enum here;
#include "UnsignedTransaction.h"
#include "NetworkType.h"
#include "PassphraseHelper.h"
#include "WalletListenerImpl.h"

namespace Monero {
struct Wallet; // forward declaration
}


class TransactionHistory;
class TransactionHistoryModel;
class TransactionHistorySortFilterModel;
class AddressBook;
class AddressBookModel;
class Subaddress;
class SubaddressModel;
class SubaddressAccount;
class SubaddressAccountModel;

class Wallet : public QObject, public PassprasePrompter
{
    Q_OBJECT
    Q_PROPERTY(bool disconnected READ disconnected NOTIFY disconnectedChanged)
    Q_PROPERTY(bool refreshing READ refreshing NOTIFY refreshingChanged)
    Q_PROPERTY(QString seed READ getSeed)
    Q_PROPERTY(QString seedLanguage READ getSeedLanguage)
    Q_PROPERTY(Status status READ status)
    Q_PROPERTY(NetworkType::Type nettype READ nettype)
//    Q_PROPERTY(ConnectionStatus connected READ connected)
    Q_PROPERTY(quint32 currentSubaddressAccount READ currentSubaddressAccount NOTIFY currentSubaddressAccountChanged)
    Q_PROPERTY(bool synchronized READ synchronized)
    Q_PROPERTY(QString errorString READ errorString)
    Q_PROPERTY(TransactionHistory * history READ history)
    Q_PROPERTY(TransactionHistorySortFilterModel * historyModel READ historyModel NOTIFY historyModelChanged)
    Q_PROPERTY(QString path READ path)
    Q_PROPERTY(AddressBookModel * addressBookModel READ addressBookModel)
    Q_PROPERTY(AddressBook * addressBook READ addressBook NOTIFY addressBookChanged)
    Q_PROPERTY(SubaddressModel * subaddressModel READ subaddressModel)
    Q_PROPERTY(Subaddress * subaddress READ subaddress)
    Q_PROPERTY(SubaddressAccountModel * subaddressAccountModel READ subaddressAccountModel)
    Q_PROPERTY(SubaddressAccount * subaddressAccount READ subaddressAccount)
    Q_PROPERTY(bool viewOnly READ viewOnly)
    Q_PROPERTY(QString secretViewKey READ getSecretViewKey)
    Q_PROPERTY(QString publicViewKey READ getPublicViewKey)
    Q_PROPERTY(QString secretSpendKey READ getSecretSpendKey)
    Q_PROPERTY(QString publicSpendKey READ getPublicSpendKey)
    Q_PROPERTY(QString daemonLogPath READ getDaemonLogPath CONSTANT)
    Q_PROPERTY(QString proxyAddress READ getProxyAddress WRITE setProxyAddress NOTIFY proxyAddressChanged)
    Q_PROPERTY(quint64 walletCreationHeight READ getWalletCreationHeight WRITE setWalletCreationHeight NOTIFY walletCreationHeightChanged)

public:


    enum Status {
        Status_Ok       = Monero::Wallet::Status_Ok,
        Status_Error    = Monero::Wallet::Status_Error,
        Status_Critical = Monero::Wallet::Status_Critical
    };

    Q_ENUM(Status)

    enum ConnectionStatus {
        ConnectionStatus_Connected       = Monero::Wallet::ConnectionStatus_Connected,
        ConnectionStatus_Disconnected    = Monero::Wallet::ConnectionStatus_Disconnected,
        ConnectionStatus_WrongVersion    = Monero::Wallet::ConnectionStatus_WrongVersion,
        ConnectionStatus_Connecting
    };

    Q_ENUM(ConnectionStatus)

    //! returns mnemonic seed
    QString getSeed() const;

    //! returns seed language
    QString getSeedLanguage() const;

    //! set seed language
    Q_INVOKABLE void setSeedLanguage(const QString &lang);

    //! returns last operation's status
    Status status() const;

    //! returns network type of the wallet.
    NetworkType::Type nettype() const;

    //! returns whether the wallet is connected, and version status
    Q_INVOKABLE ConnectionStatus connected(bool forceCheck = false);
    void updateConnectionStatusAsync();

    //! returns true if wallet was ever synchronized
    bool synchronized() const;


    //! returns last operation's error message
    QString errorString() const;

    //! changes the password using existing parameters (path, seed, seed lang)
    Q_INVOKABLE bool setPassword(const QString &password);

    //! returns wallet's public address
    Q_INVOKABLE QString address(quint32 accountIndex, quint32 addressIndex) const;

    //! returns wallet file's path
    QString path() const;

    //! saves wallet to the file by given path
    //! empty path stores in current location
    Q_INVOKABLE void storeAsync(const QJSValue &callback, const QString &path = "");

    //! initializes wallet asynchronously
    Q_INVOKABLE void initAsync(
        const QString &daemonAddress,
        bool trustedDaemon = false,
        quint64 upperTransactionLimit = 0,
        bool isRecovering = false,
        bool isRecoveringFromDevice = false,
        quint64 restoreHeight = 0,
        const QString &proxyAddress = "");

    // Set daemon rpc user/pass
    Q_INVOKABLE void setDaemonLogin(const QString &daemonUsername = "", const QString &daemonPassword = "");

    //! create a view only wallet
    Q_INVOKABLE bool createViewOnly(const QString &path, const QString &password) const;

    //! connects to daemon
    Q_INVOKABLE bool connectToDaemon();

    //! indicates id daemon is trusted
    Q_INVOKABLE void setTrustedDaemon(bool arg);

    //! returns balance
    Q_INVOKABLE quint64 balance() const;
    Q_INVOKABLE quint64 balance(quint32 accountIndex) const;
    Q_INVOKABLE quint64 balanceAll() const;

    //! returns unlocked balance
    Q_INVOKABLE quint64 unlockedBalance() const;
    Q_INVOKABLE quint64 unlockedBalance(quint32 accountIndex) const;
    Q_INVOKABLE quint64 unlockedBalanceAll() const;

    //! account/address management
    quint32 currentSubaddressAccount() const;
    Q_INVOKABLE void switchSubaddressAccount(quint32 accountIndex);
    Q_INVOKABLE void addSubaddressAccount(const QString& label);
    Q_INVOKABLE quint32 numSubaddressAccounts() const;
    Q_INVOKABLE quint32 numSubaddresses(quint32 accountIndex) const;
    Q_INVOKABLE void addSubaddress(const QString& label);
    Q_INVOKABLE QString getSubaddressLabel(quint32 accountIndex, quint32 addressIndex) const;
    Q_INVOKABLE void setSubaddressLabel(quint32 accountIndex, quint32 addressIndex, const QString &label);
    Q_INVOKABLE void deviceShowAddressAsync(quint32 accountIndex, quint32 addressIndex, const QString &paymentId);

    //! hw-device backed wallets
    Q_INVOKABLE bool isHwBacked() const;
    Q_INVOKABLE bool isLedger() const;
    Q_INVOKABLE bool isTrezor() const;

    //! returns if view only wallet
    Q_INVOKABLE bool viewOnly() const;

    Q_INVOKABLE void refreshHeightAsync();

    //! export/import key images
    Q_INVOKABLE bool exportKeyImages(const QString& path, bool all = false);
    Q_INVOKABLE bool importKeyImages(const QString& path);

    //! export/import outputs
    Q_INVOKABLE bool exportOutputs(const QString& path, bool all = false);
    Q_INVOKABLE bool importOutputs(const QString& path);

    //! refreshes the wallet
    Q_INVOKABLE bool refresh(bool historyAndSubaddresses = true);

    // pause/resume refresh
    Q_INVOKABLE void startRefresh();
    Q_INVOKABLE void pauseRefresh();

    //! creates async transaction
    Q_INVOKABLE void createTransactionAsync(
        const QVector<QString> &destinationAddresses,
        const QString &payment_id,
        const QVector<QString> &destinationAmounts,
        quint32 mixin_count,
        PendingTransaction::Priority priority);

    //! creates transaction with all outputs
    Q_INVOKABLE PendingTransaction * createTransactionAll(const QString &dst_addr, const QString &payment_id,
                                                       quint32 mixin_count, PendingTransaction::Priority priority);

    //! creates async transaction with all outputs
    Q_INVOKABLE void createTransactionAllAsync(const QString &dst_addr, const QString &payment_id,
                                               quint32 mixin_count, PendingTransaction::Priority priority);

    //! creates sweep unmixable transaction
    Q_INVOKABLE PendingTransaction * createSweepUnmixableTransaction();

    //! creates async sweep unmixable transaction
    Q_INVOKABLE void createSweepUnmixableTransactionAsync();

    //! Sign a transfer from file
    Q_INVOKABLE UnsignedTransaction * loadTxFile(const QString &fileName);

    //! Submit a transfer from file
    Q_INVOKABLE bool submitTxFile(const QString &fileName) const;

    //! asynchronous transaction commit
    Q_INVOKABLE void commitTransactionAsync(PendingTransaction * t);

    //! deletes transaction and frees memory
    Q_INVOKABLE void disposeTransaction(PendingTransaction * t);

    //! deletes unsigned transaction and frees memory
    Q_INVOKABLE void disposeTransaction(UnsignedTransaction * t);

    Q_INVOKABLE void estimateTransactionFeeAsync(
        const QVector<QString> &destinationAddresses,
        const QVector<quint64> &amounts,
        PendingTransaction::Priority priority,
        const QJSValue &callback);

    //! returns transaction history
    TransactionHistory * history() const;

    //! returns transaction history model
    TransactionHistorySortFilterModel *historyModel() const;

    //! returns Address book
    AddressBook *addressBook() const;

    //! returns adress book model
    AddressBookModel *addressBookModel() const;

    //! returns subaddress
    Subaddress *subaddress();

    //! returns subadress model
    SubaddressModel *subaddressModel();

    //! returns subaddress account
    SubaddressAccount *subaddressAccount() const;

    //! returns subadress account model
    SubaddressAccountModel *subaddressAccountModel() const;

    //! generate payment id
    Q_INVOKABLE QString generatePaymentId() const;

    //! integrated address
    Q_INVOKABLE QString integratedAddress(const QString &paymentId) const;

    //! signing a message
    Q_INVOKABLE QString signMessage(const QString &message, bool filename = false) const;

    //! verify a signed message
    Q_INVOKABLE bool verifySignedMessage(const QString &message, const QString &address, const QString &signature, bool filename = false) const;

    //! Parse URI
    Q_INVOKABLE bool parse_uri(const QString &uri, QString &address, QString &payment_id, uint64_t &amount, QString &tx_description, QString &recipient_name, QVector<QString> &unknown_parameters, QString &error);
    //! Make URI
    Q_INVOKABLE QString make_uri(const QString &address, const quint64 &amount = 0, const QString &tx_description = "", const QString &recipient_name = "") const;

    //! Namespace your cacheAttribute keys to avoid collisions
    Q_INVOKABLE bool setCacheAttribute(const QString &key, const QString &val);
    Q_INVOKABLE QString getCacheAttribute(const QString &key) const;

    Q_INVOKABLE bool setUserNote(const QString &txid, const QString &note);
    Q_INVOKABLE QString getUserNote(const QString &txid) const;
    Q_INVOKABLE QString getTxKey(const QString &txid) const;
    Q_INVOKABLE void getTxKeyAsync(const QString &txid, const QJSValue &callback);
    Q_INVOKABLE QString checkTxKey(const QString &txid, const QString &tx_key, const QString &address);
    Q_INVOKABLE QString getTxProof(const QString &txid, const QString &address, const QString &message) const;
    Q_INVOKABLE void getTxProofAsync(const QString &txid, const QString &address, const QString &message, const QJSValue &callback);
    Q_INVOKABLE QString checkTxProof(const QString &txid, const QString &address, const QString &message, const QString &signature);
    Q_INVOKABLE QString getSpendProof(const QString &txid, const QString &message) const;
    Q_INVOKABLE void getSpendProofAsync(const QString &txid, const QString &message, const QJSValue &callback);
    Q_INVOKABLE QString checkSpendProof(const QString &txid, const QString &message, const QString &signature) const;
    Q_INVOKABLE QString getReserveProof(bool all, quint32 account_index, quint64 amount, const QString &message) const;
    Q_INVOKABLE QString checkReserveProof(const QString &address, const QString &message, const QString &signature) const;
    // Rescan spent outputs
    Q_INVOKABLE bool rescanSpent();

    // check if fork rules should be used
    Q_INVOKABLE bool useForkRules(quint8 version, quint64 earlyBlocks = 0) const;

    //! Get wallet keys
    QString getSecretViewKey() const {return QString::fromStdString(m_walletImpl->secretViewKey());}
    QString getPublicViewKey() const {return QString::fromStdString(m_walletImpl->publicViewKey());}
    QString getSecretSpendKey() const {return QString::fromStdString(m_walletImpl->secretSpendKey());}
    QString getPublicSpendKey() const {return QString::fromStdString(m_walletImpl->publicSpendKey());}

    quint64 getWalletCreationHeight() const {return m_walletImpl->getRefreshFromBlockHeight();}
    void setWalletCreationHeight(quint64 height);

    QString getDaemonLogPath() const;
    QString getWalletLogPath() const;

    // Blackalled outputs
    Q_INVOKABLE bool blackballOutput(const QString &amount, const QString &offset);
    Q_INVOKABLE bool blackballOutputs(const QList<QString> &outputs, bool add);
    Q_INVOKABLE bool blackballOutputs(const QString &filename, bool add);
    Q_INVOKABLE bool unblackballOutput(const QString &amount, const QString &offset);

    // Rings
    Q_INVOKABLE QString getRing(const QString &key_image);
    Q_INVOKABLE QString getRings(const QString &txid);
    Q_INVOKABLE bool setRing(const QString &key_image, const QString &ring, bool relative);

    // key reuse mitigation options
    Q_INVOKABLE void segregatePreForkOutputs(bool segregate);
    Q_INVOKABLE void segregationHeight(quint64 height);
    Q_INVOKABLE void keyReuseMitigation2(bool mitigation);

    // Passphrase entry for hardware wallets
    Q_INVOKABLE void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort=false);
    virtual void onWalletPassphraseNeeded(bool on_device) override;

    // TODO: setListenter() when it implemented in API
signals:
    // emitted on every event happened with wallet
    // (money sent/received, new block)
    void updated();

    // emitted when refresh process finished (could take a long time)
    // signalling only after we
    void refreshed();

    void moneySpent(const QString &txId, quint64 amount);
    void moneyReceived(const QString &txId, quint64 amount);
    void unconfirmedMoneyReceived(const QString &txId, quint64 amount);
    void newBlock(quint64 height, quint64 targetHeight);
    void addressBookChanged() const;
    void historyModelChanged() const;
    void walletCreationHeightChanged();
    void deviceButtonRequest(quint64 buttonCode);
    void deviceButtonPressed();
    void walletPassphraseNeeded(bool onDevice);
    void transactionCommitted(bool status, PendingTransaction *t, const QStringList& txid);
    void heightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight) const;
    void deviceShowAddressShowed();

    // emitted when transaction is created async
    void transactionCreated(
        PendingTransaction *transaction,
        const QVector<QString> &addresses,
        const QString &paymentId,
        quint32 mixinCount);

    void connectionStatusChanged(int status) const;
    void currentSubaddressAccountChanged() const;
    void disconnectedChanged() const;
    void proxyAddressChanged() const;
    void refreshingChanged() const;

private:
    Wallet(QObject * parent = nullptr);
    Wallet(Monero::Wallet *w, QObject * parent = 0);
    ~Wallet();

    //! returns current wallet's block height
    //! (can be less than daemon's blockchain height when wallet sync in progress)
    quint64 blockChainHeight() const;

    //! returns daemon's blockchain height
    quint64 daemonBlockChainHeight() const;

    //! returns daemon's blockchain target height
    quint64 daemonBlockChainTargetHeight() const;

    //! initializes wallet
    bool init(
        const QString &daemonAddress,
        bool trustedDaemon,
        quint64 upperTransactionLimit,
        bool isRecovering,
        bool isRecoveringFromDevice,
        quint64 restoreHeight,
        const QString& proxyAddress);

    PendingTransaction *createTransaction(
        const QVector<QString> &destinationAddresses,
        const QString &payment_id,
        const QVector<QString> &destinationAmounts,
        quint32 mixin_count,
        PendingTransaction::Priority priority);

    bool disconnected() const;
    bool refreshing() const;
    void refreshingSet(bool value);
    void setConnectionStatus(ConnectionStatus value);
    QString getProxyAddress() const;
    void setProxyAddress(QString address);
    void startRefreshThread();

private:
    friend class WalletManager;
    friend class WalletListenerImpl;
    //! libwallet's
    Monero::Wallet * m_walletImpl;
    // history lifetime managed by wallet;
    TransactionHistory * m_history;
    // Used for UI history view
    mutable TransactionHistoryModel * m_historyModel;
    mutable TransactionHistorySortFilterModel * m_historySortFilterModel;
    AddressBook * m_addressBook;
    mutable AddressBookModel * m_addressBookModel;
    mutable QElapsedTimer m_daemonBlockChainHeightTime;
    mutable quint64 m_daemonBlockChainHeight;
    int     m_daemonBlockChainHeightTtl;
    mutable QElapsedTimer m_daemonBlockChainTargetHeightTime;
    mutable quint64 m_daemonBlockChainTargetHeight;
    int     m_daemonBlockChainTargetHeightTtl;
    mutable ConnectionStatus m_connectionStatus;
    int     m_connectionStatusTtl;
    mutable QElapsedTimer m_connectionStatusTime;
    bool m_disconnected;
    std::atomic<bool> m_initialized;
    uint32_t m_currentSubaddressAccount;
    Subaddress * m_subaddress;
    mutable SubaddressModel * m_subaddressModel;
    SubaddressAccount * m_subaddressAccount;
    mutable SubaddressAccountModel * m_subaddressAccountModel;
    QMutex m_asyncMutex;
    QMutex m_connectionStatusMutex;
    bool m_connectionStatusRunning;
    QString m_daemonUsername;
    QString m_daemonPassword;
    QString m_proxyAddress;
    mutable QMutex m_proxyMutex;
    std::atomic<bool> m_refreshEnabled;
    std::atomic<bool> m_refreshing;
    WalletListenerImpl *m_walletListener;
    FutureScheduler m_scheduler;
};



#endif // WALLET_H
