#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <memory.h>
#include "lib/binn.h"

#ifdef MULTIPASS_TRACE_MALLOC
#include "lib/mpmalloc.h"
#else
#define mpmalloc malloc
#define mprealloc realloc
#define mpfree free
#endif

#define UNUSED(x) (void)(x)
#define round(dbl) dbl >= 0.0 ? (int)(dbl + 0.5) : ((dbl - (double)(int)dbl) <= -0.5 ? (int)dbl : (int)(dbl - 0.5))

// magic number:  0x1F 0xb1 0x22 0x1F  =>  0x1FB1221F or 0x1F22B11F
// because the BINN_STORAGE_NOBYTES (binary 000) may not have so many sub-types (BINN_STORAGE_HAS_MORE = 0x10)
#define BINN_MAGIC            0x1F22B11F

#define MAX_BINN_HEADER       9  // [1:type][4:size][4:count]
#define MIN_BINN_SIZE         3  // [1:type][1:size][1:count]
#define CHUNK_SIZE            256  // 1024

#define BINN_STRUCT        1
#define BINN_BUFFER        2

void* (*malloc_fn)(size_t len) = 0;
void* (*realloc_fn)(void *ptr, size_t len) = 0;
void  (*free_fn)(void *ptr) = 0;

/***************************************************************************/

#if defined(__alpha__) || defined(__hppa__) || defined(__mips__) || defined(__powerpc__) || defined(__sparc__)
#define BINN_ONLY_ALIGNED_ACCESS
#elif ( defined(__arm__) || defined(__aarch64__) ) && !defined(__ARM_FEATURE_UNALIGNED)
#define BINN_ONLY_ALIGNED_ACCESS
#endif

#if defined(_WIN32)
#define BIG_ENDIAN      0x1000
#define LITTLE_ENDIAN   0x0001
#define BYTE_ORDER      LITTLE_ENDIAN
#elif defined(__APPLE__)
/* macros already defined */
#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__)
#include <sys/endian.h>
#elif defined(_AIX)
#include <sys/machine.h>
#else
#include <endian.h>
#endif

#ifndef BYTE_ORDER
#error "BYTE_ORDER not defined"
#endif
#ifndef BIG_ENDIAN
#error "BIG_ENDIAN not defined"
#endif
#ifndef LITTLE_ENDIAN
#error "LITTLE_ENDIAN not defined"
#endif
#if BIG_ENDIAN == LITTLE_ENDIAN
#error "BIG_ENDIAN == LITTLE_ENDIAN"
#endif
#if BYTE_ORDER!=BIG_ENDIAN && BYTE_ORDER!=LITTLE_ENDIAN
#error "BYTE_ORDER not supported"
#endif

typedef unsigned short int     u16;
typedef unsigned int           u32;
typedef unsigned long long int u64;

BINN_PRIVATE void copy_be16(u16 *pdest, u16 *psource) {
#if BYTE_ORDER == LITTLE_ENDIAN
  unsigned char *source = (unsigned char *) psource;
  unsigned char *dest = (unsigned char *) pdest;
  dest[0] = source[1];
  dest[1] = source[0];
#else // if BYTE_ORDER == BIG_ENDIAN
#ifdef BINN_ONLY_ALIGNED_ACCESS
  if (psource % 2 == 0){  // address aligned to 16 bit
    *pdest = *psource;
  } else {
    unsigned char *source = (unsigned char *) psource;
    unsigned char *dest = (unsigned char *) pdest;
    dest[0] = source[0];  // indexes are the same
    dest[1] = source[1];
  }
#else
  *pdest = *psource;
#endif
#endif
}

BINN_PRIVATE void copy_be32(u32 *pdest, u32 *psource) {
#if BYTE_ORDER == LITTLE_ENDIAN
  unsigned char *source = (unsigned char *) psource;
  unsigned char *dest = (unsigned char *) pdest;
  dest[0] = source[3];
  dest[1] = source[2];
  dest[2] = source[1];
  dest[3] = source[0];
#else // if BYTE_ORDER == BIG_ENDIAN
#ifdef BINN_ONLY_ALIGNED_ACCESS
  if (psource % 4 == 0){  // address aligned to 32 bit
    *pdest = *psource;
  } else {
    unsigned char *source = (unsigned char *) psource;
    unsigned char *dest = (unsigned char *) pdest;
    dest[0] = source[0];  // indexes are the same
    dest[1] = source[1];
    dest[2] = source[2];
    dest[3] = source[3];
  }
#else
  *pdest = *psource;
#endif
#endif
}

BINN_PRIVATE void copy_be64(u64 *pdest, u64 *psource) {
#if BYTE_ORDER == LITTLE_ENDIAN
  unsigned char *source = (unsigned char *) psource;
  unsigned char *dest = (unsigned char *) pdest;
  int i;
  for (i=0; i < 8; i++) {
    dest[i] = source[7-i];
  }
#else // if BYTE_ORDER == BIG_ENDIAN
#ifdef BINN_ONLY_ALIGNED_ACCESS
  if (psource % 8 == 0){  // address aligned to 64 bit
    *pdest = *psource;
  } else {
    unsigned char *source = (unsigned char *) psource;
    unsigned char *dest = (unsigned char *) pdest;
    int i;
    for (i=0; i < 8; i++) {
      dest[i] = source[i];  // indexes are the same
    }
  }
#else
  *pdest = *psource;
#endif
#endif
}

/***************************************************************************/

#ifndef WIN32
#define stricmp strcasecmp
#define strnicmp strncasecmp
#endif

/***************************************************************************/

void APIENTRY binn_set_alloc_functions(void* (*new_malloc)(size_t), void* (*new_realloc)(void*,size_t), void (*new_free)(void*)) {

  malloc_fn = new_malloc;
  realloc_fn = new_realloc;
  free_fn = new_free;

}

/***************************************************************************/

BINN_PRIVATE void check_alloc_functions() {

  if (malloc_fn == 0) malloc_fn = &mpmalloc;
  if (realloc_fn == 0) realloc_fn = &mprealloc;
  if (free_fn == 0) free_fn = &mpfree;

}

/***************************************************************************/

BINN_PRIVATE void * binn_malloc(int size) {
  check_alloc_functions();
  return malloc_fn(size);
}

/***************************************************************************/

BINN_PRIVATE void * binn_memdup(void *src, int size) {
  void *dest;

  if (src == NULL || size <= 0) return NULL;
  dest = binn_malloc(size);
  if (dest == NULL) return NULL;
  memcpy(dest, src, size);
  return dest;

}

/***************************************************************************/

BINN_PRIVATE size_t strlen2(char *str) {

  if (str == NULL) return 0;
  return strlen(str);

}

/***************************************************************************/

int APIENTRY binn_create_type(int storage_type, int data_type_index) {
  if (data_type_index < 0) return -1;
  if ((storage_type < BINN_STORAGE_MIN) || (storage_type > BINN_STORAGE_MAX)) return -1;
  if (data_type_index < 16)
    return storage_type | data_type_index;
  else if (data_type_index < 4096) {
    storage_type |= BINN_STORAGE_HAS_MORE;
    storage_type <<= 8;
    data_type_index >>= 4;
    return storage_type | data_type_index;
  } else
    return -1;
}

/***************************************************************************/

BOOL APIENTRY binn_get_type_info(int long_type, int *pstorage_type, int *pextra_type) {
  int storage_type, extra_type;
  BOOL retval=TRUE;

again:

  if (long_type < 0) {
    goto loc_invalid;
  } else if (long_type <= 0xff) {
    storage_type = long_type & BINN_STORAGE_MASK;
    extra_type = long_type & BINN_TYPE_MASK;
  } else if (long_type <= 0xffff) {
    storage_type = long_type & BINN_STORAGE_MASK16;
    storage_type >>= 8;
    extra_type = long_type & BINN_TYPE_MASK16;
    extra_type >>= 4;
  } else if (long_type & BINN_STORAGE_VIRTUAL) {
    //storage_type = BINN_STORAGE_VIRTUAL;
    //extra_type = xxx;
    long_type &= 0xffff;
    goto again;
  } else {
loc_invalid:
    storage_type = -1;
    extra_type = -1;
    retval = FALSE;
  }

  if (pstorage_type) *pstorage_type = storage_type;
  if (pextra_type) *pextra_type = extra_type;

  return retval;

}

/***************************************************************************/

BOOL APIENTRY binn_create(binn *item, int type, int size, void *pointer) {
  BOOL retval=FALSE;

  switch (type) {
    case BINN_LIST:
    case BINN_MAP:
    case BINN_OBJECT:
      break;
    default:
      goto loc_exit;
  }

  if ((item == NULL) || (size < 0)) goto loc_exit;
  if (size < MIN_BINN_SIZE) {
    if (pointer) goto loc_exit;
    else size = 0;
  }

  memset(item, 0, sizeof(binn));

  if (pointer) {
    item->pre_allocated = TRUE;
    item->pbuf = pointer;
    item->alloc_size = size;
  } else {
    item->pre_allocated = FALSE;
    if (size == 0) size = CHUNK_SIZE;
    pointer = binn_malloc(size);
    if (pointer == 0) return INVALID_BINN;
    item->pbuf = pointer;
    item->alloc_size = size;
  }

  item->header = BINN_MAGIC;
  //item->allocated = FALSE;   -- already zeroed
  item->writable = TRUE;
  item->used_size = MAX_BINN_HEADER;  // save space for the header
  item->type = type;
  //item->count = 0;           -- already zeroed
  item->dirty = TRUE;          // the header is not written to the buffer

  retval = TRUE;

loc_exit:
  return retval;

}

/***************************************************************************/

binn * APIENTRY binn_new(int type, int size, void *pointer) {
  binn *item;

  item = (binn*) binn_malloc(sizeof(binn));

  if (binn_create(item, type, size, pointer) == FALSE) {
    free_fn(item);
    return NULL;
  }

  item->allocated = TRUE;
  return item;

}

/*************************************************************************************/

BOOL APIENTRY binn_create_list(binn *list) {

  return binn_create(list, BINN_LIST, 0, NULL);

}

/*************************************************************************************/

BOOL APIENTRY binn_create_map(binn *map) {

  return binn_create(map, BINN_MAP, 0, NULL);

}

/*************************************************************************************/

BOOL APIENTRY binn_create_object(binn *object) {

  return binn_create(object, BINN_OBJECT, 0, NULL);

}

/***************************************************************************/

binn * APIENTRY binn_list() {
  return binn_new(BINN_LIST, 0, 0);
}

/***************************************************************************/

binn * APIENTRY binn_map() {
  return binn_new(BINN_MAP, 0, 0);
}

/***************************************************************************/

binn * APIENTRY binn_object() {
  return binn_new(BINN_OBJECT, 0, 0);
}

/*************************************************************************************/

