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

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

#ifndef _UTILS_H_
#define _UTILS_H_

#include "simtypes.h"

#define SIM_FILE_BACKUP ".old"

#ifdef SIM_MEMORY_CHECK
typedef struct _sim_memory_block {
  struct _sim_memory_block *left;  /* left successor in binary tree */
  struct _sim_memory_block *right; /* right successor in binary tree */
  const char *file;                /* name of function which called sim_new */
  int line;                        /* line number where sim_new was called or negative */
  unsigned time;                   /* time when allocated */
#if HAVE_LIBEFENCE || ! defined(_WIN32)
  unsigned length; /* block length as given to sim_new */
#endif
} _simblock;
#endif

/* initialize bounds-checking gcc */
int sim_init_memory (void);
void memory_log_tables (const char *module, int level); /* if built for memory debugging */

#if HAVE_MALLOC_USABLE_SIZE && ! HAVE_LIBEFENCE
#define SIM_MEMORY_SIZE(pointer, length) ((unsigned) malloc_usable_size (pointer) + (unsigned) sizeof (long))
#else
#define SIM_MEMORY_SIZE(pointer, length) \
  ((((length) + (unsigned) sizeof (long) * 2 - 1) / ((unsigned) sizeof (long) * 2) + 1) * (unsigned) sizeof (long) * 2)
#endif

#ifdef SIM_MEMORY_CHECK
#define SIM_POINTER_SIZE(pointer, length) \
  (unsigned) (SIM_MEMORY_SIZE ((char *) (pointer) - sizeof (_simblock), (length) + sizeof (_simblock)) + sizeof (long))

void *_sim_new (unsigned length, int type, const char *file, unsigned line);

#define sim_new(length) _sim_new (length, SIMNIL, __FUNCTION__, __LINE__)
#else
#define SIM_POINTER_SIZE(pointer, length) SIM_MEMORY_SIZE (pointer, length)
void *sim_new (unsigned length); /* allocate memory block. cannot return NULL (unless length is zero) */

#define _sim_new(length, type, file, line) sim_new (length)
#endif

/* wipe and free memory block. length must match the one given to sim_new. NULL pointer is ignored */
void _sim_free (void *pointer, unsigned length, const char *file, unsigned line);
void _sim_free_string (char *string, const char *file, unsigned line); /* wipe and release ASCIIz string */

#define sim_free(pointer, length) _sim_free (pointer, length, __FUNCTION__, __LINE__)
#define sim_free_string(string) _sim_free_string (string, __FUNCTION__, __LINE__)
#define SIM_FREE(pointer, length, value) (_sim_free (*(pointer), length, __FUNCTION__, __LINE__), *(pointer) = (value))
#define SIM_FREE_STRING(pointer, value) (_sim_free_string (*(pointer), __FUNCTION__, __LINE__), *(pointer) = (value))

/* event names */
#define SIM_EVENT_FATAL "fatal"
#define SIM_EVENT_CONTACT "contact"
#define SIM_EVENT_STATUS "status"
#define SIM_EVENT_MSG "msg"
#define SIM_EVENT_EDIT "edit"
#define SIM_EVENT_SENT "sent"
#define SIM_EVENT_NOTSENT "notsent"
#define SIM_EVENT_XFER "xfer"
#define SIM_EVENT_AUDIO "audio"
#define SIM_EVENT_SPEECH "speech"
#define SIM_EVENT_KEYGEN "keygen"
#define SIM_EVENT_HISTORY "history"
#define SIM_EVENT_NET "net"
#define SIM_EVENT_ERROR "error"
#define SIM_EVENT_LOG "log"
#define SIM_EVENT_TICK "tick"

/* event table keys */
#define SIM_EVENT_SPEECH_PROBABILITY "prob"
#define SIM_EVENT_SPEECH_PROGRESS "prog"

#define SIM_EVENT_SPEECH_MIN 0
#define SIM_EVENT_SPEECH_MAX 100
#define SIM_EVENT_SPEECH_END (-1)

#define SIM_EVENT_KEYGEN_TIME "seconds"
#define SIM_EVENT_KEYGEN_ADDRESS "addr"

