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

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

#ifndef SIMCORE_H
#define SIMCORE_H

#include "logindialog.h"

#include "../simcore/simapi.h"

#include <QThread>
#include <QTimer>
#include <QMessageBox>

class Contact;

#define EXIT_HELP_REPORTED 0
#define EXIT_CANNOT_INIT 1
#define EXIT_NO_DIRECTORY 2
#define EXIT_TEMP_DIRECTORY 3
#define EXIT_NO_AUTOSTART 4
#define EXIT_NO_MEMORY 5

#define EXIT_NO_ERROR 240
#define EXIT_FAILED_1 241
#define EXIT_FAILED_2 241
#define EXIT_FAILED_3 241
#define EXIT_FAILED_4 241
#define EXIT_FAILED_PASSWORD 245
#define EXIT_FAILED_THREAD 246
#define EXIT_CRASH_FAILED 247
#define EXIT_EXEC_FAILED 248
#define EXIT_EXEC_SUCCEEDED 249

#define EXIT_CRASH_CRASHED 249
#define EXIT_NO_SESSION 252
#define EXIT_CANNOT_START 253
#define EXIT_CANNOT_KILL 254
#define EXIT_CRASH_REPORTED 255

extern QByteArray g_exeName;
extern int g_terminate_signal;

#define SINGLE_MESSAGE_SHOW "SHOW"
#define SINGLE_MESSAGE_HIDE "HIDE"
#define SINGLE_MESSAGE_QUIT "QUIT"
#define SINGLE_MESSAGE_EXEC "EXEC"

enum SimAudioState {
  audio_none,
  audio_incoming,
  audio_connect,
  audio_outgoing,
  audio_ringing,
  audio_hangup,
  audio_talk,
  audio_udp,
  audio_busy,
  audio_abort,
  audio_failed,
  audio_disconnect,
  audio_missed
};

class SimParam
{
public:
  static int set(const char * param, int val, bool temp)
  {
    return sim_param_set_(param, sim_number_new(val), temp ? SIM_PARAM_TEMPORARY : SIM_PARAM_PERMANENT);
  }

  static int set(const char * param, const char * val, bool temp)
  {
    return sim_param_set_(param, sim_pointer_new(val), temp ? SIM_PARAM_TEMPORARY : SIM_PARAM_PERMANENT);
  }

  static int set(const simtype val, bool temp)
  {
    return sim_param_set_(0, val, temp ? SIM_PARAM_TEMPORARY : SIM_PARAM_PERMANENT);
  }

  static int set(const char * param, int val)
  {
    return sim_param_set_(param, sim_number_new(val), SIM_PARAM_MEMORY);
  }

  static int set(const char * param, const char * val)
  {
    return sim_param_set_(param, sim_pointer_new(val), SIM_PARAM_MEMORY);
  }

  static int set(const simtype val) { return sim_param_set_(0, val, SIM_PARAM_USER); }

  static int unset(const char * param, bool temp)
  {
    return sim_param_set_(param, sim_nil(), temp ? SIM_PARAM_MEMORY : SIM_PARAM_USER);
  }

  static QColor getColor(const char * param) { return QColor(getString(param).data()); }
  static QColor getColor(const char * param, const QColor & defaultColor);
  static const QByteArray getColorString(bool bold) { return getString(bold ? "ui.color.errors" : "ui.color.error"); }
  static const QString getIcon(const QString & name);

  static QByteArray getString(const char * param);
  static bool getNumber(const char * param, int * value);
  static int get(const char * param);

  static int getDefault(const char * param, int defaultValue, int * max = 0, int * min = 0);
  static int getMinValue(const char * param, int min = 0) { return getDefault(param, 0, 0, &min), min; }
  static int getMaxValue(const char * param, int max = 0) { return getDefault(param, 0, &max, 0), max; }
};

class SimThread : public QThread
{
  Q_OBJECT
public:
  enum E_ThreadType {
    thread_login,
    thread_logout,
    thread_quit
  };

  SimThread(E_ThreadType action, const QString & argStr)
    : m_action(action), m_argStr(argStr), m_simres(SIM_OK) {}

  void run() Q_DECL_OVERRIDE;

  E_ThreadType m_action;
  QString m_argStr;
  int m_simres;
};

class SimCore : public QObject // singleton
{
  Q_OBJECT

  typedef QObject Parent;

public:
  enum E_ConnectionState {
    state_disconnect,    // not connected
    state_connecting,    // connected to the DHT
    state_connected,     // sending to the DHT
    state_listening,     // receiving from the DHT
    state_connected_in,  // connected directly
    state_connected_out, // connected directly
    state_traversed_in,  // connected directly (traversed)
    state_traversed_out, // connected directly (traversed)
    state_reversed_in,   // connected directly (reversed from contact)
    state_reversed_out,  // connected directly (reversed to contact)
    state_relayed_in,    // connected indirectly (own proxy)
    state_relayed_out,   // connected indirectly (other proxy)
    state_relaying_in,   // waiting for traversal or reversal (own proxy)
    state_relaying_out,  // waiting for traversal or reversal (other proxy)
    state_connected_udp, // UDP audio call in progress
    state_traversed_udp, // UDP audio call in progress (traversed)
    state_NConnectionStates
  };

  SimCore();
  ~SimCore() Q_DECL_OVERRIDE;

  static int create();
  void destroy();

  static QString getError(int simres);
  static QString getErrorBuffer(int simres, char * buffer = 0, bool peek = false); // do peek if no buffer
  static QString getErrorString(int simres, const char * error, char * text);

  static Contact * getContact(int id)
  {
    if (unsigned(id) >= mc_simcore->m_contacts.size()) return 0;
    return mc_simcore->m_contacts[id];
  }

