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

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

#ifndef _MSG_H_
#define _MSG_H_

#include "simtypes.h"

/* XML history file */
#define MSG_XML_BODY "<body>"
#define MSG_XML_TAIL "</body></html>"

#define MSG_HISTORY_ENTRY "pre"
#define MSG_HISTORY_SYSTEM "u"
#define MSG_HISTORY_INCOMING "b"
#define MSG_HISTORY_DATE "code"
#define MSG_HISTORY_MSG ""
#define MSG_HISTORY_TIME "t"
#define MSG_HISTORY_UTC "u"
#define MSG_HISTORY_HANDLE "n"
#define MSG_HISTORY_EDIT "e"
#define MSG_HISTORY_XFER "h"

#define MSG_NUMBER_INVALID 0x4000000000000000LL

struct _message {
  unsigned sndtime;    /* (time_t) SIM_CMD_MSG_TIME */
  unsigned rcvtime;    /* (time_t) SIM_CMD_MSG_RECV or zero if not received */
  simnumber offset;    /* offset in history file or zero if not saved */
  simnumber handle;    /* SIM_CMD_MSG_HANDLE (may be zero if system) */
  simnumber oldhandle; /* SIM_CMD_MSG_EDIT (for SIM_MSG_TYPE_UTF) or zero.
                          SIM_CMD_XFER_SEND_HANDLE for SIM_MSG_TYPE_SYSTEM */
#if SIZEOF_VOID_P == 4
  union {
#else
  simbyte type;   /* SIM_CMD_MSG_TYPE (SIM_MSG_TYPE_xxx) */
  simbyte status; /* SIM_CMD_MSG_STATUS (SIM_MSG_xxx) */
#endif
    unsigned next; /* next message in handles hash chain or zero for end of chain */
    simtype text;  /* SIM_CMD_MSG_TEXT or nil if no text (message removed) */
#if SIZEOF_VOID_P == 4
  };
  union {
#endif
    const char *tonick; /* SIM_CMD_MSG_NICK or NULL if no previous nick */
    simtype nick;       /* SIM_CMD_MSG_SENDER */
#if SIZEOF_VOID_P == 4
  };
  unsigned index : 27; /* indexes subscript or zero if not indexed */
  unsigned type : 1;   /* SIM_CMD_MSG_TYPE (SIM_MSG_TYPE_xxx) */
  unsigned status : 4; /* SIM_CMD_MSG_STATUS (SIM_MSG_xxx) */
#else
  unsigned index; /* indexes subscript or zero if not indexed */
#endif
};

/* convert message to XML simtype */
simtype sim_msg_convert_to_xml (const struct _message *message, unsigned datetime,
                                char output[512], unsigned *spaces, unsigned *nicklen);

/* return text and *duration of message with the specified index */
simtype msg_get_text (simnumber id, unsigned idx, simnumber *duration);

/* send pending messages to client (internal use only) */
simbool msg_get_ (simclient client, simbool init);

/* return queue index for given message number or zero if not found */
unsigned msg_get_hash (simcontact contact, simnumber handle);

/* send buffered messages to client. idx is zero (internal use only) */
int msg_put (simclient client, int idx);

/* start message thread */
int msg_start_thread (simclient client);

/* stop message thread */
void msg_stop_thread_ (simclient client);

/* save pending messages */
void msg_save (simcontact contact);

/* handle periodic tasks for non-received messages */
void msg_periodic (simcontact contact, simunsigned tick);

extern int msg_fd_count; /* number of open file descriptors */

/* close history file if open */
void msg_close (simcontact contact);

/* append system message with the specified number to history file and send history event */
void event_send_history_system (simcontact contact, const char *tag, const char *text,
                                simnumber handle, simnumber recvtime, simnumber number, simbool local);

/* start connecting to contact */
int msg_connect (simcontact contact, simbool reconnect);

/* create new message of the specified type (SIM_MSG_TYPE_xxx). consumes text */
int msg_create (simcontact contact, simtype text, simbyte type, struct _message *message);

/* free message text and nick */
void msg_destroy (const struct _message *message);

/* handle periodic tasks for received messages */
void msg_send_ack (simclient client, simnumber tick);

/* send message to contact. always consumes the message and frees it when done */
int msg_send (simcontact contact, struct _message *message, unsigned idx);

/* send system message */
int msg_send_system (simcontact contact, const char *tag, const char *text, simnumber recvtime, int error);

/* queue or dequeue outgoing audio call with the specified sample rate */
#define MSG_SET_CALL(contact, time, sample) ((contact)->msgs.calltime = (time), (contact)->msgs.callsample = (sample))
#define MSG_UNSET_CALL(contact) (contact)->msgs.calltime = (contact)->msgs.callsample = 0
#define MSG_CHECK_CALL(contact) (contact)->msgs.calltime

/* edit sent message to contact. always consumes the message and frees it when done */
int msg_edit (simcontact contact, struct _message *message, simunsigned idx);

/* remove message */
int msg_remove (simcontact contact, simunsigned idx);

/* acknowledge sent system message or all of them if handle is zero */
void msg_ack (simcontact contact, simnumber handle, int resent);

/* acknowledge sent messages up to and including the given message number */
void msg_recv_ack (simcontact contact, simnumber handle);

/* queue incoming message. consumes message text */
int msg_recv (simclient client, simtype text, const simtype table);

/* check if msg_load can be called with reload = true */
int msg_wait_ (simcontact contact);

/* load messages from history file. if reload = false, load pending messages from file too. consumes messages */
int msg_load (simcontact contact, simtype messages, unsigned count, simbool reload);

/* check if there are messages to send to contact */
simbool msg_check_pending (simcontact contact);

/* initialize messages structure */
void msg_init (simcontact contact, simnumber msgnum);

/* finalize messages structure */
void msg_uninit (simcontact contact);

/* debug logging */
int msg_log_queue (const char *module, int level, simnumber id);

#endif