#define SIM_EVENT_NET_STATUS "status"
#define SIM_EVENT_NET_DHT "dht"
#define SIM_EVENT_NET_LIMIT "limit"
#define SIM_EVENT_NET_PONG "pong"
#define SIM_EVENT_NET_QOS "qos"
#define SIM_EVENT_NET_CONNECT "connect"
#define SIM_EVENT_NET_DISCONNECT "disconnect"
#define SIM_EVENT_NET_TRAVERSE "traverse"
#define SIM_EVENT_NET_XFER "xfer"
#define SIM_EVENT_NET_DEVICE "device"

#define SIM_EVENT_NET_PONG_NUMBER "num"

#define SIM_EVENT_NET_CONNECT_ENCRYPT "encrypt"
#define SIM_EVENT_NET_CONNECT_DECRYPT "decrypt"

#define SIM_EVENT_NET_TRAVERSE_STATUS "traversed"
#define SIM_EVENT_NET_TRAVERSE_ENCRYPT "encrypt"
#define SIM_EVENT_NET_TRAVERSE_DECRYPT "decrypt"

#define SIM_EVENT_NET_DEVICE_INPUT "input"
#define SIM_EVENT_NET_DEVICE_OUTPUT "output"
#define SIM_EVENT_NET_DEVICE_RING "ring"

#define SIM_EVENT_ERROR_INSTALL "install"
#define SIM_EVENT_ERROR_SOCKS "socks"
#define SIM_EVENT_ERROR_SYSTEM "system"
#define SIM_EVENT_ERROR_CIPHER "cipher"
#define SIM_EVENT_ERROR_PROTOCOL "protocol"
#define SIM_EVENT_ERROR_KEY "tofu"
#define SIM_EVENT_ERROR_XFER "xfer"
#define SIM_EVENT_ERROR_XFER_HANDLE "n"
#define SIM_EVENT_ERROR_XFER_NAME "name"
#define SIM_EVENT_ERROR_AUDIO "audio"

#define SIM_EVENT_ERROR_INIT "init"
#define SIM_EVENT_ERROR_INIT_NAME "name"
#define SIM_EVENT_ERROR_INIT_TYPE "type"
#define SIM_EVENT_ERROR_INIT_TYPE_THREAD "thread"
#define SIM_EVENT_ERROR_INIT_LOCAL "local"
#define SIM_EVENT_ERROR_FILE_CREATE "create"
#define SIM_EVENT_ERROR_FILE_OPEN "open"
#define SIM_EVENT_ERROR_FILE_CLOSE "close"
#define SIM_EVENT_ERROR_FILE_LOAD "load"
#define SIM_EVENT_ERROR_FILE_SAVE "save"
#define SIM_EVENT_ERROR_FILE_WRITE "write"
#define SIM_EVENT_ERROR_FILE_DELETE "delete"
#define SIM_EVENT_ERROR_FILE_WIPE "wipe"
#define SIM_EVENT_ERROR_FILE_RENAME "rename"
#define SIM_EVENT_ERROR_FILE_COPY "copy"

/* send event (call event handler) */
void sim_event_send_fatal (int error, simtype line);      /* consumes line. call log_fatal_ instead of this */
void event_send_fast (simnumber id, const simtype event); /* do not log and do not free the event */
void event_send_id (simnumber id, const simtype event);   /* send to non-contact id; do not free the event */
void event_send_tick_type (const char *type);             /* do not log but free the event */
void event_send_tick (simnumber id, const char *type);    /* do not log but free the event */
void event_send (simcontact contact, simtype event);      /* log and free the event */
void event_send_audio (simcontact contact, const char *oldstate, const char *newstate);
#define event_send_name_number(contact, name, type, number) event_send_name (contact, name, type, number_new (number))
void event_send_name (simcontact contact, const char *name, const char *type, simtype value); /* frees value */
#define event_send_value_number(contact, name, key, number) event_send_value (contact, name, key, number_new (number))
void event_send_value (simcontact contact, const char *name, const char *key, simtype value); /* frees value */
int _event_test_error_crypto (simcontact contact, const char *address, int error, const char *file, unsigned line);
int _event_test_error (simcontact contact, const char *type, int error, const char *file, unsigned line);
#define event_test_error_crypto(contact, address, error) \
  _event_test_error_crypto (contact, address, error, __FUNCTION__, __LINE__)
