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

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

#ifndef _SIMTYPES_H_
#define _SIMTYPES_H_

typedef void *simrandom[];
typedef struct _contact *simcontact;
typedef struct _socket *simsocket;
typedef struct _customer *simcustomer;
typedef struct _client *simclient;

#ifndef __cplusplus
#undef false
#undef true
typedef enum { false,
               true } SIMBOOL; /* define false and true - SIMBOOL is not used */
#endif
typedef int simbool; /* C++ decided that bool should be a byte */
typedef unsigned char simbyte;

#ifndef _MSC_VER
typedef long long simnumber;
typedef unsigned long long simunsigned;

#ifndef SIZEOF_VOID_P
#if __UINTPTR_MAX__ == 18446744073709551615
#define SIZEOF_VOID_P 8
#elif __UINTPTR_MAX__ == 4294967295
#define SIZEOF_VOID_P 4
#else
#error please define SIZEOF_VOID_P to 4 for 32-bit version or to 8 for 64-bit version
#endif /* __UINTPTR_MAX__ */
#endif /* SIZEOF_VOID_P */
#else
typedef __int64 simnumber;
typedef unsigned __int64 simunsigned;

#ifndef SIZEOF_VOID_P
#ifdef _WIN64
#define SIZEOF_VOID_P 8
#else
#define SIZEOF_VOID_P 4
#endif
#endif /* SIZEOF_VOID_P */
#endif /* _MSC_VER */

typedef struct _simtype {
#if SIZEOF_VOID_P == 4
  void *padding; /* align simtype to 16 bytes in 32-bit version */
#else
  unsigned len;
#endif
  int typ;
  union {
    struct {
      void *ptr;
#if SIZEOF_VOID_P == 4
      unsigned len;
#endif
    };
    simnumber num;
    simbyte *str;
    struct _simtype *arr;
    struct _simelement *tbl;
  };
} simtype;

struct _simelementalign {
  int keytype;
  simnumber keynum;
}; /* cpu that has no 64-bit registers should make 12 bytes size; else size is 16 bytes */

struct _simelement {
  struct _simelement *next; /* overlaps with key.padding */
#if SIZEOF_VOID_P == 4
  union {
    char padding[sizeof (struct _simelementalign)];
    struct {
      int keytype;
      void *keyptr;
      unsigned keylen;
    };
  };
  int valtype;
  void *valptr;
  unsigned vallen;
#else
  unsigned keylen;
  int keytype;
  void *keyptr;
  unsigned vallen;
  int valtype;
  void *valptr;
#endif
}; /* structure of a table element */

typedef struct { /* table enumerator context */
  struct _simelement *chain, *next;
  struct _simelement *first;
  unsigned length;
} simwalker;

/* data types (simtypes) */

#define SIMNUMBER 0 /* 64-bit integer */
#define SIMSTRING 1 /* memory-allocated string */
#define SIMTABLE 2  /* hash table keyed by string */
#define SIMARRAY 3  /* only used by type_array */

#define SIMARRAY_NUMBER 4 /* array of numbers */
#define SIMARRAY_STRING 5 /* array of strings */
#define SIMARRAY_TABLE 6  /* array of tables */
#define SIMARRAY_ARRAY 7  /* array of arrays */

#define SIMNIL 8     /* type of sim_nil () */
#define SIMPOINTER 9 /* user-managed string */
#define SIMBUFFER 11 /* re-sizable binary string */

#define SIMTABLE_NORMAL 0 /* table keys can collide */
#define SIMTABLE_CONST 1  /* table assumed to be collision-free */
#define SIMTABLE_RAND 2   /* use randomized hash function */
#define SIMTABLE_LONG 4   /* proxy command (internally used) */
#define SIMTABLE_SHORT 8  /* proxy data (internally used) */

simtype sim_nil (void); /* indicates nothing - cannot be used as a value */

simtype sim_number_new (simnumber number); /* convert number to simtype */
#define sim_number_free(number)            /* free a number simtype */

#define SIM_NUMBER_CHECK(number) (! ((number) >> 62) || ((number) >> 62 & 3) == 3) /* check if number can be used with sim_table_write */

/* convert string to SIMPOINTER. pointer_new assumes zero-termination.
   these strings are managed by the user (only pointers to them are stored) */

