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

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

#ifndef _CLIENT_H_
#define _CLIENT_H_

#include "simtypes.h"

struct _nat_param {
  unsigned ip[5];      /* ip addresses of peer or zero if not known */
  unsigned localip[1]; /* local ip address of peer or zero if not known */
  int port[5];         /* port numbers of peer or zero if not known */
  int localport[1];    /* local port number of peer or zero if not known */
  simnumber nonce[2];  /* relayed connection identifier of client [0] and server [1] */
};

struct _audio_param {
  int state;             /* AUDIO_CALL_xxx (see audio.h) */
  int sample;            /* audio sample rate Hz (may be zero if default or negative if fixed) */
  simnumber quality;     /* requested speex quality level */
  simnumber frames;      /* requested number of frames per packet (milliseconds) */
  simnumber mtu;         /* maximal number of bytes to send in a single packet */
  simnumber time;        /* time of last meaningful call state change or zero if none */
  const void *inparams;  /* portaudio input stream parameters or NULL if none */
  const void *outparams; /* portaudio output stream parameters or NULL if none */
  int udport;            /* peer's UDP port as reported by (proxy to) peer or zero if not reported */
};

#define CLIENT_FLAG_CONNECTED 0x100 /* connected (outgoing or incoming) */
#define CLIENT_FLAG_ACCEPTED 0x200  /* accepted (incoming connection) */
#define CLIENT_FLAG_SENDING 0x400   /* currently sending buffered data */
#define CLIENT_FLAG_BUFFERING 0x800 /* sending is suspended by traversal socket switch */
#define CLIENT_FLAG_UDP 0x1000      /* CONTACT_AUDIO_UDP has been sent */
#define CLIENT_FLAG_RINGING 0x2000  /* SIM_CMD_RING has been received */
#define CLIENT_FLAG_FORWARD 0x4000  /* client is authenticated */
#define CLIENT_FLAG_REVERSE 0x8000  /* temporary client for connection reversal */
#define CLIENT_FLAG_ERROR 0x10000   /* abnormal client disconnect (need to re-search) */
#define CLIENT_FLAG_MSG 0x20000     /* cancelled client still processing chat messages */

struct _client {
  simclient next;           /* next client in linked list - must be first element of structure */
  simsocket sock;           /* network socket */
  simcontact contact;       /* contact this client belongs to */
  void *msgtid;             /* pth thread identifier of message thread */
  void *msgqueue;           /* pth message queue of chat messages to send */
  void *msgevent;           /* pth event for the message queue */
  struct nat *nat;          /* NAT traversal context or NULL if not connected through proxy */
  struct _nat_param param;  /* NAT reversal parameters (ip[0] also for traversal) */
  int rcvtimeout;           /* NAT traversal socket receive timeout (or zero if no traversal success) */
  unsigned proxyip;         /* peer address of client's proxy (or client's address if no proxy) */
  int proxyport;            /* client's proxy port (or client port if no proxy) */
  int proxyudport;          /* client's real (proxy) port (in case proxyport is SIM_PROTO_PORT) */
  int count;                /* number of references */
  unsigned ownip;           /* my ip address as reported by peer or zero if not reported */
  int version;              /* peer's protocol version or zero if not known */
  int flags;                /* SIM_REPLY_FLAG_xxx | CLIENT_FLAG_xxx */
  struct _audio_param call; /* audio call parameters */
  simunsigned sndtick;      /* timestamp of last sent UDP packet */
  simunsigned tick;         /* tick of last non-idle packet sent or received */
  simnumber pongtick;       /* tick of last received pong packet */
  simnumber typingping;     /* tick of last sent ping packet for typing notification live check or zero if none */
  simnumber pong;           /* current packet round-trip time or zero if not known */
  simnumber latency[4];     /* latency times in milliseconds or zero if not known (see the #defines below) */
  simtype insecure;         /* insecure OS version name or nil if client OS is secure */
  simtype buffer;           /* buffer for packets to be sent later */
};

#define CLIENT_PONG_CLIENT 0 /* ping time from me to peer */
#define CLIENT_PONG_PROXY 1  /* ping time from me to proxy */
#define CLIENT_PING_CLIENT 2 /* ping time from peer to me */
#define CLIENT_PING_PROXY 3  /* ping time from peer to proxy */

/* reference a client by handle. return client */
simclient client_acquire (simclient client);

/* dereference a client by handle and synchronously disconnect it if no longer used */
void client_release (simclient client);

/* return number of clients sockets (including NAT traversal sockets). cancelled or *cancelled must be NULL or zero.
   if oldest is not NULL, also return oldest non-cancelled non-idle client which does not belong to contact */
int client_count (int *cancelled, simcontact contact, simclient *oldest);

/* add status data to existing (version or status) packet */
void client_add_status (simtype table, simcontact contact);

/* create a new client */
simclient client_new (simsocket sock, simcontact contact, int flags);