  Contact * getContact() const { return getContact(m_myId); }
  Contact * getContactId(simnumber simId) { return m_contacts[getContactId(simId, false)]; }

  int getContactId(simnumber simId, bool doNotCreate);
  int getTestContactId();

  static SimCore * get() { return mc_simcore; }
  SimThread * getThread() const { return m_simThread; }

  void getUserContact(simnumber simId, int myStatus);
  void getUserContacts();

  static E_ConnectionState getConnectionState(int id);

  QString getMyNick() const;
  void getMyContact();

  static int getMyStatus(unsigned * flags = 0);
  static int setMyStatus(int status);

  static QWidget * getParent(QWidget * parent)
  {
    return !mc_simcore ? 0 : mc_simcore->m_login ? mc_simcore->m_login : mc_simcore->m_logged ? parent : 0;
  }

  void setLoginDialog(LoginDialog * login);

  bool isPinging() const { return m_latencyTimer.isActive(); }
  static bool isCalling() { return mc_calling; }

  static char isHidden() { return mc_hidden; }
  static bool isHiding() { return mc_hidden == 'h' && SimParam::get("ui.main.hideshow"); }
  static void unhide();
  static void hide();

  void customEvent(QEvent * event) Q_DECL_OVERRIDE;

  void emitContactChanged(int id) { emit signalContactChanged(id); }

  void emitTransferStateChanged(int id, simnumber xferId, const char * type)
  {
    emit signalTransferState(id, xferId, type);
  }

  void emitTransferChanged(int id, simnumber xferId, const QString & oldName, const QString & newName)
  {
    emit signalTransfer(id, xferId, oldName, newName);
  }

  static bool execMessageBox(QMessageBox::Icon icon, const char * title, const QString & message,
                             const QString & checkBox);
  static bool execMessageBox(QCheckBox * checkBox, const QString & title, const QString & message, bool yes);
  static bool execMessageBox(bool critical, const QString & title, const QString & message,
                             const QString & checkBox = QString());

  static bool checkProcess(quint64 pid, bool terminate); // no wait
  static bool killProcess(quint64 pid, int timeout);
  static int execProcess();

  void registerHotKey(int id, int hotKey);
  void unregisterHotKey(int id);

  void readSettings();
  static void readStyleSheet(char login);

  bool loginUser(bool createKey);
  bool loginStart(const char * password, const QString & keygenPassword);

  void loginFinish();
  static void showContacts();

  static int showLoginError(int simres);
  bool loginError(int simres, const char * password, bool prompt);

  bool isLogged() const { return m_logged; }
  void logoutStart(bool quit);

  int startThread(const QString & argStr, SimThread::E_ThreadType action);

  static QString mc_startupUser;
  static QString mc_startupPass;
  static QString mc_keygenPass;
  static QString mc_lastPass;
  static bool mc_rpc;
  static bool mc_popup;
  static bool mc_autolog;
  static bool mc_startupMinimized;
  static bool mc_startupOffline;
  static bool mc_startupSocks;
  static bool mc_hasAudioChanged;
  static int mc_hasSocksError;
  static int mc_hasProtocolError;
  static int mc_hasThreadError;
  static int mc_hasXferError;
  static simnumber mc_hasXferErrorId;

  std::vector<Contact *> m_contacts;

signals:
  void signalContactAdded(int);
  void signalContactStatusChanged(unsigned, int, int);
  void signalContactAudioChanged(unsigned, SimAudioState);
  void signalContactChanged(unsigned);
  void signalContactConnectionChanged(unsigned, int);
  void signalMessageReceived(unsigned, int, bool);
  void signalMessageEdited(unsigned, int);
  void signalMessageSent(unsigned, int, int);
  void signalTransferState(unsigned, qlonglong, const char *);
  void signalTransfer(unsigned, qlonglong, const QString &, const QString &);
  void signalSpeech(unsigned, int, int, int);
  void signalKeygen(const QString &, simnumber);
  void signalInvalidAudioDevice(bool);

public slots:
  void onLogout();
  void onLogin();

  void onRPCmessageReceived(const QString & message);
  void onTimerTimeout();

  void onSpeechTimeout();
  void onLatencyTimeout();
  void onHangupTimeout();

protected:
  void checkAudioLatency(simnumber simId);
  static void checkAudioHangup(simnumber simId);

  static SimCore * mc_simcore;
  static char mc_hidden;         // 0 - not hidden, h - hidden, s - hidden which is currently shown
  QHash<simnumber, int> m_idMap; // simcore id to UI id
  class KeyGen * m_keygenDialog;
  class SimThread * m_simThread;
  class HotKeyThread * m_hotKeyThread;
  static bool mc_calling;
  static char mc_callConnection; // 0 - direct, m - relayed by my proxy, p - relayed by peer proxy
  static simnumber mc_callTime;
  bool m_logged;
  int m_notifications;
  int m_myId;
  WId m_hwnd;
  void * m_hotKey[2];
  QTimer m_coreTimer;
  int m_speechProb;
  QMessageBox * m_speechDialog;
  QTimer m_speechTimer;
  LoginDialog * m_login;
  QMessageBox * m_latencyDialog;
  simnumber m_latencyId;
  QTimer m_latencyTimer;
  int m_latencyCount;
  bool m_latencyClosed;
  int m_latencyFilter;
  int m_latencyDelay;
  Contact * m_hangupData;
  QTimer m_hangupTimer;
};

class HotKeyThread : public QThread
{
  Q_OBJECT
public:
  HotKeyThread()
    : m_xcb(0) { m_keycode[1] = m_keycode[0] = 0; }

  void init(int id, int hotkey);
  void uninit();

  void run() Q_DECL_OVERRIDE;

  void * m_xcb;
  int m_keycode[2];
};

#endif
