/* Copyright (c) 2020-2022 The Creators of Simphone

   See the file COPYING.LESSER.txt for copying permission.
*/

#ifndef DATA_H
#define DATA_H

#include "qmodels.h"

#ifdef _DEBUG
#define SIM_UI_NAME "simDBG"
#else
#define SIM_UI_NAME "simphone"
#endif

class DataProvider : public QObject
{
public:
  //virtual ~DataProvider() {}

  DataProvider()
    : m_changeLockCount(0), m_hasChange(false) {}

  void attachModel(BaseModel * model);
  void detachModel(BaseModel * model);

  void beginChanges() { ++m_changeLockCount; }
  void doneChanges() { --m_changeLockCount, notifyChanges(false); }

  void notifyChanges(bool force);

protected:
  void notifyInserted(int start, int end);
  void notifyDeleted(int start, int end);

  int m_changeLockCount;
  bool m_hasChange;
  std::vector<BaseModel *> m_models;
};

// Logs are handled like messages
class Logs : public DataProvider
{
public:
  unsigned count();

  QString getLogLevel(int ndx) const;
  char checkLogDate(int ndx) const;
  QString getLogLine(int ndx) const;
  QString getLogTime(int ndx) const;
  static const char * getLogDate(int ndx);
  static QString getLogText(int ndx, bool textOnly);

  static Logs * getLogs()
  {
    if (!mc_logs) mc_logs = new Logs();
    return mc_logs;
  }

  static void newLog();

  char isNewDate() const { return m_diffDate; }

  void reset() { m_size = 0, m_lastIndex = 0; }

protected:
  Logs();
  ~Logs() Q_DECL_OVERRIDE { mc_logs = 0; }

  unsigned m_size;
  mutable int m_lastIndex;
  mutable char m_diffDate;
  mutable bool m_diffTime;
  mutable bool m_showTime;
  mutable bool m_showLevel;

  static Logs * mc_logs;
};

class ChatFrame;
class ChatWindow;

class Contact
{
  Q_DECLARE_TR_FUNCTIONS(Contact)

public:
  enum E_State {
    state_none,      // offline
    state_unknown,   // gone
    state_logged,    // online
    state_busy,      // do not disturb
    state_hide,      // not visible
    state_away,      // idle or away
    state_new,       // contact is not accepted
    state_blocked,   // contact is blocked temporarily (suppressed)
    state_deleted,   // contact is deleted (permanently blocked)
    state_invisible, // this must be the last state
    state_Nstates
  };

  enum E_CallState {
    call_none,
    call_incoming,
    call_talking,
    call_outgoing
  };

  enum E_FlagsState {
    flag_testContact = 0x80000000,
    flag_myself = 0x40000000,
    flag_system = 0x20000000,
    flag_notify = 0x10000000,
    flag_hasUnreadMsg = 0x08000000,
    flag_hasMissedCall = 0x04000000,
    flag_hasIncomingFile = 0x02000000,
    flag_hasNewContact = 0x01000000,
    flag_newContact = 0x00800000,
    flag_blocked = 0x00400000,
    flag_deleted = 0x00200000,
    flag_forgotten = 0x00100000,
    flag_verified = 0x00080000,
    flag_verify = 0x00040000,
    flag_typing = 0x00020000,
    flag_traversed = 0x00010000,
    flag_loadError = 0x00008000,
    flag_Notifications = flag_hasUnreadMsg | flag_hasMissedCall | flag_hasIncomingFile | flag_hasNewContact,

    shift_cState = 4, //F0
    mask_cState = 0x0000000F << shift_cState,
    shift_state = 0, //0F
    mask_state = 0x0000000F << shift_state
  };

  Contact(simnumber simId, int id);
  ~Contact();

  void clearNotifications(E_FlagsState flags);
  void setNotifications(E_FlagsState flags);

  void setState(E_State state) { m_state = (m_state & ~mask_state) | state << shift_state; }
  unsigned getState() const { return (m_state & Contact::mask_state) >> Contact::shift_state; }

  void setTestContact(bool test);
  bool isTest() const { return m_state & flag_testContact; }

  void setNewContact(bool newContact);
  bool isNewContact() const { return (m_state & flag_newContact) != 0; }