BOOL APIENTRY binn_load(void *data, binn *value) {

  if ((data == NULL) || (value == NULL)) return FALSE;
  memset(value, 0, sizeof(binn));
  value->header = BINN_MAGIC;
  //value->allocated = FALSE;  --  already zeroed
  //value->writable = FALSE;

  if (binn_is_valid(data, &value->type, &value->count, &value->size) == FALSE) return FALSE;
  value->ptr = data;
  return TRUE;

}

/*************************************************************************************/

binn * APIENTRY binn_open(void *data) {
  binn *item;

  item = (binn*) binn_malloc(sizeof(binn));

  if (binn_load(data, item) == FALSE) {
    free_fn(item);
    return NULL;
  }

  item->allocated = TRUE;
  return item;

}

/***************************************************************************/

BINN_PRIVATE int binn_get_ptr_type(void *ptr) {

  if (ptr == NULL) return 0;

  switch (*(unsigned int *)ptr) {
  case BINN_MAGIC:
    return BINN_STRUCT;
  default:
    return BINN_BUFFER;
  }

}

/***************************************************************************/

BOOL APIENTRY binn_is_struct(void *ptr) {

  if (ptr == NULL) return FALSE;

  if ((*(unsigned int *)ptr) == BINN_MAGIC) {
    return TRUE;
  } else {
    return FALSE;
  }

}

/***************************************************************************/

BINN_PRIVATE int CalcAllocation(int needed_size, int alloc_size) {
  int calc_size;

  calc_size = alloc_size;
  while (calc_size < needed_size) {
    calc_size <<= 1;  // same as *= 2
    //calc_size += CHUNK_SIZE;  -- this is slower than the above line, because there are more reallocations
  }
  return calc_size;

}

/***************************************************************************/

BINN_PRIVATE BOOL CheckAllocation(binn *item, int add_size) {
  int  alloc_size;
  void *ptr;

  if (item->used_size + add_size > item->alloc_size) {
    if (item->pre_allocated) return FALSE;
    alloc_size = CalcAllocation(item->used_size + add_size, item->alloc_size);
    ptr = realloc_fn(item->pbuf, alloc_size);
    if (ptr == NULL) return FALSE;
    item->pbuf = ptr;
    item->alloc_size = alloc_size;
  }

  return TRUE;

}

/***************************************************************************/

#if BYTE_ORDER == BIG_ENDIAN

BINN_PRIVATE int get_storage_size(int storage_type) {

  switch (storage_type) {
  case BINN_STORAGE_NOBYTES:
    return 0;
  case BINN_STORAGE_BYTE:
    return 1;
  case BINN_STORAGE_WORD:
    return 2;
  case BINN_STORAGE_DWORD:
    return 4;
  case BINN_STORAGE_QWORD:
    return 8;
  default:
    return 0;
  }

}

#endif

/***************************************************************************/

BINN_PRIVATE unsigned char * AdvanceDataPos(unsigned char *p, unsigned char *plimit) {
  unsigned char byte;
  int  storage_type, DataSize;

  if (p > plimit) return 0;

  byte = *p; p++;
  storage_type = byte & BINN_STORAGE_MASK;
  if (byte & BINN_STORAGE_HAS_MORE) p++;

  switch (storage_type) {
  case BINN_STORAGE_NOBYTES:
    //p += 0;
    break;
  case BINN_STORAGE_BYTE:
    p ++;
    break;
  case BINN_STORAGE_WORD:
    p += 2;
    break;
  case BINN_STORAGE_DWORD:
    p += 4;
    break;
  case BINN_STORAGE_QWORD:
    p += 8;
    break;
  case BINN_STORAGE_BLOB:
    if (p + sizeof(int) - 1 > plimit) return 0;
    copy_be32((u32*)&DataSize, (u32*)p);
    p += 4 + DataSize;
    break;
  case BINN_STORAGE_CONTAINER:
    if (p > plimit) return 0;
    DataSize = *((unsigned char*)p);
    if (DataSize & 0x80) {
      if (p + sizeof(int) - 1 > plimit) return 0;
      copy_be32((u32*)&DataSize, (u32*)p);
      DataSize &= 0x7FFFFFFF;
    }
    DataSize--;  // remove the type byte already added before
    p += DataSize;
    break;
  case BINN_STORAGE_STRING:
    if (p > plimit) return 0;
    DataSize = *((unsigned char*)p);
    if (DataSize & 0x80) {
      if (p + sizeof(int) - 1 > plimit) return 0;
      copy_be32((u32*)&DataSize, (u32*)p);
      DataSize &= 0x7FFFFFFF;
      p+=4;
    } else {
      p++;
    }
    p += DataSize;
    p++;  // null terminator.
    break;
  default:
    return 0;
  }

  if (p > plimit) return 0;

  return p;

}

/***************************************************************************/

BINN_PRIVATE unsigned char * SearchForID(unsigned char *p, int header_size, int size, int numitems, int id) {
  unsigned char *plimit, *base;
  int  i, int32;

  base = p;
  plimit = p + size - 1;
  p += header_size;

  // search for the ID in all the arguments.
  for (i = 0; i < numitems; i++) {
    copy_be32((u32*)&int32, (u32*)p);
    p += 4;
    if (p > plimit) break;
    // Compare if the IDs are equal.
    if (int32 == id) return p;
    // xxx
    p = AdvanceDataPos(p, plimit);
    if ((p == 0) || (p < base)) break;
  }

  return NULL;

}

/***************************************************************************/

BINN_PRIVATE unsigned char * SearchForKey(unsigned char *p, int header_size, int size, int numitems, char *key) {
  unsigned char len, *plimit, *base;
  int  i, keylen;

  base = p;
  plimit = p + size - 1;
  p += header_size;

  keylen = strlen(key);

  // search for the key in all the arguments.
  for (i = 0; i < numitems; i++) {
    len = *((unsigned char *)p);
    p++;
    if (p > plimit) break;
    // Compare if the strings are equal.
    if (len > 0) {
      if (strnicmp((char*)p, key, len) == 0) {   // note that there is no null terminator here
        if (keylen == len) {
          p += len;
          return p;
        }
      }
      p += len;
      if (p > plimit) break;
    } else if (len == keylen) {   // in the case of empty string: ""
      return p;
    }
    // xxx
    p = AdvanceDataPos(p, plimit);
    if ((p == 0) || (p < base)) break;
  }

  return NULL;

}

/***************************************************************************/

BINN_PRIVATE BOOL AddValue(binn *item, int type, void *pvalue, int size);

/***************************************************************************/

BINN_PRIVATE BOOL binn_list_add_raw(binn *item, int type, void *pvalue, int size) {

  if ((item == NULL) || (item->type != BINN_LIST) || (item->writable == FALSE)) return FALSE;

  //if (CheckAllocation(item, 4) == FALSE) return FALSE;  // 4 bytes used for data_store and data_format.

  if (AddValue(item, type, pvalue, size) == FALSE) return FALSE;

  item->count++;

  return TRUE;

}

/***************************************************************************/

BINN_PRIVATE BOOL binn_object_set_raw(binn *item, char *key, int type, void *pvalue, int size) {
  unsigned char *p, len;
  int int32;

  if ((item == NULL) || (item->type != BINN_OBJECT) || (item->writable == FALSE)) return FALSE;

  if (key == NULL) return FALSE;
  int32 = strlen(key);
  if (int32 > 255) return FALSE;

  // is the key already in it?
  p = SearchForKey((unsigned char*)item->pbuf, MAX_BINN_HEADER, item->used_size, item->count, key);
  if (p) return FALSE;

  // start adding it

  if (CheckAllocation(item, 1 + int32) == FALSE) return FALSE;  // bytes used for the key size and the key itself.

  p = ((unsigned char *) item->pbuf) + item->used_size;
  len = int32;
  *p = len;
  p++;
  memcpy(p, key, int32);
  int32++;  // now contains the strlen + 1 byte for the len
  item->used_size += int32;

  if (AddValue(item, type, pvalue, size) == FALSE) {
    item->used_size -= int32;
    return FALSE;
  }

  item->count++;

  return TRUE;

}

/***************************************************************************/

BINN_PRIVATE BOOL binn_map_set_raw(binn *item, int id, int type, void *pvalue, int size) {
  unsigned char *p;

  if ((item == NULL) || (item->type != BINN_MAP) || (item->writable == FALSE)) return FALSE;

  // is the ID already in it?
  p = SearchForID((unsigned char*)item->pbuf, MAX_BINN_HEADER, item->used_size, item->count, id);
  if (p) return FALSE;

  // start adding it

  if (CheckAllocation(item, 4) == FALSE) return FALSE;  // 4 bytes used for the id.

  p = ((unsigned char *) item->pbuf) + item->used_size;
  copy_be32((u32*)p, (u32*)&id);
  item->used_size += 4;

  if (AddValue(item, type, pvalue, size) == FALSE) {
    item->used_size -= 4;
    return FALSE;
  }

  item->count++;

  return TRUE;

}

/***************************************************************************/

BINN_PRIVATE void * compress_int(int *pstorage_type, int *ptype, void *psource) {
  int storage_type, storage_type2, type, type2=0;
  int64  vint;
  uint64 vuint;
  char *pvalue;
#if BYTE_ORDER == BIG_ENDIAN
  int size1, size2;
#endif

  storage_type = *pstorage_type;
  if (storage_type == BINN_STORAGE_BYTE) return psource;

  type = *ptype;

  switch (type) {
  case BINN_INT64:
    vint = *(int64*)psource;
    goto loc_signed;
  case BINN_INT32:
    vint = *(int*)psource;
    goto loc_signed;
  case BINN_INT16:
    vint = *(short*)psource;
    goto loc_signed;
  case BINN_UINT64:
    vuint = *(uint64*)psource;
    goto loc_positive;
  case BINN_UINT32:
    vuint = *(unsigned int*)psource;
    goto loc_positive;
  case BINN_UINT16:
    vuint = *(unsigned short*)psource;
    goto loc_positive;
  }

loc_signed:

  if (vint >= 0) {
    vuint = vint;
    goto loc_positive;
  }

//loc_negative:

  if (vint >= INT8_MIN) {
    type2 = BINN_INT8;
  } else
  if (vint >= INT16_MIN) {
    type2 = BINN_INT16;
  } else
  if (vint >= INT32_MIN) {
    type2 = BINN_INT32;
  }
  goto loc_exit;

loc_positive:

  if (vuint <= UINT8_MAX) {
    type2 = BINN_UINT8;
  } else
  if (vuint <= UINT16_MAX) {
    type2 = BINN_UINT16;
  } else
  if (vuint <= UINT32_MAX) {
    type2 = BINN_UINT32;
  }

loc_exit:

  pvalue = (char *) psource;

  if ((type2) && (type2 != type)) {
    *ptype = type2;
    storage_type2 = binn_get_write_storage(type2);
    *pstorage_type = storage_type2;
#if BYTE_ORDER == BIG_ENDIAN
    size1 = get_storage_size(storage_type);
    size2 = get_storage_size(storage_type2);
    pvalue += (size1 - size2);
#endif
  }

  return pvalue;

}

/***************************************************************************/