simtype sim_pointer_new_length (const void *string, unsigned length);
simtype sim_pointer_new (const char *string);

/* create dynamically allocated (and automatically freed) string */

simtype _sim_string_new (unsigned length, const char *file, unsigned line); /* leaves space for terminating zero byte */
simtype sim_string_new_pointer (void *pointer, unsigned length);            /* pointer must have been returned by sim_new */
void _sim_string_free (simtype string, const char *file, unsigned line);
simtype _sim_string_buffer_new (unsigned length, const char *file, unsigned line); /* create SIMBUFFER (binary string) */
void _sim_string_buffer_free (simtype buffer, const char *file, unsigned line);    /* free SIMBUFFER */

/* expand or truncate (realloc) buffer to length */

void _sim_string_buffer_append (simtype *buffer, unsigned oldlen, unsigned addlen, const char *file, unsigned line); /* return newlen */
simtype _sim_string_buffer_truncate (simtype buffer, unsigned length, const char *file, unsigned line);              /* return string */

/* memcpy. these functions add the terminating 0 but don't count it */

simtype _sim_string_copy (const char *string, const char *file, unsigned line);
simtype _sim_string_copy_length (const void *string, unsigned length, const char *file, unsigned line);
simtype _sim_string_copy_string (const simtype string, const char *file, unsigned line);

simtype _sim_string_cat (const char *string1, const char *string2, const char *file, unsigned line);
simtype _sim_string_cat_length (const char *string1, unsigned length1, const char *string2, unsigned length2,
                                const char *file, unsigned line);

/* concatenate multiple strings; terminate list with NULL */
simtype _sim_string_concat (const char *file, unsigned line, const char *string, ...);

/* check if two types differ */

simbool _sim_string_check_diff_length (const simtype string1, const void *string2, unsigned length);
simbool sim_string_check_diff (const simtype string1, const char *string2);
simbool _sim_type_check_diff (const simtype value1, const simtype value2, const char *file, unsigned line);

/* create or destroy dynamically allocated array of specified type */

simtype _sim_array_new (unsigned length, int type, const char *file, unsigned line);
simtype _sim_array_new_type (unsigned length, simtype value, const char *file, unsigned line); /* initialize first element */
void _sim_array_free (simtype array, const char *file, unsigned line);

/* return type of array's elements. for array of array, return SIMARRAY */
int _sim_array_get_type (const simtype array, const char *file, unsigned line);

#define sim_array_size(array) (array).arr[0].len /* return real (allocated) size of array */

/* create protocol template element */

#define SIM_NEW_NUMBER(key) key, sim_number_new (0)
#define SIM_NEW_STRING(key) key, sim_pointer_new ("")
#define SIM_NEW_NUMBERS(key, length) key, _sim_array_new_type (length, sim_number_new (0), __FUNCTION__, __LINE__)
#define SIM_NEW_STRINGS(key, length) key, _sim_array_new_type (length, sim_pointer_new (""), __FUNCTION__, __LINE__)

/* expand (realloc) or delete elements from array */

void _sim_array_append (simtype *array, simtype value, const char *file, unsigned line);                /* memcpy */
void _sim_array_delete (simtype *array, unsigned idx, unsigned count, const char *file, unsigned line); /* frees deleted elements */

/* return a full copy of array or table */

simtype _sim_array_copy (const simtype array, simbool strings, const char *file, unsigned line);                  /* recursive copy if necessary */
simtype _sim_type_copy (const simtype value, simbool strings, const char *file, unsigned line);                   /* use only if value type unknown */
simtype _sim_table_copy (const simtype table, unsigned length, simbool strings, const char *file, unsigned line); /* recursive copy if necessary */

/* create or destroy hash table with length number of buckets */

simtype _sim_table_new (unsigned length, int type, const char *file, unsigned line);
simtype _sim_table_new_const (unsigned length, int type, const char *file, unsigned line, const char *key, ...);

void _sim_table_free (simtype table, const char *file, unsigned line);
unsigned _sim_table_count (const simtype table, const char *file, unsigned line); /* return number of elements in a table */

/* lookup key in a table. return value or nil if not found.
   _sim_table_get_key_type also returns nil if value not of specified type */