  void setMyself(bool myself);
  bool isMe() const { return (m_state & flag_myself) != 0; }

  void setSystem(bool system);
  bool isSystem() const { return (m_state & flag_system) != 0; }

  void setForgotten(bool forgotten);
  bool isForgotten() const { return (m_state & flag_forgotten) != 0; }

  void setDeleted(bool deleted);
  bool isDeleted() const { return (m_state & flag_deleted) != 0; }

  void setBlocked(bool blocked);
  bool isBlocked() const { return (m_state & flag_blocked) != 0; }

  void setCallState(E_CallState cState) { m_state = (m_state & ~mask_cState) | cState << shift_cState; }
  bool isCallState(E_CallState cState) const { return (m_state & mask_cState) >> shift_cState == unsigned(cState); }

  void setCryptoError(int err, bool hidden) { (hidden ? m_cryptoHidden : m_cryptoShown).insert(err); }
  void clearCryptoError(int err, bool hidden) { (hidden ? m_cryptoHidden : m_cryptoShown).remove(err); }
  void clearCryptoErrors(bool hidden) { (hidden ? m_cryptoHidden : m_cryptoShown).clear(); }

  bool isCryptoError(int err, bool hidden) const { return (hidden ? m_cryptoHidden : m_cryptoShown).contains(err); }
  QSet<int> getCryptoErrors(bool hidden) const { return hidden ? m_cryptoHidden : m_cryptoShown; }

  void setVerified() { m_state |= flag_verified; }
  bool isVerified() const { return (m_state & flag_verified) != 0; }

  void setVerify(bool verify);
  bool isVerify() const { return (m_state & flag_verify) != 0; }

  void setTypingFlag(bool typing);
  bool isTyping() const { return (m_state & flag_typing) != 0; }

  void setTraversed(bool traversed);
  bool hasTraversed() const { return (m_state & flag_traversed) != 0; }

  void setLoadError() { m_state |= flag_loadError; }
  bool hasLoadError() const { return (m_state & flag_loadError) != 0; }

  QString getNickToolTip(bool disambiguation) const;
  QString getStateToolTip(unsigned state) const;

  QString getAuthText() const;

  unsigned getContactState() const;
  void setStatus(short status) { setState(convertStatusToState(m_status = status)); }

  void setDialog(int dialog, QDialog * window, int posX, int posY)
  {
    m_dialog[dialog] = window, m_posX[dialog] = posX, m_posY[dialog] = posY;
  }

  void setDialog(int dialog, QDialog * window) { m_dialog[dialog] = window; }
  QDialog * getDialog(int dialog) const { return m_dialog[dialog]; }
  QDialog * getDialog(int dialog, int * posX, int * posY) const;

  QString getTextId() const { return (QString("0").repeated(20) + QString::number(qulonglong(m_simId))).right(20); }
  QString getDefaultNick() const { return "@" + getTextId(); }

  static QString findCountry(unsigned ip);
  QString convertLocationToCountry(const char * ip, int location);
  static bool convertAddress(const QString & ip, unsigned * result);

  E_State convertStatusToState(short status, QString * name = 0) const;

  static QString convertTimeToString(simnumber time);
  static QString convertTimeToText(simnumber time, int units = 0);

  int m_id;          // internal contact id
  short m_status;    // current status (SIM_STATUS_xxx)
  simnumber m_simId; // contact id (as given by simcore API)

  QString m_nick; // nick name to display
  QString m_info; // info line to display
  int m_editMax;  // number of last messages that can be edited

  unsigned m_state;         // low order nibble is enum E_State, higher order nibble is E_CallState
  simnumber m_rights;       // contact flags
  QString m_badSystemName;  // insecure system name
  QString m_badCryptoName;  // undesired cipher name
  QSet<int> m_cryptoShown;  // currently shown crypto errors
  QSet<int> m_cryptoHidden; // currently hidden crypto errors

  simunsigned m_soundTimes[5]; // time of last received messages
  int m_newMessages;           // number of received messages

  ChatFrame * m_chatFrame;
  ChatWindow * m_chatWindow; // a chat window used to show chat in split window mode

  QImage * m_avatar;

protected:
  QDialog * m_dialog[3];
  int m_posX[3];
  int m_posY[3];

  QString m_ip[2];
  QString m_country[2];
};

#endif