BINN_PRIVATE int type_family(int type);

BINN_PRIVATE BOOL AddValue(binn *item, int type, void *pvalue, int size) {
  int int32, ArgSize, storage_type, extra_type;
  unsigned char *p;

  binn_get_type_info(type, &storage_type, &extra_type);

  if (pvalue == NULL) {
    switch (storage_type) {
      case BINN_STORAGE_NOBYTES:
        break;
      case BINN_STORAGE_BLOB:
      case BINN_STORAGE_STRING:
        if (size == 0) break; // the 2 above are allowed to have 0 length
      default:
        return FALSE;
    }
  }

  if ((type_family(type) == BINN_FAMILY_INT) && (item->disable_int_compression == FALSE))
    pvalue = compress_int(&storage_type, &type, pvalue);

  switch (storage_type) {
    case BINN_STORAGE_NOBYTES:
      size = 0;
      ArgSize = size;
      break;
    case BINN_STORAGE_BYTE:
      size = 1;
      ArgSize = size;
      break;
    case BINN_STORAGE_WORD:
      size = 2;
      ArgSize = size;
      break;
    case BINN_STORAGE_DWORD:
      size = 4;
      ArgSize = size;
      break;
    case BINN_STORAGE_QWORD:
      size = 8;
      ArgSize = size;
      break;
    case BINN_STORAGE_BLOB:
      if (size < 0) return FALSE;
      //if (size == 0) ...
      ArgSize = size + 4;
      break;
    case BINN_STORAGE_STRING:
      if (size < 0) return FALSE;
      if (size == 0) size = strlen2( (char *) pvalue);
      ArgSize = size + 5; // at least this size
      break;
    case BINN_STORAGE_CONTAINER:
      if (size <= 0) return FALSE;
      ArgSize = size;
      break;
    default:
      return FALSE;
  }

  ArgSize += 2;  // at least 2 bytes used for data_type.
  if (CheckAllocation(item, ArgSize) == FALSE) return FALSE;

  // Gets the pointer to the next place in buffer
  p = ((unsigned char *) item->pbuf) + item->used_size;

  // If the data is not a container, store the data type
  if (storage_type != BINN_STORAGE_CONTAINER) {
    if (type > 255) {
      u16 type16 = type;
      copy_be16((u16*)p, (u16*)&type16);
      p += 2;
      item->used_size += 2;
    } else {
      *p = type;
      p++;
      item->used_size++;
    }
  }

  switch (storage_type) {
    case BINN_STORAGE_NOBYTES:
      // Nothing to do.
      break;
    case BINN_STORAGE_BYTE:
      *((char *) p) = *((char *) pvalue);
      item->used_size += 1;
      break;
    case BINN_STORAGE_WORD:
      copy_be16((u16*)p, (u16*)pvalue);
      item->used_size += 2;
      break;
    case BINN_STORAGE_DWORD:
      copy_be32((u32*)p, (u32*)pvalue);
      item->used_size += 4;
      break;
    case BINN_STORAGE_QWORD:
      copy_be64((u64*)p, (u64*)pvalue);
      item->used_size += 8;
      break;
    case BINN_STORAGE_BLOB:
      copy_be32((u32*)p, (u32*)&size);
      p += 4;
      memcpy(p, pvalue, size);
      item->used_size += 4 + size;
      break;
    case BINN_STORAGE_STRING:
      if (size > 127) {
        int32 = size | 0x80000000;
        copy_be32((u32*)p, (u32*)&int32);
        p += 4;
        item->used_size += 4;
      } else {
        *((unsigned char *) p) = size;
        p++;
        item->used_size++;
      }
      memcpy(p, pvalue, size);
      p += size;
      *((char *) p) = (char) 0;
      size++;  // null terminator
      item->used_size += size;
      break;
    case BINN_STORAGE_CONTAINER:
      memcpy(p, pvalue, size);
      item->used_size += size;
      break;
  }

  item->dirty = TRUE;

  return TRUE;
}

/***************************************************************************/

BINN_PRIVATE BOOL binn_save_header(binn *item) {
  unsigned char byte, *p;
  int int32, size;

  if (item == NULL) return FALSE;

#ifndef BINN_DISABLE_SMALL_HEADER

  p = ((unsigned char *) item->pbuf) + MAX_BINN_HEADER;
  size = item->used_size - MAX_BINN_HEADER + 3;  // at least 3 bytes for the header

  // write the count
  if (item->count > 127) {
    p -= 4;
    size += 3;
    int32 = item->count | 0x80000000;
    copy_be32((u32*)p, (u32*)&int32);
  } else {
    p--;
    *p = (unsigned char) item->count;
  }

  // write the size
  if (size > 127) {
    p -= 4;
    size += 3;
    int32 = size | 0x80000000;
    copy_be32((u32*)p, (u32*)&int32);
  } else {
    p--;
    *p = (unsigned char) size;
  }

  // write the type.
  p--;
  *p = (unsigned char) item->type;

  // set the values
  item->ptr = p;
  item->size = size;

  UNUSED(byte);

#else

  p = (unsigned char *) item->pbuf;

  // write the type.
  byte = item->type;
  *p = byte; p++;
  // write the size
  int32 = item->used_size | 0x80000000;
  copy_be32((u32*)p, (u32*)&int32);
  p+=4;
  // write the count
  int32 = item->count | 0x80000000;
  copy_be32((u32*)p, (u32*)&int32);

  item->ptr = item->pbuf;
  item->size = item->used_size;

#endif

  item->dirty = FALSE;

  return TRUE;

}

/***************************************************************************/

void APIENTRY binn_free(binn *item) {

  if (item == NULL) return;

  if ((item->writable) && (item->pre_allocated == FALSE)) {
    free_fn(item->pbuf);
  }

  if (item->freefn) item->freefn(item->ptr);

  if (item->allocated) {
    free_fn(item);
  } else {
    memset(item, 0, sizeof(binn));
    item->header = BINN_MAGIC;
  }

}

/***************************************************************************/
// free the binn structure but keeps the binn buffer allocated, returning a pointer to it. use the free function to release the buffer later
void * APIENTRY binn_release(binn *item) {
  void *data;

  if (item == NULL) return NULL;

  data = binn_ptr(item);

  if (data > item->pbuf) {
    memmove(item->pbuf, data, item->size);
    data = item->pbuf;
  }

  if (item->allocated) {
    free_fn(item);
  } else {
    memset(item, 0, sizeof(binn));
    item->header = BINN_MAGIC;
  }

  return data;

}

/***************************************************************************/

BINN_PRIVATE BOOL IsValidBinnHeader(void *pbuf, int *ptype, int *pcount, int *psize, int *pheadersize) {
  unsigned char byte, *p, *plimit=0;
  int int32, type, size, count;

  if (pbuf == NULL) return FALSE;

  p = (unsigned char *) pbuf;

  if (psize && *psize > 0) {
    plimit = p + *psize - 1;
  }

  // get the type
  byte = *p; p++;
  if ((byte & BINN_STORAGE_MASK) != BINN_STORAGE_CONTAINER) return FALSE;
  if (byte & BINN_STORAGE_HAS_MORE) return FALSE;
  type = byte;

  switch (type) {
    case BINN_LIST:
    case BINN_MAP:
    case BINN_OBJECT:
      break;
    default:
      return FALSE;
  }

  // get the size
  if (plimit && p > plimit) return FALSE;
  int32 = *((unsigned char*)p);
  if (int32 & 0x80) {
    if (plimit && p + sizeof(int) - 1 > plimit) return FALSE;
    copy_be32((u32*)&int32, (u32*)p);
    int32 &= 0x7FFFFFFF;
    p+=4;
  } else {
    p++;
  }
  size = int32;

  // get the count
  if (plimit && p > plimit) return FALSE;
  int32 = *((unsigned char*)p);
  if (int32 & 0x80) {
    if (plimit && p + sizeof(int) - 1 > plimit) return FALSE;
    copy_be32((u32*)&int32, (u32*)p);
    int32 &= 0x7FFFFFFF;
    p+=4;
  } else {
    p++;
  }
  count = int32;

#if 0
  // get the size
  copy_be32((u32*)&size, (u32*)p);
  size &= 0x7FFFFFFF;
  p+=4;

  // get the count
  copy_be32((u32*)&count, (u32*)p);
  count &= 0x7FFFFFFF;
  p+=4;
#endif

  if ((size < MIN_BINN_SIZE) || (count < 0)) return FALSE;

  // return the values
  if (ptype)  *ptype  = type;
  if (pcount) *pcount = count;
  if (psize && *psize==0) *psize = size;
  if (pheadersize) *pheadersize = (int) (p - (unsigned char*)pbuf);
  return TRUE;
}

/***************************************************************************/

BINN_PRIVATE int binn_buf_type(void *pbuf) {
  int  type;

  if (!IsValidBinnHeader(pbuf, &type, NULL, NULL, NULL)) return INVALID_BINN;

  return type;

}

/***************************************************************************/

BINN_PRIVATE int binn_buf_count(void *pbuf) {
  int  nitems;

  if (!IsValidBinnHeader(pbuf, NULL, &nitems, NULL, NULL)) return 0;

  return nitems;

}

/***************************************************************************/

BINN_PRIVATE int binn_buf_size(void *pbuf) {
  int  size=0;

  if (!IsValidBinnHeader(pbuf, NULL, NULL, &size, NULL)) return 0;

  return size;

}

/***************************************************************************/

void * APIENTRY binn_ptr(void *ptr) {
  binn *item;

  switch (binn_get_ptr_type(ptr)) {
  case BINN_STRUCT:
    item = (binn*) ptr;
    if (item->writable && item->dirty) {
      binn_save_header(item);
    }
    return item->ptr;
  case BINN_BUFFER:
    return ptr;
  default:
    return NULL;
  }

}

/***************************************************************************/

int APIENTRY binn_size(void *ptr) {
  binn *item;

  switch (binn_get_ptr_type(ptr)) {
  case BINN_STRUCT:
    item = (binn*) ptr;
    if (item->writable && item->dirty) {
      binn_save_header(item);
    }
    return item->size;
  case BINN_BUFFER:
    return binn_buf_size(ptr);
  default:
    return 0;
  }

}

/***************************************************************************/

int APIENTRY binn_type(void *ptr) {
  binn *item;

  switch (binn_get_ptr_type(ptr)) {
  case BINN_STRUCT:
    item = (binn*) ptr;
    return item->type;
  case BINN_BUFFER:
    return binn_buf_type(ptr);
  default:
    return -1;
  }

}

/***************************************************************************/

int APIENTRY binn_count(void *ptr) {
  binn *item;

  switch (binn_get_ptr_type(ptr)) {
  case BINN_STRUCT:
    item = (binn*) ptr;
    return item->count;
  case BINN_BUFFER:
    return binn_buf_count(ptr);
  default:
    return -1;
  }

}

/***************************************************************************/