simtype _sim_table_get_key (const simtype table, const simtype key, const char *file, unsigned line);
simtype _sim_table_get_key_type (const simtype table, const simtype key, int type, const char *file, unsigned line);

/* set values to table (possibly overwriting old values) or add values to table (not overwriting old ones).
   note that set and add functions "consume" dynamically allocated keys and values; if you don't want
   them to be automatically freed (by the table), use sim_pointer_new or sim_pointer_new_length to pass a pointer. */

/* store value to key (replacing the old one if any). return nil if old value didn't exist,
   else free old value before returning - do not use the return value except for checking if its nil */
simtype _sim_table_set_key (simtype table, simtype key, simtype value, const char *file, unsigned line);

/* store value to key only if key didn't exist. return nil if successful, else return old value */
simtype _sim_table_add_key (simtype table, simtype key, simtype value, const char *file, unsigned line);

/* delete key from a table. return old value or nil if key didn't exist.
   if key existed, free old value before returning - do not use the return value except for checking if its nil */
simtype _sim_table_delete_key (simtype table, const simtype key, const char *file, unsigned line);

/* delete and return elements without freeing them */

/* detach value from table. frees key but not value. return value or nil if failed.
   _sim_table_detach_key_type also returns nil if value not of specified type */
simtype _sim_table_detach_key (simtype table, const simtype key, const char *file, unsigned line);
simtype _sim_table_detach_key_type (simtype table, const simtype key, int type, const char *file, unsigned line);

/* detach value from array so it won't be freed with the array. return old value which is not freed */
simtype _sim_array_detach (simtype array, unsigned idx, const char *file, unsigned line);

/* remove all detached values from array in one sweep */
void _sim_array_detach_end (simtype *array, const char *file, unsigned line);

/* enumerate elements of a table */

/* start enumerating. return false if not a table */
simbool _sim_table_walk_first (simwalker *context, const simtype table, const char *file, unsigned line);

/* return next value (and key) or nil if no more elements. if key = NULL, don't return key */
simtype sim_table_walk_next (simwalker *context, simtype *key); /* also return nil if value not of specified type */
simtype _sim_table_walk_next_type (simwalker *context, simtype *key, int type, const char *file, unsigned line);

/* stop enumerating: table_walk_next will keep returning nil after sim_table_walk_last is called */
void sim_table_walk_last (simwalker *context);

/* free value of any type - use only if type of value is unknown */

void _sim_type_free (simtype value, const char *file, unsigned line);

/* convert table to and from byte stream */

/* return number of bytes in converted simtype without converting it */
unsigned _sim_type_size (const simtype value, const char *file, unsigned line);

/* must return true if exactly buffer.len bytes read/written, else return false to abort. */
typedef simbool (simstreamer) (void *context, simtype buffer);

/* convert byte stream to table. return nil if error occurred */
simtype _sim_table_read (const simtype proto, const simtype proto2, simstreamer *reader, void *context,
                         const char *file, unsigned line);

/* convert table to byte stream. return true if successfully converted, false if error occurred */
simbool _sim_table_write (const simtype table, simstreamer *writer, void *context, const char *file, unsigned line);

#ifndef SIM_MEMORY_CHECK
simtype sim_string_new (unsigned length);
simtype sim_string_buffer_new (unsigned length);
simtype sim_string_copy (const char *string);
simtype sim_string_copy_length (const void *string, unsigned length);
simtype sim_string_cat (const char *string1, const char *string2);
simtype sim_string_cat_length (const char *string1, unsigned length1, const char *string2, unsigned length2);
simtype sim_string_concat (const char *string, ...);

#define _sim_string_new(length, file, line) sim_string_new (length)
#define _sim_string_buffer_new(length, file, line) sim_string_buffer_new (length)
#define _sim_string_copy(string, file, line) sim_string_copy (string)
#define _sim_string_copy_length(string, length, file, line) sim_string_copy_length (string, length)
#define _sim_string_cat(string1, string2, file, line) sim_string_cat (string1, string2)
#define _sim_string_cat_length(string1, length1, string2, length2, file, line) \
  sim_string_cat_length (string1, length1, string2, length2)
#define _sim_string_concat(file, line, string, ...) sim_string_concat (string, __VA_ARGS__)
#endif

#endif
