text: // Copyright (c) 2011-2016 The Bitcoin Core developers
  // Distributed under the MIT software license, see the accompanying
  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  //
  // File contains modifications by: The Gulden developers
  // All modifications:
  // Copyright (c) 2016-2018 The Gulden developers
  // Authored by: Malcolm MacLeod (mmacleod@webmail.co.za)
  // Distributed under the GULDEN software license, see the accompanying
  // file COPYING
  
  #include "clientmodel.h"
  
  #include "bantablemodel.h"
  #include "guiconstants.h"
  #include "guiutil.h"
  #include "peertablemodel.h"
  
  #include "alert.h"
  #include "chain.h"
  #include "chainparams.h"
  #include "checkpoints.h"
  #include "clientversion.h"
  #include "validation/validation.h"
  #include "net.h"
  #include "txmempool.h"
  #include "ui_interface.h"
  #include "util.h"
  #include "warnings.h"
  
  #include <stdint.h>
  
  #include <QDebug>
  #include <QTimer>
  
  #include "Gulden/util.h"
  
  class CBlockIndex;
  
  static const int64_t nClientStartupTime = GetTime();
  static int64_t nLastHeaderTipUpdateNotification = 0;
  static int64_t nLastBlockTipUpdateNotification = 0;
  
  ClientModel::ClientModel(OptionsModel *_optionsModel, QObject *parent) :
      QObject(parent),
      optionsModel(_optionsModel),
      peerTableModel(0),
      banTableModel(0),
      pollTimer(0)
  {
      cachedBestHeaderHeight = -1;
      cachedBestHeaderTime = -1;
      cachedProbableHeight = Params().Checkpoints().mapCheckpoints.rbegin()->first;
      cachedPoW2Phase = 1;
      peerTableModel = new PeerTableModel(this);
      banTableModel = new BanTableModel(this);
  
      //fixme: (2.1) - Get rid of this timer - core signals should handle this.
      pollTimer = new QTimer(this);
      connect(pollTimer, SIGNAL(timeout()), this, SLOT(updateTimer()));
      pollTimer->start(MODEL_UPDATE_DELAY);
  
      subscribeToCoreSignals();
  }
  
  ClientModel::~ClientModel()
  {
  }
  
  int ClientModel::getNumConnections(unsigned int flags) const
  {
      CConnman::NumConnections connections = CConnman::CONNECTIONS_NONE;
  
      if(flags == CONNECTIONS_IN)
          connections = CConnman::CONNECTIONS_IN;
      else if (flags == CONNECTIONS_OUT)
          connections = CConnman::CONNECTIONS_OUT;
      else if (flags == CONNECTIONS_ALL)
          connections = CConnman::CONNECTIONS_ALL;
  
      if(g_connman)
           return g_connman->GetNodeCount(connections);
      return 0;
  }
  
  int ClientModel::getNumBlocks() const
  {
      LOCK(cs_main);
      return chainActive.Height();
  }
  
  int ClientModel::getHeaderTipHeight() const
  {
      if (cachedBestHeaderHeight == -1) {
          // make sure we initially populate the cache via a cs_main lock
          // otherwise we need to wait for a tip update
          LOCK(cs_main);
          if (pindexBestHeader) {
              cachedBestHeaderHeight = pindexBestHeader->nHeight;
              cachedBestHeaderTime = pindexBestHeader->GetBlockTime();
          }
      }
      return cachedBestHeaderHeight;
  }
  
  int64_t ClientModel::getHeaderTipTime() const
  {
      if (cachedBestHeaderTime == -1) {
          LOCK(cs_main);
          if (pindexBestHeader) {
              cachedBestHeaderHeight = pindexBestHeader->nHeight;
              cachedBestHeaderTime = pindexBestHeader->GetBlockTime();
          }
      }
      return cachedBestHeaderTime;
  }
  
  int ClientModel::getProbableHeight() const
  {
      return cachedProbableHeight;
  }
  
  quint64 ClientModel::getTotalBytesRecv() const
  {
      if(!g_connman)
          return 0;
      return g_connman->GetTotalBytesRecv();
  }
  
  quint64 ClientModel::getTotalBytesSent() const
  {
      if(!g_connman)
          return 0;
      return g_connman->GetTotalBytesSent();
  }
  
  QDateTime ClientModel::getLastBlockDate() const
  {
      LOCK(cs_main);
  
      if (chainActive.Tip())
          return QDateTime::fromTime_t(chainActive.Tip()->GetBlockTime());
  
      return QDateTime::fromTime_t(Params().GenesisBlock().GetBlockTime()); // Genesis block's time of current network
  }
  
  long ClientModel::getMempoolSize() const
  {
      return mempool.size();
  }
  
  size_t ClientModel::getMempoolDynamicUsage() const
  {
      return mempool.DynamicMemoryUsage();
  }
  
  double ClientModel::getVerificationProgress(const CBlockIndex *tipIn) const
  {
      CBlockIndex *tip = const_cast<CBlockIndex *>(tipIn);
      if (!tip)
      {
          LOCK(cs_main);
          tip = chainActive.Tip();
      }
      return GuessVerificationProgress(Params().TxData(), tip);
  }
  
  void ClientModel::updateTimer()
  {
      // no locking required at this point
      // the following calls will acquire the required lock
      Q_EMIT mempoolSizeChanged(getMempoolSize(), getMempoolDynamicUsage());
      Q_EMIT bytesChanged(getTotalBytesRecv(), getTotalBytesSent());
  }
  
  void ClientModel::updateNumConnections(int numConnections)
  {
      Q_EMIT numConnectionsChanged(numConnections);
  }
  
  void ClientModel::updateNetworkActive(bool networkActive)
  {
      Q_EMIT networkActiveChanged(networkActive);
  }
  
  void ClientModel::updateAlert(const QString &hash, int status)
  {
      // Show error message notification for new alert
      if(status == CT_NEW)
      {
          uint256 hash_256;
          hash_256.SetHex(hash.toStdString());
          CAlert alert = CAlert::getAlertByHash(hash_256);
          if(!alert.IsNull())
          {
              Q_EMIT message(tr("Network Alert"), QString::fromStdString(alert.strStatusBar), CClientUIInterface::ICON_ERROR);
          }
      }
      Q_EMIT alertsChanged(getStatusBarWarnings());
  }
  
  void ClientModel::updateUIAlert(const QString& alertMessage)
  {
      Q_EMIT alert(alertMessage);
  }
  
  bool ClientModel::inInitialBlockDownload() const
  {
      return IsInitialBlockDownload();
  }
  
  enum BlockSource ClientModel::getBlockSource() const
  {
      if (fReindex)
          return BLOCK_SOURCE_REINDEX;
      else if (fImporting)
          return BLOCK_SOURCE_DISK;
      else if (getNumConnections() > 0)
          return BLOCK_SOURCE_NETWORK;
  
      return BLOCK_SOURCE_NONE;
  }
  
  void ClientModel::setNetworkActive(bool active)
  {
      if (g_connman) {
           g_connman->SetNetworkActive(active);
      }
  }
  
  bool ClientModel::getNetworkActive() const
  {
      if (g_connman) {
          return g_connman->GetNetworkActive();
      }
      return false;
  }
  
  QString ClientModel::getStatusBarWarnings() const
  {
      return QString::fromStdString(GetWarnings("gui"));
  }
  
  OptionsModel *ClientModel::getOptionsModel()
  {
      return optionsModel;
  }
  
  PeerTableModel *ClientModel::getPeerTableModel()
  {
      return peerTableModel;
  }
  
  BanTableModel *ClientModel::getBanTableModel()
  {
      return banTableModel;
  }
  
  QString ClientModel::formatFullVersion() const
  {
      return QString::fromStdString(FormatFullVersion());
  }
  
  QString ClientModel::formatSubVersion() const
  {
      return QString::fromStdString(strSubVersion);
  }
  
  bool ClientModel::isReleaseVersion() const
  {
      return CLIENT_VERSION_IS_RELEASE;
  }
  
  QString ClientModel::formatClientStartupTime() const
  {
      return QDateTime::fromTime_t(nClientStartupTime).toString();
  }
  
  QString ClientModel::dataDir() const
  {
      return GUIUtil::boostPathToQString(GetDataDir());
  }
  
  void ClientModel::updateBanlist()
  {
      banTableModel->refresh();
  }
  
  // Handlers for core signals
  static void ShowProgress(ClientModel *clientmodel, const std::string &title, int nProgress)
  {
      // emits signal "showProgress"
      QMetaObject::invokeMethod(clientmodel, "showProgress", Qt::QueuedConnection,
                                Q_ARG(QString, QString::fromStdString(title)),
                                Q_ARG(int, nProgress));
  }
  
  static void NotifyNumConnectionsChanged(ClientModel *clientmodel, int newNumConnections)
  {
      // Too noisy: qDebug() << "NotifyNumConnectionsChanged: " + QString::number(newNumConnections);
      QMetaObject::invokeMethod(clientmodel, "updateNumConnections", Qt::QueuedConnection,
                                Q_ARG(int, newNumConnections));
  }
  
  static void NotifyAlertChanged(ClientModel *clientmodel, const uint256 &hash, ChangeType status)
  {
      qDebug() << "NotifyAlertChanged: " + QString::fromStdString(hash.GetHex()) + " status=" + QString::number(status);
      QMetaObject::invokeMethod(clientmodel, "updateAlert", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(hash.GetHex())), Q_ARG(int, status));
  }
  
  static void NotifyUIAlertChanged(ClientModel *clientmodel, const std::string& alertMessage)
  {
      QMetaObject::invokeMethod(clientmodel, "updateUIAlert", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(alertMessage)));
  }
  
  
  static void NotifyNetworkActiveChanged(ClientModel *clientmodel, bool networkActive)
  {
      QMetaObject::invokeMethod(clientmodel, "updateNetworkActive", Qt::QueuedConnection,
                                Q_ARG(bool, networkActive));
  }
  
  static void BannedListChanged(ClientModel *clientmodel)
  {
      qDebug() << QString("%1: Requesting update for peer banlist").arg(__func__);
      QMetaObject::invokeMethod(clientmodel, "updateBanlist", Qt::QueuedConnection);
  }
  
  static void BlockTipChanged(ClientModel *clientmodel, bool initialSync, const CBlockIndex *pIndex, bool fHeader)
  {
      // lock free async UI updates in case we have a new block tip
      // during initial sync, only update the UI if the last update
      // was > 250ms (MODEL_UPDATE_DELAY) ago
      int64_t now = 0;
      if (initialSync)
          now = GetTimeMillis();
  
      int64_t& nLastUpdateNotification = fHeader ? nLastHeaderTipUpdateNotification : nLastBlockTipUpdateNotification;
  
      if (fHeader) {
          // cache best headers time and height to reduce future cs_main locks
          clientmodel->cachedBestHeaderHeight = pIndex->nHeight;
          clientmodel->cachedBestHeaderTime = pIndex->GetBlockTime();
      }
      // if we are in-sync, update the UI regardless of last update time
      if (!initialSync || now - nLastUpdateNotification > MODEL_UPDATE_DELAY) {
          clientmodel->updatePoW2Display();
          //pass a async signal to the UI thread
          QMetaObject::invokeMethod(clientmodel, "numBlocksChanged", Qt::QueuedConnection,
                                    Q_ARG(int, pIndex->nHeight),
                                    Q_ARG(QDateTime, QDateTime::fromTime_t(pIndex->GetBlockTime())),
                                    Q_ARG(double, clientmodel->getVerificationProgress(pIndex)),
                                    Q_ARG(bool, fHeader));
          nLastUpdateNotification = now;
      }
  }
  
  void ClientModel::updatePoW2Display()
  {
      //fixme: (2.1) We can remove this for 2.1
      cachedPoW2Phase = GetPoW2Phase(chainActive.Tip(), Params(), chainActive);
  }
  
  static void HeaderProgressChanged(ClientModel *clientmodel, int currentCount, int probableHeight, int headerTipHeight, int64_t headerTipTime)
  {
      clientmodel->cachedBestHeaderHeight = headerTipHeight;
      clientmodel->cachedBestHeaderTime = headerTipTime;
      clientmodel->cachedProbableHeight = probableHeight;
  
      int64_t now = GetTimeMillis();
      if (now - nLastHeaderTipUpdateNotification > MODEL_UPDATE_DELAY) {
          QMetaObject::invokeMethod(clientmodel, "headerProgressChanged", Qt::QueuedConnection,
                                    Q_ARG(int, currentCount),
                                    Q_ARG(int, probableHeight));
          nLastHeaderTipUpdateNotification = now;
      }
  }
  
  void ClientModel::subscribeToCoreSignals()
  {
      // Connect signals to client
      uiInterface.ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2));
      uiInterface.NotifyNumConnectionsChanged.connect(boost::bind(NotifyNumConnectionsChanged, this, _1));
      uiInterface.NotifyNetworkActiveChanged.connect(boost::bind(NotifyNetworkActiveChanged, this, _1));
      uiInterface.NotifyAlertChanged.connect(boost::bind(NotifyAlertChanged, this, _1, _2));
      uiInterface.NotifyUIAlertChanged.connect(boost::bind(NotifyUIAlertChanged, this, _1));
      uiInterface.BannedListChanged.connect(boost::bind(BannedListChanged, this));
      uiInterface.NotifyBlockTip.connect(boost::bind(BlockTipChanged, this, _1, _2, false));
      uiInterface.NotifyHeaderTip.connect(boost::bind(BlockTipChanged, this, _1, _2, true));
      uiInterface.NotifyHeaderProgress.connect(boost::bind(HeaderProgressChanged, this, _1, _2, _3, _4));
  }
  
  void ClientModel::unsubscribeFromCoreSignals()
  {
      if (pollTimer)
      disconnect(pollTimer, SIGNAL(timeout()), this, SLOT(updateTimer()));
  
      // Disconnect signals from client
      uiInterface.ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2));
      uiInterface.NotifyNumConnectionsChanged.disconnect(boost::bind(NotifyNumConnectionsChanged, this, _1));
      uiInterface.NotifyNetworkActiveChanged.disconnect(boost::bind(NotifyNetworkActiveChanged, this, _1));
      uiInterface.NotifyAlertChanged.disconnect(boost::bind(NotifyAlertChanged, this, _1, _2));
      uiInterface.NotifyUIAlertChanged.disconnect(boost::bind(NotifyUIAlertChanged, this, _1));
      uiInterface.BannedListChanged.disconnect(boost::bind(BannedListChanged, this));
      uiInterface.NotifyBlockTip.disconnect(boost::bind(BlockTipChanged, this, _1, _2, false));
      uiInterface.NotifyHeaderTip.disconnect(boost::bind(BlockTipChanged, this, _1, _2, true));
      uiInterface.NotifyHeaderProgress.disconnect(boost::bind(HeaderProgressChanged, this, _1, _2, _3, _4));
  }
  