BOOL APIENTRY binn_is_valid_ex(void *ptr, int *ptype, int *pcount, int *psize) {
  int  i, type, count, size, header_size;
  unsigned char *p, *plimit, *base, len;
  void *pbuf;

  pbuf = binn_ptr(ptr);
  if (pbuf == NULL) return FALSE;

  // is there an informed size?
  if (psize && *psize > 0) {
    size = *psize;
  } else {
    size = 0;
  }

  if (!IsValidBinnHeader(pbuf, &type, &count, &size, &header_size)) return FALSE;

  // is there an informed size?
  if (psize && *psize > 0) {
    // is it the same as the one in the buffer?
    if (size != *psize) return FALSE;
  }
  // is there an informed count?
  if (pcount && *pcount > 0) {
    // is it the same as the one in the buffer?
    if (count != *pcount) return FALSE;
  }
  // is there an informed type?
  if (ptype && *ptype != 0) {
    // is it the same as the one in the buffer?
    if (type != *ptype) return FALSE;
  }

  // it could compare the content size with the size informed on the header

  p = (unsigned char *)pbuf;
  base = p;
  plimit = p + size;

  p += header_size;

  // process all the arguments.
  for (i = 0; i < count; i++) {
    switch (type) {
      case BINN_OBJECT:
        // gets the string size (argument name)
        len = *p;
        p++;
        //if (len == 0) goto Invalid;
        // increment the used space
        p += len;
        break;
      case BINN_MAP:
        // increment the used space
        p += 4;
        break;
      //case BINN_LIST:
      //  break;
    }
    // xxx
    p = AdvanceDataPos(p, plimit);
    if ((p == 0) || (p < base)) goto Invalid;
  }

  if (ptype  && *ptype==0)  *ptype  = type;
  if (pcount && *pcount==0) *pcount = count;
  if (psize  && *psize==0)  *psize  = size;
  return TRUE;

Invalid:
  return FALSE;

}

/***************************************************************************/

BOOL APIENTRY binn_is_valid(void *ptr, int *ptype, int *pcount, int *psize) {

  if (ptype)  *ptype  = 0;
  if (pcount) *pcount = 0;
  if (psize)  *psize  = 0;

  return binn_is_valid_ex(ptr, ptype, pcount, psize);

}

/***************************************************************************/
/*** INTERNAL FUNCTIONS ****************************************************/
/***************************************************************************/

BINN_PRIVATE BOOL GetValue(unsigned char *p, binn *value) {
  unsigned char byte;
  int   data_type, storage_type;  //, extra_type;
  int   DataSize;
  void *p2;

  if (value == NULL) return FALSE;
  memset(value, 0, sizeof(binn));
  value->header = BINN_MAGIC;
  //value->allocated = FALSE;  --  already zeroed
  //value->writable = FALSE;

  // saves for use with BINN_STORAGE_CONTAINER
  p2 = p;

  // read the data type
  byte = *p; p++;
  storage_type = byte & BINN_STORAGE_MASK;
  if (byte & BINN_STORAGE_HAS_MORE) {
    data_type = byte << 8;
    byte = *p; p++;
    data_type |= byte;
    //extra_type = data_type & BINN_TYPE_MASK16;
  } else {
    data_type = byte;
    //extra_type = byte & BINN_TYPE_MASK;
  }

  //value->storage_type = storage_type;
  value->type = data_type;

  switch (storage_type) {
  case BINN_STORAGE_NOBYTES:
    break;
  case BINN_STORAGE_BYTE:
    value->vuint8 = *((unsigned char *) p);
    value->ptr = p;   //value->ptr = &value->vuint8;
    break;
  case BINN_STORAGE_WORD:
    copy_be16((u16*)&value->vint16, (u16*)p);
    value->ptr = &value->vint16;
    break;
  case BINN_STORAGE_DWORD:
    copy_be32((u32*)&value->vint32, (u32*)p);
    value->ptr = &value->vint32;
    break;
  case BINN_STORAGE_QWORD:
    copy_be64((u64*)&value->vint64, (u64*)p);
    value->ptr = &value->vint64;
    break;
  case BINN_STORAGE_BLOB:
    copy_be32((u32*)&value->size, (u32*)p);
    p+=4;
    value->ptr = p;
    break;
  case BINN_STORAGE_CONTAINER:
    value->ptr = p2;  // <-- it returns the pointer to the container, not the data
    if (IsValidBinnHeader(p2, NULL, &value->count, &value->size, NULL) == FALSE) return FALSE;
    break;
  case BINN_STORAGE_STRING:
    DataSize = *((unsigned char*)p);
    if (DataSize & 0x80) {
      copy_be32((u32*)&DataSize, (u32*)p);
      DataSize &= 0x7FFFFFFF;
      p+=4;
    } else {
      p++;
    }
    value->size = DataSize;
    value->ptr = p;
    break;
  default:
    return FALSE;
  }

  // convert the returned value, if needed

  switch (value->type) {
    case BINN_TRUE:
      value->type = BINN_BOOL;
      value->vbool = TRUE;
      value->ptr = &value->vbool;
      break;
    case BINN_FALSE:
      value->type = BINN_BOOL;
      value->vbool = FALSE;
      value->ptr = &value->vbool;
      break;
#ifdef BINN_EXTENDED
    case BINN_SINGLE_STR:
      value->type = BINN_SINGLE;
      value->vfloat = (float) atof((const char*)value->ptr);  // converts from string to double, and then to float
      value->ptr = &value->vfloat;
      break;
    case BINN_DOUBLE_STR:
      value->type = BINN_DOUBLE;
      value->vdouble = atof((const char*)value->ptr);  // converts from string to double
      value->ptr = &value->vdouble;
      break;
#endif
    /*
    case BINN_DECIMAL:
    case BINN_CURRENCYSTR:
    case BINN_DATE:
    case BINN_DATETIME:
    case BINN_TIME:
    */
  }

  return TRUE;

}

/***************************************************************************/

#if BYTE_ORDER == LITTLE_ENDIAN

// on little-endian devices we store the value so we can return a pointer to integers.
// it's valid only for single-threaded apps. multi-threaded apps must use the _get_ functions instead.

binn local_value;

BINN_PRIVATE void * store_value(binn *value) {

  memcpy(&local_value, value, sizeof(binn));

  switch (binn_get_read_storage(value->type)) {
  case BINN_STORAGE_NOBYTES:
    // return a valid pointer
  case BINN_STORAGE_WORD:
  case BINN_STORAGE_DWORD:
  case BINN_STORAGE_QWORD:
    return &local_value.vint32;  // returns the pointer to the converted value, from big-endian to little-endian
  }

  return value->ptr;   // returns from the on stack value to be thread-safe (for list, map, object, string and blob)

}

#endif

/***************************************************************************/
/*** READ FUNCTIONS ********************************************************/
/***************************************************************************/

BOOL APIENTRY binn_object_get_value(void *ptr, char *key, binn *value) {
  int type, count, size=0, header_size;
  unsigned char *p;

  ptr = binn_ptr(ptr);
  if ((ptr == 0) || (key == 0) || (value == 0)) return FALSE;

  // check the header
  if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) return FALSE;

  if (type != BINN_OBJECT) return FALSE;
  if (count == 0) return FALSE;

  p = (unsigned char *) ptr;
  p = SearchForKey(p, header_size, size, count, key);
  if (p == FALSE) return FALSE;

  return GetValue(p, value);

}

/***************************************************************************/

BOOL APIENTRY binn_map_get_value(void* ptr, int id, binn *value) {
  int type, count, size=0, header_size;
  unsigned char *p;

  ptr = binn_ptr(ptr);
  if ((ptr == 0) || (value == 0)) return FALSE;

  // check the header
  if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) return FALSE;

  if (type != BINN_MAP) return FALSE;
  if (count == 0) return FALSE;

  p = (unsigned char *) ptr;
  p = SearchForID(p, header_size, size, count, id);
  if (p == FALSE) return FALSE;

  return GetValue(p, value);

}

/***************************************************************************/

BOOL APIENTRY binn_list_get_value(void* ptr, int pos, binn *value) {
  int  i, type, count, size=0, header_size;
  unsigned char *p, *plimit, *base;

  ptr = binn_ptr(ptr);
  if ((ptr == 0) || (value == 0)) return FALSE;

  // check the header
  if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) return FALSE;

  if (type != BINN_LIST) return FALSE;
  if (count == 0) return FALSE;
  if ((pos <= 0) | (pos > count)) return FALSE;
  pos--;  // convert from base 1 to base 0

  p = (unsigned char *) ptr;
  base = p;
  plimit = p + size;
  p += header_size;

  for (i = 0; i < pos; i++) {
    p = AdvanceDataPos(p, plimit);
    if ((p == 0) || (p < base)) return FALSE;
  }

  return GetValue(p, value);

}

/***************************************************************************/
/*** READ PAIR BY POSITION *************************************************/
/***************************************************************************/

BINN_PRIVATE BOOL binn_read_pair(int expected_type, void *ptr, int pos, int *pid, char *pkey, binn *value) {
  int  type, count, size=0, header_size;
  int  i, int32, id, counter=0;
  unsigned char *p, *plimit, *base, *key, len;

  ptr = binn_ptr(ptr);

  // check the header
  if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) return FALSE;

  if ((type != expected_type) || (count == 0) || (pos < 1) || (pos > count)) return FALSE;

  p = (unsigned char *) ptr;
  base = p;
  plimit = p + size - 1;
  p += header_size;

  for (i = 0; i < count; i++) {
    switch (type) {
      case BINN_MAP:
        copy_be32((u32*)&int32, (u32*)p);
        p += 4;
        if (p > plimit) return FALSE;
        id = int32;
        break;
      case BINN_OBJECT:
        len = *((unsigned char *)p); p++;
        if (p > plimit) return FALSE;
        key = p;
        p += len;
        if (p > plimit) return FALSE;
        break;
    }
    counter++;
    if (counter == pos) goto found;
    //
    p = AdvanceDataPos(p, plimit);
    if ((p == 0) || (p < base)) return FALSE;
  }

  return FALSE;

found:

  switch (type) {
    case BINN_MAP:
      if (pid) *pid = id;
      break;
    case BINN_OBJECT:
      if (pkey) {
        memcpy(pkey, key, len);
        pkey[len] = 0;
      }
      break;
  }

  return GetValue(p, value);

}

/***************************************************************************/

BOOL APIENTRY binn_map_get_pair(void *ptr, int pos, int *pid, binn *value) {

  return binn_read_pair(BINN_MAP, ptr, pos, pid, NULL, value);

}

/***************************************************************************/

BOOL APIENTRY binn_object_get_pair(void *ptr, int pos, char *pkey, binn *value) {

  return binn_read_pair(BINN_OBJECT, ptr, pos, NULL, pkey, value);

}

/***************************************************************************/

binn * APIENTRY binn_map_pair(void *map, int pos, int *pid) {
  binn *value;

  value = (binn *) binn_malloc(sizeof(binn));

  if (binn_read_pair(BINN_MAP, map, pos, pid, NULL, value) == FALSE) {
    free_fn(value);
    return NULL;
  }

  value->allocated = TRUE;
  return value;

}