#define event_test_error(contact, type, error) _event_test_error (contact, type, error, __FUNCTION__, __LINE__)

/* register and unregister handler for an event */
void *event_register (const char *name, const void *handler);

/* initialize and deinitialize event handlers */
void event_init (void);
void event_unregister (void);
void event_uninit (void);

/* 64-bit integer sprintf format specifier */
#ifdef _WIN32
#define SIM_FORMAT_64 "I64"
#else
#define SIM_FORMAT_64 "ll"
#endif

/* convert 32-bit unicode to dynamically-allocated UTF-8 (caller must call string_free).
   *length is number of unicode characters on input, number of bytes on output. return nil if is not a valid unicode string */
simtype sim_convert_int_to_utf (const int *string, unsigned *length);

/* convert UTF-16 to dynamically-allocated UTF-8 (call must call string_free). *length is number of shorts on input,
   number of bytes on output. return nil (and *length = 0) if string is not valid (has an unpaired surrogate) */
simtype sim_convert_ucs_to_utf (const unsigned short *string, unsigned *length);

/* convert UTF-8 to dynamically-allocated 32-bit unicode (caller must call string_free).
   *length is number of bytes on input, number of unicode characters on output. return nil if string is not a valid UTF-8 string */
simtype sim_convert_utf_to_int (const char *string, unsigned *length);

/* convert UTF-8 to dynamically-allocated little-endian UTF-16 (caller must call string_free).
   *length is number of bytes on input, number of UTF-16 shorts on output. return nil if string is not a valid UTF-8 string */
simtype sim_convert_utf_to_ucs (const char *string, unsigned *length);

/* convert UTF-8 to dynamically-allocated 32-bit unicode or little-endian UTF-16 (caller must call string_free). */
simtype sim_convert_utf (const char *string, unsigned *length);

/* convert simtype to XML. return SIMSTRING or nil if invalid; caller must call string_free */
simtype sim_convert_type_to_xml (const simtype value, const char *tag);

/* stringify simtype. return SIMSTRING or nil if invalid; caller must call string_free */
simtype sim_convert_type_to_json (const simtype value);

/* compare first bytes of string with constant string */
#define SIM_STRING_GET_LENGTH_CONST(string) (sizeof (string) - 1)
#define SIM_STRING_CHECK_DIFF_CONST(string, conststr) strncmp (string, conststr, SIM_STRING_GET_LENGTH_CONST (conststr))

/* return number of elements of an array */
#define SIM_ARRAY_SIZE(pointer) (sizeof (pointer) / sizeof (*(pointer)))
#define SIM_ARRAY(pointer) (pointer), SIM_ARRAY_SIZE (pointer)

/* return a positive random integer */
#ifdef _WIN32
#define sim_get_random(max) ((rand () << 16 ^ rand ()) % (max))
#else
#define sim_get_random(max) (rand () % (max))
#endif

/* convert 64-bit unsigned decimal to 20-character string (caller must call string_free).
   if prefix or suffix is an empty (non-NULL) string, the result may be shorter than 20 characters */
simtype sim_convert_simunsigned_to_string (const char *prefix, simunsigned id, const char *suffix);

#define SIM_SIZE_TIME 20 /* length of time string (including terminating zero) */

/* convert real time to ASCII. return length or zero if failed */
unsigned sim_convert_time_to_string (simunsigned datetime, char output[SIM_SIZE_TIME]);

/* convert real time from ASCII. return zero if failed */
simunsigned sim_convert_string_to_time (const char *datetime, unsigned length);

/* return a lowercase copy of ASCII string */
simtype sim_convert_string_to_locase (const char *string);

/* return an uppercase copy of ASCII string */
simtype sim_convert_string_to_upcase (const char *string);

/* return an ASCII hash of a string */
simtype sim_convert_string_to_hash (const simtype string);

/* find name in list of names and return its index or def if not found */
int sim_convert_string_to_enum (const char *name, int min, int def, const char *names[], unsigned size);

/* convert bitmap to array of strings. undefined bits are ignored */
simtype sim_convert_flags_to_strings (simunsigned flags, const char *names[], unsigned size);

/* convert array of strings to bitmap. undefined strings are ignored */
simunsigned sim_convert_strings_to_flags (const simtype array, const char *names[], unsigned size);

