text: #include "guiutil.h"
  #include "guiconstants.h"
  #include "bitcoinunits.h"
  #include "optionsmodel.h"
  #include "walletmodel.h"
  #include "messagemodel.h"
  #include "addresstablemodel.h"
  
  #include "ui_interface.h"
  #include "base58.h"
  #include "json_spirit.h"
  
  #include <QSet>
  #include <QTimer>
  #include <QDateTime>
  #include <QSortFilterProxyModel>
  #include <QClipboard>
  #include <QMessageBox>
  #include <QMenu>
  #include <QFont>
  #include <QColor>
  
  Q_DECLARE_METATYPE(std::vector<unsigned char>);
  
  QList<QString> ambiguous; /**< Specifies Ambiguous addresses */
  
  const QString MessageModel::Sent = "Sent";
  const QString MessageModel::Received = "Received";
  
  struct MessageTableEntryLessThan
  {
      bool operator()(const MessageTableEntry &a, const MessageTableEntry &b) const {return a.received_datetime < b.received_datetime;};
      bool operator()(const MessageTableEntry &a, const QDateTime         &b) const {return a.received_datetime < b;}
      bool operator()(const QDateTime         &a, const MessageTableEntry &b) const {return a < b.received_datetime;}
  };
  
  // Private implementation
  class MessageTablePriv
  {
  public:
      QList<MessageTableEntry> cachedMessageTable;
      MessageModel *parent;
  
      MessageTablePriv(MessageModel *parent):
          parent(parent) {}
  
      void refreshMessageTable()
      {
          cachedMessageTable.clear();
          
          if (parent->getWalletModel()->getEncryptionStatus() == WalletModel::Locked)
          {
              // -- messages are stored encrypted, can't load them without the private keys
              return;
          };
  
          {
              LOCK(cs_smsgDB);
  
              SecMsgDB dbSmsg;
  
              if (!dbSmsg.Open("cr+"))
                  //throw runtime_error("Could not open DB.");
                  return;
  
              unsigned char chKey[18];
              std::vector<unsigned char> vchKey;
              vchKey.resize(18);
  
              SecMsgStored smsgStored;
              MessageData msg;
              QString label;
              QDateTime sent_datetime;
              QDateTime received_datetime;
  
              std::string sPrefix("im");
              leveldb::Iterator* it = dbSmsg.pdb->NewIterator(leveldb::ReadOptions());
              while (dbSmsg.NextSmesg(it, sPrefix, chKey, smsgStored))
              {
                  uint32_t nPayload = smsgStored.vchMessage.size() - SMSG_HDR_LEN;
                  if (SecureMsgDecrypt(false, smsgStored.sAddrTo, &smsgStored.vchMessage[0], &smsgStored.vchMessage[SMSG_HDR_LEN], nPayload, msg) == 0)
                  {
                      label = parent->getWalletModel()->getAddressTableModel()->labelForAddress(QString::fromStdString(msg.sFromAddress));
  
                      sent_datetime    .setTime_t(msg.timestamp);
                      received_datetime.setTime_t(smsgStored.timeReceived);
                      
                      memcpy(&vchKey[0], chKey, 18);
  
                      addMessageEntry(MessageTableEntry(vchKey,
                                                        MessageTableEntry::Received,
                                                        label,
                                                        QString::fromStdString(smsgStored.sAddrTo),
                                                        QString::fromStdString(msg.sFromAddress),
                                                        sent_datetime,
                                                        received_datetime,
                                                        (char*)&msg.vchMessage[0]),
                                      true);
                  }
              };
  
              delete it;
  
              sPrefix = "sm";
              it = dbSmsg.pdb->NewIterator(leveldb::ReadOptions());
              while (dbSmsg.NextSmesg(it, sPrefix, chKey, smsgStored))
              {
                  uint32_t nPayload = smsgStored.vchMessage.size() - SMSG_HDR_LEN;
                  if (SecureMsgDecrypt(false, smsgStored.sAddrOutbox, &smsgStored.vchMessage[0], &smsgStored.vchMessage[SMSG_HDR_LEN], nPayload, msg) == 0)
                  {
                      label = parent->getWalletModel()->getAddressTableModel()->labelForAddress(QString::fromStdString(smsgStored.sAddrTo));
  
                      sent_datetime    .setTime_t(msg.timestamp);
                      received_datetime.setTime_t(smsgStored.timeReceived);
                      
                      memcpy(&vchKey[0], chKey, 18);
  
                      addMessageEntry(MessageTableEntry(vchKey,
                                                        MessageTableEntry::Sent,
                                                        label,
                                                        QString::fromStdString(smsgStored.sAddrTo),
                                                        QString::fromStdString(msg.sFromAddress),
                                                        sent_datetime,
                                                        received_datetime,
                                                        (char*)&msg.vchMessage[0]),
                                      true);
                  }
              };
  
              delete it;
          }
      }
  
      void newMessage(const SecMsgStored& inboxHdr)
      {
          // we have to copy it, because it doesn't like constants going into Decrypt
          SecMsgStored smsgStored = inboxHdr;
          MessageData msg;
          QString label;
          QDateTime sent_datetime;
          QDateTime received_datetime;
  
          uint32_t nPayload = smsgStored.vchMessage.size() - SMSG_HDR_LEN;
          if (SecureMsgDecrypt(false, smsgStored.sAddrTo, &smsgStored.vchMessage[0], &smsgStored.vchMessage[SMSG_HDR_LEN], nPayload, msg) == 0)
          {
              label = parent->getWalletModel()->getAddressTableModel()->labelForAddress(QString::fromStdString(msg.sFromAddress));
  
              sent_datetime    .setTime_t(msg.timestamp);
              received_datetime.setTime_t(smsgStored.timeReceived);
  
              std::string sPrefix("im");
              SecureMessage* psmsg = (SecureMessage*) &smsgStored.vchMessage[0];
              
              std::vector<unsigned char> vchKey;
              vchKey.resize(18);
              memcpy(&vchKey[0],  sPrefix.data(),  2);
              memcpy(&vchKey[2],  &psmsg->timestamp, 8);
              memcpy(&vchKey[10], &smsgStored.vchMessage[SMSG_HDR_LEN], 8);    // sample
  
              addMessageEntry(MessageTableEntry(vchKey,
                                                MessageTableEntry::Received,
                                                label,
                                                QString::fromStdString(smsgStored.sAddrTo),
                                                QString::fromStdString(msg.sFromAddress),
                                                sent_datetime,
                                                received_datetime,
                                                (char*)&msg.vchMessage[0]),
                              false);
          }
      }
  
      void newOutboxMessage(const SecMsgStored& outboxHdr)
      {
  
          SecMsgStored smsgStored = outboxHdr;
          MessageData msg;
          QString label;
          QDateTime sent_datetime;
          QDateTime received_datetime;
  
          uint32_t nPayload = smsgStored.vchMessage.size() - SMSG_HDR_LEN;
          if (SecureMsgDecrypt(false, smsgStored.sAddrOutbox, &smsgStored.vchMessage[0], &smsgStored.vchMessage[SMSG_HDR_LEN], nPayload, msg) == 0)
          {
              label = parent->getWalletModel()->getAddressTableModel()->labelForAddress(QString::fromStdString(smsgStored.sAddrTo));
  
              sent_datetime    .setTime_t(msg.timestamp);
              received_datetime.setTime_t(smsgStored.timeReceived);
  
              std::string sPrefix("sm");
              SecureMessage* psmsg = (SecureMessage*) &smsgStored.vchMessage[0];
              std::vector<unsigned char> vchKey;
              vchKey.resize(18);
              memcpy(&vchKey[0],  sPrefix.data(),  2);
              memcpy(&vchKey[2],  &psmsg->timestamp, 8);
              memcpy(&vchKey[10], &smsgStored.vchMessage[SMSG_HDR_LEN], 8);    // sample
  
              addMessageEntry(MessageTableEntry(vchKey,
                                                MessageTableEntry::Sent,
                                                label,
                                                QString::fromStdString(smsgStored.sAddrTo),
                                                QString::fromStdString(msg.sFromAddress),
                                                sent_datetime,
                                                received_datetime,
                                                (char*)&msg.vchMessage[0]),
                              false);
          }
      }
  
      void walletUnlocked()
      {
          // -- wallet is unlocked, can get at the private keys now
          refreshMessageTable();
          
          parent->reset(); // reload table view
          
          if (parent->proxyModel)
          {
              parent->proxyModel->setFilterRole(false);
              parent->proxyModel->setFilterFixedString("");
              parent->resetFilter();
              parent->proxyModel->setFilterRole(MessageModel::Ambiguous);
              parent->proxyModel->setFilterFixedString("true");
          }
          
          //invalidateFilter()
      }
      
      void setEncryptionStatus(int status)
      {
          if (status == WalletModel::Locked)
          {
              // -- Wallet is locked, clear secure message display.
              cachedMessageTable.clear();
  
              parent->reset(); // reload table view
          };
      };
  
      MessageTableEntry *index(int idx)
      {
          if(idx >= 0 && idx < cachedMessageTable.size())
              return &cachedMessageTable[idx];
          else
              return 0;
      }
  
  private:
      // Get the json value
      const json_spirit::mValue & find_value(json_spirit::mObject & obj, const char * key)
      {
          std::string newKey = key;
  
          json_spirit::mObject::const_iterator i = obj.find(newKey);
  
          if(i != obj.end() && i->first == newKey)
              return i->second;
          else
              return json_spirit::mValue::null;
      }
  
      const std::string get_value(json_spirit::mObject & obj, const char * key)
      {
          json_spirit::mValue val = find_value(obj, key);
  
          if(val.is_null())
              return "";
          else
              return val.get_str();
      }
  
      // Determine if it is a special message, i.e.: Invoice, Receipt, etc...
      void handleMessageEntry(const MessageTableEntry & message, const bool append)
      {
          addMessageEntry(message, append);
          json_spirit::mValue mVal;
          json_spirit::read(message.message.toStdString(), mVal);
  
          if(mVal.is_null())
          {
              addMessageEntry(message, append);
              return;
          }
  
          json_spirit::mObject mObj(mVal.get_obj());
          json_spirit::mValue mvType = find_value(mObj, "type");
  
      }
  
      void addMessageEntry(const MessageTableEntry & message, const bool & append)
      {
          if(append)
          {
              cachedMessageTable.append(message);
          } else
          {
              int index = qLowerBound(cachedMessageTable.begin(), cachedMessageTable.end(), message.received_datetime, MessageTableEntryLessThan()) - cachedMessageTable.begin();
              parent->beginInsertRows(QModelIndex(), index, index);
              cachedMessageTable.insert(
                          index,
                          message);
              parent->endInsertRows();
          }
      }
  
  };
  
  MessageModel::MessageModel(CWallet *wallet, WalletModel *walletModel, QObject *parent) :
      QAbstractTableModel(parent), wallet(wallet), walletModel(walletModel), optionsModel(0), priv(0)
  {
      columns << tr("Type") << tr("Sent Date Time") << tr("Received Date Time") << tr("Label") << tr("To Address") << tr("From Address") << tr("Message");
      
      proxyModel = NULL;
      
      optionsModel = walletModel->getOptionsModel();
  
      priv = new MessageTablePriv(this);
      priv->refreshMessageTable();
  
      subscribeToCoreSignals();
  }
  
  MessageModel::~MessageModel()
  {
      if (proxyModel)
          delete proxyModel;
      
      delete priv;
      unsubscribeFromCoreSignals();
  }
  
  bool MessageModel::getAddressOrPubkey(QString &address, QString &pubkey) const
  {
      CpromisecoinAddress addressParsed(address.toStdString());
  
      if(addressParsed.IsValid()) {
          CKeyID  destinationAddress;
          CPubKey destinationKey;
  
          addressParsed.GetKeyID(destinationAddress);
  
          if (SecureMsgGetStoredKey(destinationAddress, destinationKey) != 0
              && SecureMsgGetLocalKey(destinationAddress, destinationKey) != 0) // test if it's a local key
              return false;
  
          address = destinationAddress.ToString().c_str();
          pubkey = EncodeBase58(destinationKey.Raw()).c_str();
  
          return true;
      }
  
      return false;
  }
  
  WalletModel *MessageModel::getWalletModel()
  {
      return walletModel;
  }
  
  OptionsModel *MessageModel::getOptionsModel()
  {
      return optionsModel;
  }
  
  MessageModel::StatusCode MessageModel::sendMessages(const QList<SendMessagesRecipient> &recipients, const QString &addressFrom)
  {
  
      QSet<QString> setAddress;
  
      if(recipients.empty())
          return OK;
  
      // Pre-check input data for validity
      foreach(const SendMessagesRecipient &rcp, recipients)
      {
          if(!walletModel->validateAddress(rcp.address))
              return InvalidAddress;
  
          if(rcp.message == "")
              return MessageCreationFailed;
  
          std::string sendTo  = rcp.address.toStdString();
          std::string pubkey  = rcp.pubkey.toStdString();
          std::string message = rcp.message.toStdString();
          std::string addFrom = addressFrom.toStdString();
  
          SecureMsgAddAddress(sendTo, pubkey);
          setAddress.insert(rcp.address);
          
          std::string sError;
          if (SecureMsgSend(addFrom, sendTo, message, sError) != 0)
          {
              QMessageBox::warning(NULL, tr("Send Secure Message"),
                  tr("Send failed: %1.").arg(sError.c_str()),
                  QMessageBox::Ok, QMessageBox::Ok);
              
              return FailedErrorShown;
          };
  
          // Add addresses / update labels that we've sent to to the address book
          std::string strAddress = rcp.address.toStdString();
          CTxDestination dest = CpromisecoinAddress(strAddress).Get();
          std::string strLabel = rcp.label.toStdString();
          {
              LOCK(wallet->cs_wallet);
  
              std::map<CTxDestination, std::string>::iterator mi = wallet->mapAddressBook.find(dest);
  
              // Check if we have a new address or an updated label
              if (mi == wallet->mapAddressBook.end() || mi->second != strLabel)
              {
                  wallet->SetAddressBookName(dest, strLabel);
              }
          }
      }
  
      if(recipients.size() > setAddress.size())
          return DuplicateAddress;
  
      return OK;
  }
  
  MessageModel::StatusCode MessageModel::sendMessages(const QList<SendMessagesRecipient> &recipients)
  {
      return sendMessages(recipients, "anon");
  }
  
  int MessageModel::rowCount(const QModelIndex &parent) const
  {
      Q_UNUSED(parent);
      return priv->cachedMessageTable.size();
  }
  
  int MessageModel::columnCount(const QModelIndex &parent) const
  {
      Q_UNUSED(parent);
      return columns.length();
  }
  
  QVariant MessageModel::data(const QModelIndex &index, int role) const
  {
      if(!index.isValid())
          return QVariant();
  
      MessageTableEntry *rec = static_cast<MessageTableEntry*>(index.internalPointer());
  
      switch(role)
      {
      /*
      case Qt::DecorationRole:
          switch(index.column())
          {
              return txStatusDecoration(rec);
          case ToAddress:
              return txAddressDecoration(rec);
          }
          break;*/
      case Qt::DisplayRole:
          switch(index.column())
          {
              case Label:	           return (rec->label.isEmpty() ? tr("(no label)") : rec->label);
              case ToAddress:	       return rec->to_address;
              case FromAddress:      return rec->from_address;
              case SentDateTime:     return rec->sent_datetime;
              case ReceivedDateTime: return rec->received_datetime;
              case Message:          return rec->message;
              case TypeInt:          return rec->type;
              case HTML:             return rec->received_datetime.toString() + "<br>"  + (rec->label.isEmpty() ? rec->from_address : rec->label)  + "<br>" + rec->message;
              case Type:
                  switch(rec->type)
                  {
                      case MessageTableEntry::Sent:     return Sent;
                      case MessageTableEntry::Received: return Received;
                      default: break;
                  }
              case Key:               return QVariant::fromValue(rec->chKey);
          }
          break;
  
      case KeyRole:           return QVariant::fromValue(rec->chKey);
      case TypeRole:          return rec->type;
      case SentDateRole:      return rec->sent_datetime;
      case ReceivedDateRole:  return rec->received_datetime;
      case FromAddressRole:   return rec->from_address;
      case ToAddressRole:     return rec->to_address;
      case FilterAddressRole: return (rec->type == MessageTableEntry::Sent ? rec->to_address + rec->from_address : rec->from_address + rec->to_address);
      case LabelRole:         return rec->label;
      case MessageRole:       return rec->message;
      case ShortMessageRole:  return rec->message; // TODO: Short message
      case HTMLRole:          return rec->received_datetime.toString() + "<br>"  + (rec->label.isEmpty() ? rec->from_address : rec->label)  + "<br>" + rec->message;
      case Ambiguous:
          int it;
  
          for (it = 0; it<ambiguous.length(); it++) {
              if(ambiguous[it] == (rec->type == MessageTableEntry::Sent ? rec->to_address + rec->from_address : rec->from_address + rec->to_address))
                  return false;
          }
          QString address = (rec->type == MessageTableEntry::Sent ? rec->to_address + rec->from_address : rec->from_address + rec->to_address);
          ambiguous.append(address);
  
          return "true";
          break;
      }
  
      return QVariant();
  }
  
  QVariant MessageModel::headerData(int section, Qt::Orientation orientation, int role) const
  {
      return (orientation == Qt::Horizontal && role == Qt::DisplayRole ? columns[section] : QVariant());
  }
  
  Qt::ItemFlags MessageModel::flags(const QModelIndex & index) const
  {
      if(index.isValid())
          return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
  
      return 0;
  }
  
  QModelIndex MessageModel::index(int row, int column, const QModelIndex & parent) const
  {
      Q_UNUSED(parent);
      MessageTableEntry *data = priv->index(row);
      return (data ? createIndex(row, column, priv->index(row)) : QModelIndex());
  }
  
  bool MessageModel::removeRows(int row, int count, const QModelIndex & parent)
  {
      MessageTableEntry *rec = priv->index(row);
      if(count != 1 || !rec)
          // Can only remove one row at a time, and cannot remove rows not in model.
          // Also refuse to remove receiving addresses.
          return false;
  
      {
          LOCK(cs_smsgDB);
          SecMsgDB dbSmsg;
  
          if (!dbSmsg.Open("cr+"))
              //throw runtime_error("Could not open DB.");
              return false;
  
          dbSmsg.EraseSmesg(&rec->chKey[0]);
      }
  
      beginRemoveRows(parent, row, row);
      priv->cachedMessageTable.removeAt(row);
      endRemoveRows();
  
      return true;
  }
  
  void MessageModel::resetFilter()
  {
      ambiguous.clear();
  }
  
  void MessageModel::newMessage(const SecMsgStored &smsg)
  {
      priv->newMessage(smsg);
  }
  
  
  void MessageModel::newOutboxMessage(const SecMsgStored &smsgOutbox)
  {
      priv->newOutboxMessage(smsgOutbox);
  }
  
  
  void MessageModel::walletUnlocked()
  {
      priv->walletUnlocked();
  }
  
  void MessageModel::setEncryptionStatus(int status)
  {
      priv->setEncryptionStatus(status);
  }
  
  
  static void NotifySecMsgInbox(MessageModel *messageModel, SecMsgStored inboxHdr)
  {
      // Too noisy: OutputDebugStringF("NotifySecMsgInboxChanged %s\n", message);
      QMetaObject::invokeMethod(messageModel, "newMessage", Qt::QueuedConnection,
                                Q_ARG(SecMsgStored, inboxHdr));
  }
  
  static void NotifySecMsgOutbox(MessageModel *messageModel, SecMsgStored outboxHdr)
  {
      QMetaObject::invokeMethod(messageModel, "newOutboxMessage", Qt::QueuedConnection,
                                Q_ARG(SecMsgStored, outboxHdr));
  }
  
  static void NotifySecMsgWallet(MessageModel *messageModel)
  {
      QMetaObject::invokeMethod(messageModel, "walletUnlocked", Qt::QueuedConnection);
  }
  
  void MessageModel::subscribeToCoreSignals()
  {
      qRegisterMetaType<SecMsgStored>("SecMsgStored");
  
      // Connect signals
      NotifySecMsgInboxChanged.connect(boost::bind(NotifySecMsgInbox, this, _1));
      NotifySecMsgOutboxChanged.connect(boost::bind(NotifySecMsgOutbox, this, _1));
      NotifySecMsgWalletUnlocked.connect(boost::bind(NotifySecMsgWallet, this));
      
      connect(walletModel, SIGNAL(encryptionStatusChanged(int)), this, SLOT(setEncryptionStatus(int)));
  }
  
  void MessageModel::unsubscribeFromCoreSignals()
  {
      // Disconnect signals
      NotifySecMsgInboxChanged.disconnect(boost::bind(NotifySecMsgInbox, this, _1));
      NotifySecMsgOutboxChanged.disconnect(boost::bind(NotifySecMsgOutbox, this, _1));
      NotifySecMsgWalletUnlocked.disconnect(boost::bind(NotifySecMsgWallet, this));
      
      disconnect(walletModel, SIGNAL(encryptionStatusChanged(int)), this, SLOT(setEncryptionStatus(int)));
  }
  