/***************************************************************************/

binn * APIENTRY binn_object_pair(void *obj, int pos, char *pkey) {
  binn *value;

  value = (binn *) binn_malloc(sizeof(binn));

  if (binn_read_pair(BINN_OBJECT, obj, pos, NULL, pkey, value) == FALSE) {
    free_fn(value);
    return NULL;
  }

  value->allocated = TRUE;
  return value;

}

/***************************************************************************/
/***************************************************************************/

void * APIENTRY binn_map_read_pair(void *ptr, int pos, int *pid, int *ptype, int *psize) {
  binn value;

  if (binn_map_get_pair(ptr, pos, pid, &value) == FALSE) return NULL;
  if (ptype) *ptype = value.type;
  if (psize) *psize = value.size;
#if BYTE_ORDER == LITTLE_ENDIAN
  return store_value(&value);
#else
  return value.ptr;
#endif

}

/***************************************************************************/

void * APIENTRY binn_object_read_pair(void *ptr, int pos, char *pkey, int *ptype, int *psize) {
  binn value;

  if (binn_object_get_pair(ptr, pos, pkey, &value) == FALSE) return NULL;
  if (ptype) *ptype = value.type;
  if (psize) *psize = value.size;
#if BYTE_ORDER == LITTLE_ENDIAN
  return store_value(&value);
#else
  return value.ptr;
#endif

}

/***************************************************************************/
/*** SEQUENTIAL READ FUNCTIONS *********************************************/
/***************************************************************************/

BOOL APIENTRY binn_iter_init(binn_iter *iter, void *ptr, int expected_type) {
  int  type, count, size=0, header_size;

  ptr = binn_ptr(ptr);
  if ((ptr == 0) || (iter == 0)) return FALSE;
  memset(iter, 0, sizeof(binn_iter));

  // check the header
  if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) return FALSE;

  if (type != expected_type) return FALSE;
  //if (count == 0) return FALSE;  -- should not be used

  iter->plimit = (unsigned char *)ptr + size - 1;
  iter->pnext = (unsigned char *)ptr + header_size;
  iter->count = count;
  iter->current = 0;
  iter->type = type;

  return TRUE;
}

/***************************************************************************/

BOOL APIENTRY binn_list_next(binn_iter *iter, binn *value) {
  unsigned char *pnow;

  if ((iter == 0) || (iter->pnext == 0) || (iter->pnext > iter->plimit) || (iter->current > iter->count) || (iter->type != BINN_LIST)) return FALSE;

  iter->current++;
  if (iter->current > iter->count) return FALSE;

  pnow = iter->pnext;
  iter->pnext = AdvanceDataPos(pnow, iter->plimit);
  if (iter->pnext != 0 && iter->pnext < pnow) return FALSE;

  return GetValue(pnow, value);

}

/***************************************************************************/

BINN_PRIVATE BOOL binn_read_next_pair(int expected_type, binn_iter *iter, int *pid, char *pkey, binn *value) {
  int  int32, id;
  unsigned char *p, *key;
  unsigned short len;

  if ((iter == 0) || (iter->pnext == 0) || (iter->pnext > iter->plimit) || (iter->current > iter->count) || (iter->type != expected_type)) return FALSE;

  iter->current++;
  if (iter->current > iter->count) return FALSE;

  p = iter->pnext;

  switch (expected_type) {
    case BINN_MAP:
      copy_be32((u32*)&int32, (u32*)p);
      p += 4;
      if (p > iter->plimit) return FALSE;
      id = int32;
      if (pid) *pid = id;
      break;
    case BINN_OBJECT:
      len = *((unsigned char *)p); p++;
      key = p;
      p += len;
      if (p > iter->plimit) return FALSE;
      if (pkey) {
        memcpy(pkey, key, len);
        pkey[len] = 0;
      }
      break;
  }

  iter->pnext = AdvanceDataPos(p, iter->plimit);
  if (iter->pnext != 0 && iter->pnext < p) return FALSE;

  return GetValue(p, value);

}

/***************************************************************************/

BOOL APIENTRY binn_map_next(binn_iter *iter, int *pid, binn *value) {

  return binn_read_next_pair(BINN_MAP, iter, pid, NULL, value);

}

/***************************************************************************/

BOOL APIENTRY binn_object_next(binn_iter *iter, char *pkey, binn *value) {

  return binn_read_next_pair(BINN_OBJECT, iter, NULL, pkey, value);

}

/***************************************************************************/
/***************************************************************************/

binn * APIENTRY binn_list_next_value(binn_iter *iter) {
  binn *value;

  value = (binn *) binn_malloc(sizeof(binn));

  if (binn_list_next(iter, value) == FALSE) {
    free_fn(value);
    return NULL;
  }

  value->allocated = TRUE;
  return value;

}

/***************************************************************************/

binn * APIENTRY binn_map_next_value(binn_iter *iter, int *pid) {
  binn *value;

  value = (binn *) binn_malloc(sizeof(binn));

  if (binn_map_next(iter, pid, value) == FALSE) {
    free_fn(value);
    return NULL;
  }

  value->allocated = TRUE;
  return value;

}

/***************************************************************************/

binn * APIENTRY binn_object_next_value(binn_iter *iter, char *pkey) {
  binn *value;

  value = (binn *) binn_malloc(sizeof(binn));

  if (binn_object_next(iter, pkey, value) == FALSE) {
    free_fn(value);
    return NULL;
  }

  value->allocated = TRUE;
  return value;

}

/***************************************************************************/
/***************************************************************************/

void * APIENTRY binn_list_read_next(binn_iter *iter, int *ptype, int *psize) {
  binn value;

  if (binn_list_next(iter, &value) == FALSE) return NULL;
  if (ptype) *ptype = value.type;
  if (psize) *psize = value.size;
#if BYTE_ORDER == LITTLE_ENDIAN
  return store_value(&value);
#else
  return value.ptr;
#endif

}

/***************************************************************************/

void * APIENTRY binn_map_read_next(binn_iter *iter, int *pid, int *ptype, int *psize) {
  binn value;

  if (binn_map_next(iter, pid, &value) == FALSE) return NULL;
  if (ptype) *ptype = value.type;
  if (psize) *psize = value.size;
#if BYTE_ORDER == LITTLE_ENDIAN
  return store_value(&value);
#else
  return value.ptr;
#endif

}

/***************************************************************************/

void * APIENTRY binn_object_read_next(binn_iter *iter, char *pkey, int *ptype, int *psize) {
  binn value;

  if (binn_object_next(iter, pkey, &value) == FALSE) return NULL;
  if (ptype) *ptype = value.type;
  if (psize) *psize = value.size;
#if BYTE_ORDER == LITTLE_ENDIAN
  return store_value(&value);
#else
  return value.ptr;
#endif

}

/*************************************************************************************/
/****** EXTENDED INTERFACE ***********************************************************/
/****** none of the functions above call the functions below *************************/
/*************************************************************************************/

int APIENTRY binn_get_write_storage(int type) {
  int storage_type;

  switch (type) {
    case BINN_SINGLE_STR:
    case BINN_DOUBLE_STR:
      return BINN_STORAGE_STRING;

    case BINN_BOOL:
      return BINN_STORAGE_NOBYTES;

    default:
      binn_get_type_info(type, &storage_type, NULL);
      return storage_type;
  }

}

/*************************************************************************************/

int APIENTRY binn_get_read_storage(int type) {
  int storage_type;

  switch (type) {
#ifdef BINN_EXTENDED
    case BINN_SINGLE_STR:
      return BINN_STORAGE_DWORD;
    case BINN_DOUBLE_STR:
      return BINN_STORAGE_QWORD;
#endif
    case BINN_BOOL:
    case BINN_TRUE:
    case BINN_FALSE:
      return BINN_STORAGE_DWORD;
    default:
      binn_get_type_info(type, &storage_type, NULL);
      return storage_type;
  }

}

/*************************************************************************************/

BINN_PRIVATE BOOL GetWriteConvertedData(int *ptype, void **ppvalue, int *psize) {
  int  type;
  float  f1;
  double d1;
  char pstr[128];

  UNUSED(pstr);
  UNUSED(d1);
  UNUSED(f1);

  type = *ptype;

  if (*ppvalue == NULL) {
    switch (type) {
      case BINN_NULL:
      case BINN_TRUE:
      case BINN_FALSE:
        break;
      case BINN_STRING:
      case BINN_BLOB:
        if (*psize == 0) break;
      default:
        return FALSE;
    }
  }

  switch (type) {
#ifdef BINN_EXTENDED
    case BINN_SINGLE:
      f1 = **(float**)ppvalue;
      d1 = f1;  // convert from float (32bits) to double (64bits)
      type = BINN_SINGLE_STR;
      goto conv_double;
    case BINN_DOUBLE:
      d1 = **(double**)ppvalue;
      type = BINN_DOUBLE_STR;
conv_double:
      // the '%.17e' is more precise than the '%g'
      snprintf(pstr, 127, "%.17e", d1);
      *ppvalue = pstr;
      *ptype = type;
      break;
#endif
    case BINN_DECIMAL:
    case BINN_CURRENCYSTR:
      /*
      if (binn_malloc_extptr(128) == NULL) return FALSE;
      snprintf(sptr, 127, "%E", **ppvalue);
      *ppvalue = sptr;
      */
      return TRUE;  //! temporary
      break;

    case BINN_DATE:
    case BINN_DATETIME:
    case BINN_TIME:
      return TRUE;  //! temporary
      break;

    case BINN_BOOL:
      if (**((BOOL**)ppvalue) == FALSE) {
        type = BINN_FALSE;
      } else {
        type = BINN_TRUE;
      }
      *ptype = type;
      break;

  }

  return TRUE;

}

/*************************************************************************************/

BINN_PRIVATE int type_family(int type)  {

  switch (type) {
    case BINN_LIST:
    case BINN_MAP:
    case BINN_OBJECT:
      return BINN_FAMILY_BINN;

    case BINN_INT8:
    case BINN_INT16:
    case BINN_INT32:
    case BINN_INT64:
    case BINN_UINT8:
    case BINN_UINT16:
    case BINN_UINT32:
    case BINN_UINT64:
      return BINN_FAMILY_INT;

    case BINN_FLOAT32:
    case BINN_FLOAT64:
    //case BINN_SINGLE:
    case BINN_SINGLE_STR:
    //case BINN_DOUBLE:
    case BINN_DOUBLE_STR:
      return BINN_FAMILY_FLOAT;

    case BINN_STRING:
    case BINN_HTML:
    case BINN_CSS:
    case BINN_XML:
    case BINN_JSON:
    case BINN_JAVASCRIPT:
      return BINN_FAMILY_STRING;

    case BINN_BLOB:
    case BINN_JPEG:
    case BINN_GIF:
    case BINN_PNG:
    case BINN_BMP:
      return BINN_FAMILY_BLOB;

    case BINN_DECIMAL:
    case BINN_CURRENCY:
    case BINN_DATE:
    case BINN_TIME:
    case BINN_DATETIME:
      return BINN_FAMILY_STRING;

    case BINN_BOOL:
      return BINN_FAMILY_BOOL;

    case BINN_NULL:
      return BINN_FAMILY_NULL;

    default:
      // if it wasn't found
      return BINN_FAMILY_NONE;
  }

}