/* return error description in simcore static buffer */
const char *convert_error (int error, simbool peek);

/* return description of error code. use sim_error_peek for non-destructive read */
const char *sim_error_peek (int error);
const char *sim_error_get (int error);

#define SIM_SIZE_ERROR 1024

/* return error description in user output buffer */
const char *sim_error_get_buffer (int error, simbool peek, char output[SIM_SIZE_ERROR]);
const char *sim_error_get_message (int error, int language, simbool peek, char output[SIM_SIZE_ERROR]);

#define ERROR_BASE_CARES (-9900)  /* start of cares errors */
#define ERROR_BASE_EXPAT (-10000) /* start of expat errors and end of portaudio errors */

/* get additional error information (for internal use) */
char *sim_error_get_text (int error);

/* set additional error information (for internal use) */
int sim_error_set_text (const char *prefix, const char *text, const char *suffix, int error);
#define sim_error_reset_text() sim_error_set_text (NULL, NULL, NULL, SIM_OK)

/* save and restore additional error information (for internal use) */
int sim_error_save_text (const char **text);
void sim_error_load_text (const char *text, int error);

/* set language for error messages or reset to default if language is negative. return old language or negative if failed */
int sim_error_init_language (int language);

/* initialize and deinitialize error buffer */
int error_init (void);
int error_uninit (void);

/* delete linked list element. next pointer must be first element in list element structure. return NULL if element not found */
void *sim_list_delete (void *list, const void *element);

/* append linked list element. next pointer must be first element in list element structure */
void sim_list_append (void *list, void *last, void *element);

/* create a Pth queue with msgport and event. arguments are pointers to pth_msgport_t and pth_event_t */
#define pth_queue_new(msgport, event, fd) _pth_queue_new (SIM_MODULE, msgport, event, fd)
int _pth_queue_new (const char *module, void *msgport, void *event, int fd);

/* destroy a Pth queue with msgport and event. arguments are pointers to pth_msgport_t and pth_event_t */
void pth_queue_free (void *msgport, void *event, unsigned size);

/* put an item into a Pth queue. msgport IS a pth_msgport_t. item is a pointer to pth_message_t */
#define pth_queue_put(msgport, item) _pth_queue_put (SIM_MODULE, msgport, item)
int _pth_queue_put (const char *module, void *msgport, void *item);

/* get an item from a Pth queue */
#define pth_queue_get(msgport) pth_msgport_get (msgport)

/* wait for a Pth queue item to become available. return positive on success, negative on error */
#define pth_queue_wait_(event, fd) _pth_queue_wait_ (SIM_MODULE, event, fd)
int _pth_queue_wait_ (const char *module, void *event, int fd);

/* release thread-local storage. return NULL */
#define system_log_stack(starttick) \
  LOG_CODE_DEBUG_ (_system_log_stack (SIM_MODULE, SIM_LOG_DEBUG, starttick, __FUNCTION__))
#define pth_thread_exit_(ssl) system_log_stack (pth_thread_get_tick ()), _pth_thread_exit_ (ssl, __FUNCTION__)
void *_pth_thread_exit_ (simbool ssl, const char *function);

/* start a Pth thread. user must call pth_thread_join_ (&*tid) later if tid not NULL */
#define pth_thread_spawn(THREAD, context, contact, tid, number) \
  _pth_thread_spawn (THREAD, context, contact, tid, pointer_new (#THREAD), number)
int _pth_thread_spawn (void *(*thread) (void *), void *context, simcontact contact, void *tid,
                       simtype name, simnumber number); /* consumes name */

/* set *tid to NULL and wait for Pth thread to finish. NULL *tid is a no-op (but should be avoided) */
#define pth_thread_join_(tid, value, THREAD, number) _pth_thread_join_ (tid, value, #THREAD, number)
int _pth_thread_join_ (void *tid, void **value, const char *name, simnumber number);

/* prioritize current thread */
void pth_thread_set_priority (void);

/* switch execution control to simcore */
int pth_thread_yield_ (unsigned useconds);

/* debug logging */
simtype pth_thread_acquire (void);
simunsigned pth_thread_get_tick (void);
void *pth_thread_release (simtype thread); /* frees thread info */
simbool pth_log_threads (const char *module, int level);

#endif