/* return a command packet to send. key/key2 may be NULL in which case value/value2 ignored. else consumes value/value2 */
simtype client_new_cmd (const char *cmd, const char *key, simtype value, const char *key2, simtype value2);

#define CLIENT_SEND_STATUS_OFF 0     /* send is unlikely to succeed */
#define CLIENT_SEND_STATUS_ON 1      /* send is likely to succeed */
#define CLIENT_SEND_STATUS_INFO (-1) /* send only if info changed */

/* send status. use NULL client to broadcast to all connected clients. sendmode is CLIENT_SEND_STATUS_xxx */
simtype client_new_status (simclient client); /* return status packet to send */
int client_send_status (simclient client, int sendmode);

/* communicate with client over TCP (send table). consume the table (and keys and values) */
int client_send (simclient client, simtype table);
int client_send_ (simclient client, simtype table);
#define client_send_cmd(client, cmd, key, value, key2, value2) \
  client_send (client, client_new_cmd (cmd, key, value, key2, value2))
#define client_send_cmd_(client, cmd, key, value, key2, value2) \
  client_send_ (client, client_new_cmd (cmd, key, value, key2, value2))

/* send proxy ip and port to all connected clients */
void client_send_proxy (void);

/* communicate with client over UDP (send packet or process a received one) */
int client_send_udp (simclient client, simtype table, simnumber sequence, unsigned ip, int port); /* consumes the table */
int client_recv_udp (const simtype input, unsigned ip, int port);

/* receive table over TCP */
int client_recv_ (simclient client, simtype *table);

/* check if ip address seems to be my own; notsure is value to return if not sure */
simbool client_check_local (unsigned ip, simbool notsure);

#define CLIENT_PROBE_CONNECT (-2) /* connect (with client/server loop) - cannot be used with client_probe */
#define CLIENT_PROBE_LOGOFF (-1)  /* send "off" packet */
#define CLIENT_PROBE_LOGON 0      /* send "on" packet (subject to current status like CLIENT_PROBE_CONNECT) */
#define CLIENT_PROBE_ACCEPT 1     /* send "on" packet even if offline */
#define CLIENT_PROBE_DHT 2        /* check if ip address is valid */
#define CLIENT_PROBE_REVERSE 3    /* check if ip address is valid; do not report listening port */

/* send an out-of-band (status) packet to contact. type is CLIENT_PROBE_xxx */
int client_probe (simcontact contact, unsigned ip, int port, const char *host, int type);

extern int client_probe_count; /* number of currently running probes */

/* start the server client loop only once */
void client_loop_ (simclient client, simbool nat);

/* connect to a contact */
int client_connect_ (simclient client, simcontact contact);

/* find a connected client. flags must be CLIENT_FLAG_CONNECTED */
simclient client_find (simcontact contact, int flags);

/* check if client is trying to connect. return zero if not */
simclient client_find_connecting (simcontact contact, simbool connected);

/* check if local client or probe with the given source ip and source port exists */
simbool client_find_local (unsigned ip, int port);

/* return real socket statistics of all clients of a contact (including cancelled clients). j is CONTACT_STAT_xxx */
simnumber client_get_stat (simcontact contact, unsigned j, simbool oldstat);

/* return connection state and proxy ip address (if any) */
simtype client_get_state (simclient client, unsigned *proxyip);

/* set desired sample rate Hz (negative = fixed, zero = default) and call state (AUDIO_CALL_xxx) */
void client_set_call (simclient client, int sample, int state);

/* switch socket of a client and send buffered packets if any. the socket lock MUST be held while this is called */
int client_set_socket_ (simclient client, simsocket sock);

/* disconnect proxy if/when no call state through it. error must not be SIM_OK */
void client_cancel_proxy (int error);

/* asynchronously disconnect a connected client. error must not be SIM_OK.
   return (last) cancelled IN or OUT client (not referenced) */
simclient client_cancel_contact (simcontact contact, int error);

/* stop running probes; return the number of undead. SIM_STATUS_INVISIBLE to kill all probes.
   SIM_STATUS_OFF to leave logoff probes running. SIM_STATUS_ON to kill only logoff probes. */
int client_cancel_probes (int status);

/* cancel local socket as soon as possible */
int client_cancel_local (simcustomer local, simsocket sock);

/* send a BYE command and then disconnect client. if client is NULL then disconnect all */
void client_cancel (simclient client, int error);

/* check if client is idle and kill it if necessary */
void client_periodic (simclient client, simnumber tick);

/* send logon or logoff status to all contacts */
void client_logoff (int status);
void client_logon (simbool now);

/* initialize and deinitialize client module */
int client_init_ (void);
int client_uninit_ (simbool init);

/* debug logging */
void client_log_clients (const char *module, int level, simbool sessions);
void client_log_probes (const char *module, int level);
void client_log_traversers (const char *module, int level);
void client_log_self (const char *module, int level);

#endif