/*************************************************************************************/

BINN_PRIVATE int int_type(int type)  {

  switch (type) {
  case BINN_INT8:
  case BINN_INT16:
  case BINN_INT32:
  case BINN_INT64:
    return BINN_SIGNED_INT;

  case BINN_UINT8:
  case BINN_UINT16:
  case BINN_UINT32:
  case BINN_UINT64:
    return BINN_UNSIGNED_INT;

  default:
    return 0;
  }

}

/*************************************************************************************/

BINN_PRIVATE BOOL copy_raw_value(void *psource, void *pdest, int data_store) {

  switch (data_store) {
  case BINN_STORAGE_NOBYTES:
    break;
  case BINN_STORAGE_BYTE:
    *((char *) pdest) = *(char *)psource;
    break;
  case BINN_STORAGE_WORD:
    *((short *) pdest) = *(short *)psource;
    break;
  case BINN_STORAGE_DWORD:
    *((int *) pdest) = *(int *)psource;
    break;
  case BINN_STORAGE_QWORD:
    *((uint64 *) pdest) = *(uint64 *)psource;
    break;
  case BINN_STORAGE_BLOB:
  case BINN_STORAGE_STRING:
  case BINN_STORAGE_CONTAINER:
    *((char **) pdest) = (char *)psource;
    break;
  default:
    return FALSE;
  }

  return TRUE;

}

/*************************************************************************************/

BINN_PRIVATE BOOL copy_int_value(void *psource, void *pdest, int source_type, int dest_type) {
  uint64 vuint64; int64 vint64;

  switch (source_type) {
  case BINN_INT8:
    vint64 = *(signed char *)psource;
    break;
  case BINN_INT16:
    vint64 = *(short *)psource;
    break;
  case BINN_INT32:
    vint64 = *(int *)psource;
    break;
  case BINN_INT64:
    vint64 = *(int64 *)psource;
    break;

  case BINN_UINT8:
    vuint64 = *(unsigned char *)psource;
    break;
  case BINN_UINT16:
    vuint64 = *(unsigned short *)psource;
    break;
  case BINN_UINT32:
    vuint64 = *(unsigned int *)psource;
    break;
  case BINN_UINT64:
    vuint64 = *(uint64 *)psource;
    break;

  default:
    return FALSE;
  }


  // copy from int64 to uint64, if possible

  if ((int_type(source_type) == BINN_UNSIGNED_INT) && (int_type(dest_type) == BINN_SIGNED_INT)) {
    if (vuint64 > INT64_MAX) return FALSE;
    vint64 = vuint64;
  } else if ((int_type(source_type) == BINN_SIGNED_INT) && (int_type(dest_type) == BINN_UNSIGNED_INT)) {
    if (vint64 < 0) return FALSE;
    vuint64 = vint64;
  }


  switch (dest_type) {
  case BINN_INT8:
    if ((vint64 < INT8_MIN) || (vint64 > INT8_MAX)) return FALSE;
    *(signed char *)pdest = (signed char) vint64;
    break;
  case BINN_INT16:
    if ((vint64 < INT16_MIN) || (vint64 > INT16_MAX)) return FALSE;
    *(short *)pdest = (short) vint64;
    break;
  case BINN_INT32:
    if ((vint64 < INT32_MIN) || (vint64 > INT32_MAX)) return FALSE;
    *(int *)pdest = (int) vint64;
    break;
  case BINN_INT64:
    *(int64 *)pdest = vint64;
    break;

  case BINN_UINT8:
    if (vuint64 > UINT8_MAX) return FALSE;
    *(unsigned char *)pdest = (unsigned char) vuint64;
    break;
  case BINN_UINT16:
    if (vuint64 > UINT16_MAX) return FALSE;
    *(unsigned short *)pdest = (unsigned short) vuint64;
    break;
  case BINN_UINT32:
    if (vuint64 > UINT32_MAX) return FALSE;
    *(unsigned int *)pdest = (unsigned int) vuint64;
    break;
  case BINN_UINT64:
    *(uint64 *)pdest = vuint64;
    break;

  default:
    return FALSE;
  }

  return TRUE;

}

/*************************************************************************************/

BINN_PRIVATE BOOL copy_float_value(void *psource, void *pdest, int source_type, int dest_type) {

  switch (source_type) {
  case BINN_FLOAT32:
    *(double *)pdest = *(float *)psource;
    break;
  case BINN_FLOAT64:
    *(float *)pdest = (float) *(double *)psource;
    break;
  default:
    return FALSE;
  }

  return TRUE;

}

/*************************************************************************************/

BINN_PRIVATE void zero_value(void *pvalue, int type) {
  //int size=0;

  switch (binn_get_read_storage(type)) {
  case BINN_STORAGE_NOBYTES:
    break;
  case BINN_STORAGE_BYTE:
    *((char *) pvalue) = 0;
    //size=1;
    break;
  case BINN_STORAGE_WORD:
    *((short *) pvalue) = 0;
    //size=2;
    break;
  case BINN_STORAGE_DWORD:
    *((int *) pvalue) = 0;
    //size=4;
    break;
  case BINN_STORAGE_QWORD:
    *((uint64 *) pvalue) = 0;
    //size=8;
    break;
  case BINN_STORAGE_BLOB:
  case BINN_STORAGE_STRING:
  case BINN_STORAGE_CONTAINER:
    *(char **)pvalue = NULL;
    break;
  }

  //if (size>0) memset(pvalue, 0, size);

}

/*************************************************************************************/

BINN_PRIVATE BOOL copy_value(void *psource, void *pdest, int source_type, int dest_type, int data_store) {

  if (type_family(source_type) != type_family(dest_type)) return FALSE;

  if ((type_family(source_type) == BINN_FAMILY_INT) && (source_type != dest_type)) {
    return copy_int_value(psource, pdest, source_type, dest_type);
  } else if ((type_family(source_type) == BINN_FAMILY_FLOAT) && (source_type != dest_type)) {
    return copy_float_value(psource, pdest, source_type, dest_type);
  } else {
    return copy_raw_value(psource, pdest, data_store);
  }

}

/*************************************************************************************/
/*** WRITE FUNCTIONS *****************************************************************/
/*************************************************************************************/

BOOL APIENTRY binn_list_add(binn *list, int type, void *pvalue, int size) {

  if (GetWriteConvertedData(&type, &pvalue, &size) == FALSE) return FALSE;

  return binn_list_add_raw(list, type, pvalue, size);

}

/*************************************************************************************/

BOOL APIENTRY binn_map_set(binn *map, int id, int type, void *pvalue, int size) {

  if (GetWriteConvertedData(&type, &pvalue, &size) == FALSE) return FALSE;

  return binn_map_set_raw(map, id, type, pvalue, size);

}

/*************************************************************************************/

BOOL APIENTRY binn_object_set(binn *obj, char *key, int type, void *pvalue, int size) {

  if (GetWriteConvertedData(&type, &pvalue, &size) == FALSE) return FALSE;

  return binn_object_set_raw(obj, key, type, pvalue, size);

}

/*************************************************************************************/

// this function is used by the wrappers
BOOL APIENTRY binn_add_value(binn *item, int binn_type, int id, char *name, int type, void *pvalue, int size) {

  switch (binn_type) {
    case BINN_LIST:
      return binn_list_add(item, type, pvalue, size);
    case BINN_MAP:
      return binn_map_set(item, id, type, pvalue, size);
    case BINN_OBJECT:
      return binn_object_set(item, name, type, pvalue, size);
    default:
      return FALSE;
  }

}

/*************************************************************************************/
/*************************************************************************************/

BOOL APIENTRY binn_list_add_new(binn *list, binn *value) {
  BOOL retval;

  retval = binn_list_add_value(list, value);
  if (value) free_fn(value);
  return retval;

}

/*************************************************************************************/

BOOL APIENTRY binn_map_set_new(binn *map, int id, binn *value) {
  BOOL retval;

  retval = binn_map_set_value(map, id, value);
  if (value) free_fn(value);
  return retval;

}

/*************************************************************************************/

BOOL APIENTRY binn_object_set_new(binn *obj, char *key, binn *value) {
  BOOL retval;

  retval = binn_object_set_value(obj, key, value);
  if (value) free_fn(value);
  return retval;

}

/*************************************************************************************/
/*** READ FUNCTIONS ******************************************************************/
/*************************************************************************************/

binn * APIENTRY binn_list_value(void *ptr, int pos) {
  binn *value;

  value = (binn *) binn_malloc(sizeof(binn));

  if (binn_list_get_value(ptr, pos, value) == FALSE) {
    free_fn(value);
    return NULL;
  }

  value->allocated = TRUE;
  return value;

}

/*************************************************************************************/

binn * APIENTRY binn_map_value(void *ptr, int id) {
  binn *value;

  value = (binn *) binn_malloc(sizeof(binn));

  if (binn_map_get_value(ptr, id, value) == FALSE) {
    free_fn(value);
    return NULL;
  }

  value->allocated = TRUE;
  return value;

}

/*************************************************************************************/

binn * APIENTRY binn_object_value(void *ptr, char *key) {
  binn *value;

  value = (binn *) binn_malloc(sizeof(binn));

  if (binn_object_get_value(ptr, key, value) == FALSE) {
    free_fn(value);
    return NULL;
  }

  value->allocated = TRUE;
  return value;

}

/***************************************************************************/
/***************************************************************************/

void * APIENTRY binn_list_read(void *list, int pos, int *ptype, int *psize) {
  binn value;

  if (binn_list_get_value(list, pos, &value) == FALSE) return NULL;
  if (ptype) *ptype = value.type;
  if (psize) *psize = value.size;
#if BYTE_ORDER == LITTLE_ENDIAN
  return store_value(&value);
#else
  return value.ptr;
#endif

}

/***************************************************************************/

void * APIENTRY binn_map_read(void *map, int id, int *ptype, int *psize) {
  binn value;

  if (binn_map_get_value(map, id, &value) == FALSE) return NULL;
  if (ptype) *ptype = value.type;
  if (psize) *psize = value.size;
#if BYTE_ORDER == LITTLE_ENDIAN
  return store_value(&value);
#else
  return value.ptr;
#endif

}

/***************************************************************************/

void * APIENTRY binn_object_read(void *obj, char *key, int *ptype, int *psize) {
  binn value;

  if (binn_object_get_value(obj, key, &value) == FALSE) return NULL;
  if (ptype) *ptype = value.type;
  if (psize) *psize = value.size;
#if BYTE_ORDER == LITTLE_ENDIAN
  return store_value(&value);
#else
  return value.ptr;
#endif

}

/***************************************************************************/
/***************************************************************************/

BOOL APIENTRY binn_list_get(void *ptr, int pos, int type, void *pvalue, int *psize) {
  binn value;
  int storage_type;

  storage_type = binn_get_read_storage(type);
  if ((storage_type != BINN_STORAGE_NOBYTES) && (pvalue == NULL)) return FALSE;

  zero_value(pvalue, type);

  if (binn_list_get_value(ptr, pos, &value) == FALSE) return FALSE;

  if (copy_value(value.ptr, pvalue, value.type, type, storage_type) == FALSE) return FALSE;

  if (psize) *psize = value.size;

  return TRUE;

}

/***************************************************************************/

BOOL APIENTRY binn_map_get(void *ptr, int id, int type, void *pvalue, int *psize) {
  binn value;
  int storage_type;

  storage_type = binn_get_read_storage(type);
  if ((storage_type != BINN_STORAGE_NOBYTES) && (pvalue == NULL)) return FALSE;

  zero_value(pvalue, type);

  if (binn_map_get_value(ptr, id, &value) == FALSE) return FALSE;

  if (copy_value(value.ptr, pvalue, value.type, type, storage_type) == FALSE) return FALSE;

  if (psize) *psize = value.size;

  return TRUE;

}

/***************************************************************************/

//   if (binn_object_get(obj, "multiplier", BINN_INT32, &multiplier, NULL) == FALSE) xxx;

BOOL APIENTRY binn_object_get(void *ptr, char *key, int type, void *pvalue, int *psize) {
  binn value;
  int storage_type;

  storage_type = binn_get_read_storage(type);
  if ((storage_type != BINN_STORAGE_NOBYTES) && (pvalue == NULL)) return FALSE;

  zero_value(pvalue, type);

  if (binn_object_get_value(ptr, key, &value) == FALSE) return FALSE;

  if (copy_value(value.ptr, pvalue, value.type, type, storage_type) == FALSE) return FALSE;

  if (psize) *psize = value.size;

  return TRUE;

}

/***************************************************************************/
/***************************************************************************/

// these functions below may not be implemented as inline functions, because
// they use a lot of space, even for the variable. so they will be exported.

// but what about using as static?
//    is there any problem with wrappers? can these wrappers implement these functions using the header?
//    if as static, will they be present even on modules that don't use the functions?

signed char APIENTRY binn_list_int8(void *list, int pos) {
  signed char value;

  binn_list_get(list, pos, BINN_INT8, &value, NULL);

  return value;
}

short APIENTRY binn_list_int16(void *list, int pos) {
  short value;

  binn_list_get(list, pos, BINN_INT16, &value, NULL);

  return value;
}

int APIENTRY binn_list_int32(void *list, int pos) {
  int value;

  binn_list_get(list, pos, BINN_INT32, &value, NULL);

  return value;
}

int64 APIENTRY binn_list_int64(void *list, int pos) {
  int64 value;

  binn_list_get(list, pos, BINN_INT64, &value, NULL);

  return value;
}

unsigned char APIENTRY binn_list_uint8(void *list, int pos) {
  unsigned char value;

  binn_list_get(list, pos, BINN_UINT8, &value, NULL);

  return value;
}

unsigned short APIENTRY binn_list_uint16(void *list, int pos) {
  unsigned short value;

  binn_list_get(list, pos, BINN_UINT16, &value, NULL);

  return value;
}

unsigned int APIENTRY binn_list_uint32(void *list, int pos) {
  unsigned int value;

  binn_list_get(list, pos, BINN_UINT32, &value, NULL);

  return value;
}

uint64 APIENTRY binn_list_uint64(void *list, int pos) {
  uint64 value;

  binn_list_get(list, pos, BINN_UINT64, &value, NULL);

  return value;
}

float APIENTRY binn_list_float(void *list, int pos) {
  float value;

  binn_list_get(list, pos, BINN_FLOAT32, &value, NULL);

  return value;
}

double APIENTRY binn_list_double(void *list, int pos) {
  double value;

  binn_list_get(list, pos, BINN_FLOAT64, &value, NULL);

  return value;
}

BOOL APIENTRY binn_list_bool(void *list, int pos) {
  BOOL value;

  binn_list_get(list, pos, BINN_BOOL, &value, NULL);

  return value;
}

BOOL APIENTRY binn_list_null(void *list, int pos) {

  return binn_list_get(list, pos, BINN_NULL, NULL, NULL);

}

char * APIENTRY binn_list_str(void *list, int pos) {
  char *value;

  binn_list_get(list, pos, BINN_STRING, &value, NULL);

  return value;
}

void * APIENTRY binn_list_blob(void *list, int pos, int *psize) {
  void *value;

  binn_list_get(list, pos, BINN_BLOB, &value, psize);

  return value;
}

void * APIENTRY binn_list_list(void *list, int pos) {
  void *value;

  binn_list_get(list, pos, BINN_LIST, &value, NULL);

  return value;
}

void * APIENTRY binn_list_map(void *list, int pos) {
  void *value;

  binn_list_get(list, pos, BINN_MAP, &value, NULL);

  return value;
}

void * APIENTRY binn_list_object(void *list, int pos) {
  void *value;

  binn_list_get(list, pos, BINN_OBJECT, &value, NULL);

  return value;
}

/***************************************************************************/

signed char APIENTRY binn_map_int8(void *map, int id) {
  signed char value;

  binn_map_get(map, id, BINN_INT8, &value, NULL);

  return value;
}

short APIENTRY binn_map_int16(void *map, int id) {
  short value;

  binn_map_get(map, id, BINN_INT16, &value, NULL);

  return value;
}

int APIENTRY binn_map_int32(void *map, int id) {
  int value;

  binn_map_get(map, id, BINN_INT32, &value, NULL);

  return value;
}

int64 APIENTRY binn_map_int64(void *map, int id) {
  int64 value;

  binn_map_get(map, id, BINN_INT64, &value, NULL);

  return value;
}

unsigned char APIENTRY binn_map_uint8(void *map, int id) {
  unsigned char value;

  binn_map_get(map, id, BINN_UINT8, &value, NULL);

  return value;
}

unsigned short APIENTRY binn_map_uint16(void *map, int id) {
  unsigned short value;

  binn_map_get(map, id, BINN_UINT16, &value, NULL);

  return value;
}

unsigned int APIENTRY binn_map_uint32(void *map, int id) {
  unsigned int value;

  binn_map_get(map, id, BINN_UINT32, &value, NULL);

  return value;
}

uint64 APIENTRY binn_map_uint64(void *map, int id) {
  uint64 value;

  binn_map_get(map, id, BINN_UINT64, &value, NULL);

  return value;
}

float APIENTRY binn_map_float(void *map, int id) {
  float value;

  binn_map_get(map, id, BINN_FLOAT32, &value, NULL);

  return value;
}

double APIENTRY binn_map_double(void *map, int id) {
  double value;

  binn_map_get(map, id, BINN_FLOAT64, &value, NULL);

  return value;
}

BOOL APIENTRY binn_map_bool(void *map, int id) {
  BOOL value;

  binn_map_get(map, id, BINN_BOOL, &value, NULL);

  return value;
}

BOOL APIENTRY binn_map_null(void *map, int id) {

  return binn_map_get(map, id, BINN_NULL, NULL, NULL);

}

char * APIENTRY binn_map_str(void *map, int id) {
  char *value;

  binn_map_get(map, id, BINN_STRING, &value, NULL);

  return value;
}

void * APIENTRY binn_map_blob(void *map, int id, int *psize) {
  void *value;

  binn_map_get(map, id, BINN_BLOB, &value, psize);

  return value;
}

void * APIENTRY binn_map_list(void *map, int id) {
  void *value;

  binn_map_get(map, id, BINN_LIST, &value, NULL);

  return value;
}

void * APIENTRY binn_map_map(void *map, int id) {
  void *value;

  binn_map_get(map, id, BINN_MAP, &value, NULL);

  return value;
}

void * APIENTRY binn_map_object(void *map, int id) {
  void *value;

  binn_map_get(map, id, BINN_OBJECT, &value, NULL);

  return value;
}

/***************************************************************************/

signed char APIENTRY binn_object_int8(void *obj, char *key) {
  signed char value;

  binn_object_get(obj, key, BINN_INT8, &value, NULL);

  return value;
}

short APIENTRY binn_object_int16(void *obj, char *key) {
  short value;

  binn_object_get(obj, key, BINN_INT16, &value, NULL);

  return value;
}

int APIENTRY binn_object_int32(void *obj, char *key) {
  int value;

  binn_object_get(obj, key, BINN_INT32, &value, NULL);

  return value;
}

int64 APIENTRY binn_object_int64(void *obj, char *key) {
  int64 value;

  binn_object_get(obj, key, BINN_INT64, &value, NULL);

  return value;
}

unsigned char APIENTRY binn_object_uint8(void *obj, char *key) {
  unsigned char value;

  binn_object_get(obj, key, BINN_UINT8, &value, NULL);

  return value;
}

unsigned short APIENTRY binn_object_uint16(void *obj, char *key) {
  unsigned short value;

  binn_object_get(obj, key, BINN_UINT16, &value, NULL);

  return value;
}

unsigned int APIENTRY binn_object_uint32(void *obj, char *key) {
  unsigned int value;

  binn_object_get(obj, key, BINN_UINT32, &value, NULL);

  return value;
}

uint64 APIENTRY binn_object_uint64(void *obj, char *key) {
  uint64 value;

  binn_object_get(obj, key, BINN_UINT64, &value, NULL);

  return value;
}

float APIENTRY binn_object_float(void *obj, char *key) {
  float value;

  binn_object_get(obj, key, BINN_FLOAT32, &value, NULL);

  return value;
}

double APIENTRY binn_object_double(void *obj, char *key) {
  double value;

  binn_object_get(obj, key, BINN_FLOAT64, &value, NULL);

  return value;
}

BOOL APIENTRY binn_object_bool(void *obj, char *key) {
  BOOL value;

  binn_object_get(obj, key, BINN_BOOL, &value, NULL);

  return value;
}

BOOL APIENTRY binn_object_null(void *obj, char *key) {

  return binn_object_get(obj, key, BINN_NULL, NULL, NULL);

}

char * APIENTRY binn_object_str(void *obj, char *key) {
  char *value;

  binn_object_get(obj, key, BINN_STRING, &value, NULL);

  return value;
}

void * APIENTRY binn_object_blob(void *obj, char *key, int *psize) {
  void *value;

  binn_object_get(obj, key, BINN_BLOB, &value, psize);

  return value;
}

void * APIENTRY binn_object_list(void *obj, char *key) {
  void *value;

  binn_object_get(obj, key, BINN_LIST, &value, NULL);

  return value;
}

void * APIENTRY binn_object_map(void *obj, char *key) {
  void *value;

  binn_object_get(obj, key, BINN_MAP, &value, NULL);

  return value;
}

void * APIENTRY binn_object_object(void *obj, char *key) {
  void *value;

  binn_object_get(obj, key, BINN_OBJECT, &value, NULL);

  return value;
}

/*************************************************************************************/
/*************************************************************************************/

BINN_PRIVATE binn * binn_alloc_item() {
  binn *item;
  item = (binn *) binn_malloc(sizeof(binn));
  if (item) {
    memset(item, 0, sizeof(binn));
    item->header = BINN_MAGIC;
    item->allocated = TRUE;
    //item->writable = FALSE;  -- already zeroed
  }
  return item;
}

/*************************************************************************************/

binn * APIENTRY binn_value(int type, void *pvalue, int size, binn_mem_free freefn) {
  int storage_type;
  binn *item = binn_alloc_item();
  if (item) {
    item->type = type;
    binn_get_type_info(type, &storage_type, NULL);
    switch (storage_type) {
    case BINN_STORAGE_NOBYTES:
      break;
    case BINN_STORAGE_STRING:
      if (size == 0) size = strlen((char*)pvalue) + 1;
    case BINN_STORAGE_BLOB:
    case BINN_STORAGE_CONTAINER:
      if (freefn == BINN_TRANSIENT) {
        item->ptr = binn_memdup(pvalue, size);
        if (item->ptr == NULL) {
          free_fn(item);
          return NULL;
        }
        item->freefn = free_fn;
        if (storage_type == BINN_STORAGE_STRING) size--;
      } else {
        item->ptr = pvalue;
        item->freefn = freefn;
      }
      item->size = size;
      break;
    default:
      item->ptr = &item->vint32;
      copy_raw_value(pvalue, item->ptr, storage_type);
    }
  }
  return item;
}

/*************************************************************************************/

BOOL APIENTRY binn_set_string(binn *item, char *str, binn_mem_free pfree) {

  if (item == NULL || str == NULL) return FALSE;

  if (pfree == BINN_TRANSIENT) {
    item->ptr = binn_memdup(str, strlen(str) + 1);
    if (item->ptr == NULL) return FALSE;
    item->freefn = free_fn;
  } else {
    item->ptr = str;
    item->freefn = pfree;
  }

  item->type = BINN_STRING;
  return TRUE;

}

/*************************************************************************************/

BOOL APIENTRY binn_set_blob(binn *item, void *ptr, int size, binn_mem_free pfree) {

  if (item == NULL || ptr == NULL) return FALSE;

  if (pfree == BINN_TRANSIENT) {
    item->ptr = binn_memdup(ptr, size);
    if (item->ptr == NULL) return FALSE;
    item->freefn = free_fn;
  } else {
    item->ptr = ptr;
    item->freefn = pfree;
  }

  item->type = BINN_BLOB;
  item->size = size;
  return TRUE;

}

/*************************************************************************************/
/*** READ CONVERTED VALUE ************************************************************/
/*************************************************************************************/

#ifdef _MSC_VER
#define atoi64 _atoi64
#else
int64 atoi64(char *str) {
  int64 retval;
  int is_negative=0;

  if (*str == '-') {
    is_negative = 1;
    str++;
  }
  retval = 0;
  for (; *str; str++) {
    retval = 10 * retval + (*str - '0');
  }
  if (is_negative) retval *= -1;
  return retval;
}
#endif

/*****************************************************************************/

BINN_PRIVATE BOOL is_integer(char *p) {
  BOOL retval;

  if (p == NULL) return FALSE;
  if (*p == '-') p++;
  if (*p == 0) return FALSE;

  retval = TRUE;

  for (; *p; p++) {
    if ( (*p < '0') || (*p > '9') ) {
      retval = FALSE;
    }
  }

  return retval;
}

/*****************************************************************************/

BINN_PRIVATE BOOL is_float(char *p) {
  BOOL retval, number_found=FALSE;

  if (p == NULL) return FALSE;
  if (*p == '-') p++;
  if (*p == 0) return FALSE;

  retval = TRUE;

  for (; *p; p++) {
    if ((*p == '.') || (*p == ',')) {
      if (!number_found) retval = FALSE;
    } else if ( (*p >= '0') && (*p <= '9') ) {
      number_found = TRUE;
    } else {
      return FALSE;
    }
  }

  return retval;
}

/*************************************************************************************/

BINN_PRIVATE BOOL is_bool_str(char *str, BOOL *pbool) {
  int64  vint;
  double vdouble;

  if (str == NULL || pbool == NULL) return FALSE;

  if (stricmp(str, "true") == 0) goto loc_true;
  if (stricmp(str, "yes") == 0) goto loc_true;
  if (stricmp(str, "on") == 0) goto loc_true;
  //if (stricmp(str, "1") == 0) goto loc_true;

  if (stricmp(str, "false") == 0) goto loc_false;
  if (stricmp(str, "no") == 0) goto loc_false;
  if (stricmp(str, "off") == 0) goto loc_false;
  //if (stricmp(str, "0") == 0) goto loc_false;

  if (is_integer(str)) {
    vint = atoi64(str);
    *pbool = (vint != 0) ? TRUE : FALSE;
    return TRUE;
  } else if (is_float(str)) {
    vdouble = atof(str);
    *pbool = (vdouble != 0) ? TRUE : FALSE;
    return TRUE;
  }

  return FALSE;

loc_true:
  *pbool = TRUE;
  return TRUE;

loc_false:
  *pbool = FALSE;
  return TRUE;

}

/*************************************************************************************/

BOOL APIENTRY binn_get_int32(binn *value, int *pint) {

  if (value == NULL || pint == NULL) return FALSE;

  if (type_family(value->type) == BINN_FAMILY_INT) {
    return copy_int_value(value->ptr, pint, value->type, BINN_INT32);
  }

  switch (value->type) {
  case BINN_FLOAT:
    if ((value->vfloat < INT32_MIN) || (value->vfloat > INT32_MAX)) return FALSE;
    *pint = round(value->vfloat);
    break;
  case BINN_DOUBLE:
    if ((value->vdouble < INT32_MIN) || (value->vdouble > INT32_MAX)) return FALSE;
    *pint = round(value->vdouble);
    break;
  case BINN_STRING:
    if (is_integer((char*)value->ptr))
      *pint = atoi((char*)value->ptr);
    else if (is_float((char*)value->ptr))
      *pint = round(atof((char*)value->ptr));
    else
      return FALSE;
    break;
  case BINN_BOOL:
    *pint = value->vbool;
    break;
  default:
    return FALSE;
  }

  return TRUE;
}

/*************************************************************************************/

BOOL APIENTRY binn_get_int64(binn *value, int64 *pint) {

  if (value == NULL || pint == NULL) return FALSE;

  if (type_family(value->type) == BINN_FAMILY_INT) {
    return copy_int_value(value->ptr, pint, value->type, BINN_INT64);
  }

  switch (value->type) {
  case BINN_FLOAT:
    if ((value->vfloat < INT64_MIN) || (value->vfloat > INT64_MAX)) return FALSE;
    *pint = round(value->vfloat);
    break;
  case BINN_DOUBLE:
    if ((value->vdouble < INT64_MIN) || (value->vdouble > INT64_MAX)) return FALSE;
    *pint = round(value->vdouble);
    break;
  case BINN_STRING:
    if (is_integer((char*)value->ptr))
      *pint = atoi64((char*)value->ptr);
    else if (is_float((char*)value->ptr))
      *pint = round(atof((char*)value->ptr));
    else
      return FALSE;
    break;
  case BINN_BOOL:
    *pint = value->vbool;
    break;
  default:
    return FALSE;
  }

  return TRUE;
}

/*************************************************************************************/

BOOL APIENTRY binn_get_double(binn *value, double *pfloat) {
  int64 vint;

  if (value == NULL || pfloat == NULL) return FALSE;

  if (type_family(value->type) == BINN_FAMILY_INT) {
    if (copy_int_value(value->ptr, &vint, value->type, BINN_INT64) == FALSE) return FALSE;
    *pfloat = (double) vint;
    return TRUE;
  }

  switch (value->type) {
  case BINN_FLOAT:
    *pfloat = value->vfloat;
    break;
  case BINN_DOUBLE:
    *pfloat = value->vdouble;
    break;
  case BINN_STRING:
    if (is_integer((char*)value->ptr))
      *pfloat = (double) atoi64((char*)value->ptr);
    else if (is_float((char*)value->ptr))
      *pfloat = atof((char*)value->ptr);
    else
      return FALSE;
    break;
  case BINN_BOOL:
    *pfloat = value->vbool;
    break;
  default:
    return FALSE;
  }

  return TRUE;
}

/*************************************************************************************/

BOOL APIENTRY binn_get_bool(binn *value, BOOL *pbool) {
  int64 vint;

  if (value == NULL || pbool == NULL) return FALSE;

  if (type_family(value->type) == BINN_FAMILY_INT) {
    if (copy_int_value(value->ptr, &vint, value->type, BINN_INT64) == FALSE) return FALSE;
    *pbool = (vint != 0) ? TRUE : FALSE;
    return TRUE;
  }

  switch (value->type) {
  case BINN_BOOL:
    *pbool = value->vbool;
    break;
  case BINN_FLOAT:
    *pbool = (value->vfloat != 0) ? TRUE : FALSE;
    break;
  case BINN_DOUBLE:
    *pbool = (value->vdouble != 0) ? TRUE : FALSE;
    break;
  case BINN_STRING:
    return is_bool_str((char*)value->ptr, pbool);
  default:
    return FALSE;
  }

  return TRUE;
}

/*************************************************************************************/

char * APIENTRY binn_get_str(binn *value) {
  int64 vint;
  char buf[128];

  if (value == NULL) return NULL;

  if (type_family(value->type) == BINN_FAMILY_INT) {
    if (copy_int_value(value->ptr, &vint, value->type, BINN_INT64) == FALSE) return NULL;
    sprintf(buf, "%" INT64_FORMAT, vint);
    goto loc_convert_value;
  }

  switch (value->type) {
  case BINN_FLOAT:
    value->vdouble = value->vfloat;
  case BINN_DOUBLE:
    sprintf(buf, "%g", value->vdouble);
    goto loc_convert_value;
  case BINN_STRING:
    return (char*) value->ptr;
  case BINN_BOOL:
    if (value->vbool)
      strcpy(buf, "true");
    else
      strcpy(buf, "false");
    goto loc_convert_value;
  }

  return NULL;

loc_convert_value:

  //value->vint64 = 0;
  value->ptr = strdup(buf);
  if (value->ptr == NULL) return NULL;
  value->freefn = free;
  value->type = BINN_STRING;
  return (char*) value->ptr;

}

/*************************************************************************************/
/*** GENERAL FUNCTIONS ***************************************************************/
/*************************************************************************************/

BOOL APIENTRY binn_is_container(binn *item) {

  if (item == NULL) return FALSE;

  switch (item->type) {
  case BINN_LIST:
  case BINN_MAP:
  case BINN_OBJECT:
    return TRUE;
  default:
    return FALSE;
  }

}

/*************************************************************************************/