/*
 * Raw memory allocation interface
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "stdlib.h"

/*
 * Private wrapper structure for allocated memory
 */
typedef struct {
  int   size;
  int 	rw;
  void  *data;
} memblock;

/*
 * Initialize memory block structure
 */
static memblock *mem_init(int size, void *data)
{
  memblock *mem;
  
  mem = oom(malloc(sizeof(memblock)));
  mem->size = size;
  mem->data = data;
  mem->rw   = 1;
  
  return mem;
}

/*
 * Clean up memory block structure
 */
static void mem_cleanup(void *data)
{
  memblock *mem = data;
  
  if (mem) free(mem->data);
  free(mem);
}

/*
 * Clean up memory block structure put not contained pointer
 */
static void mem_halfcleanup(void *data)
{
  free(data);
}

/*
 * Check whether resource is memory resource
 */
static int is_mem(value *val)
{
  return (val->value_u.res_val->release == mem_cleanup ||
          val->value_u.res_val->release == mem_halfcleanup);
}

/*
 * Get memory pointer from memory resource
 */
static void *mem_get(void *data)
{
  memblock *mem = data;
  
  if (!mem) {
    return NULL;
  }
  return mem->data;
}

/*
 * Check whether resource is memory resource
 */
value *mem_is_resource(arena_state *s, unsigned int argc, value **argv)
{
  return value_make_bool(is_mem(argv[0]));
}

/*
 * Allocate a given number of bytes
 */
value *mem_malloc(arena_state *s, unsigned int argc, value **argv)
{
  int size = INT_OF(argv[0]);
  void *mem;
  value *res;

  mem = malloc(size);
  if (!mem) {
    return value_make_void();
  }
  res = value_make_resource(mem_init(size, mem), mem_cleanup);
  res->value_u.res_val->get = mem_get;
  return res;
}

/*
 * Allocate a given number of zero-filled bytes
 */
value *mem_calloc(arena_state *s, unsigned int argc, value **argv)
{
  int size = INT_OF(argv[0]) * INT_OF(argv[1]);
  void *mem;
  value *res;
  
  mem = calloc(INT_OF(argv[0]), INT_OF(argv[1]));
  if (!mem) {
    return value_make_void();
  }
  res = value_make_resource(mem_init(size, mem), mem_cleanup);
  res->value_u.res_val->get = mem_get;
  return res;
}

/*
 * Reallocate memory with a new size
 */
value *mem_realloc(arena_state *s, unsigned int argc, value **argv)
{
  memblock *mem = RESDATA_OF(argv[0]);
  int size = INT_OF(argv[1]);
  void *data;
  
  if (!is_mem(argv[0]) || !mem || size < 0) {
    return value_make_bool(0);
  }
  
  data = realloc(mem->data, size);
  if (!data) {
    return value_make_bool(0);
  }
  
  mem->data = data;
  mem->size = size;
  return value_make_bool(1);
}

/*
 * Free a memory resource
 */
value *mem_free(arena_state *s, unsigned int argc, value **argv)
{
  memblock *mem = RESDATA_OF(argv[0]);
  
  if (is_mem(argv[0]) && mem) {
    mem_cleanup(mem);
    RESDATA_OF(argv[0]) = NULL;
  }
  return value_make_void();
}

/*
 * Create resource for C NULL pointer
 */
value *mem_cnull(arena_state *s, unsigned int argc, value **argv)
{
  return value_make_resource(NULL, mem_cleanup);
}

/*
 * Check for resource containing C NULL pointer
 */
value *mem_is_null(arena_state *s, unsigned int argc, value **argv)
{
  memblock *mem = RESDATA_OF(argv[0]);
  
  return value_make_bool(is_mem(argv[0]) && (!mem || !mem->data));
}

/*
 * Create resource from string value
 */
value *mem_cstring(arena_state *s, unsigned int argc, value **argv)
{
  char *str = argv[0]->value_u.string_val.value;
  int len;
  char *mem;
  value *res;

  if (str && !value_str_compat(argv[0])) {
    return value_make_void();
  }

  if (!str) {
    str = "";
  }
  
  len = strlen(str) + 1;
  mem = oom(malloc(len));
  strcpy(mem, str);
  
  res = value_make_resource(mem_init(len, mem), mem_cleanup);
  res->value_u.res_val->get = mem_get;
  return res;
}

/*
 * Put character at offset in memory resource
 */
value *mem_put_char(arena_state *s, unsigned int argc, value **argv)
{
  memblock *mem = RESDATA_OF(argv[0]);
  char *put;
  int offset = INT_OF(argv[1]);
  char val = INT_OF(argv[2]);
  
  if (!is_mem(argv[0]) || !mem || !mem->rw) {
    return value_make_bool(0);
  }

  if (mem->size < offset + sizeof(char)) {
    mem->data = oom(realloc(mem->data, offset + sizeof(char)));
    mem->size = offset + sizeof(char);
  }

  put = mem->data;
  put += offset;
  *put = val;

  return value_make_bool(1);
}

/*
 * Put short integer at offset in memory resource
 */
value *mem_put_short(arena_state *s, unsigned int argc, value **argv)
{
  memblock *mem = RESDATA_OF(argv[0]);
  char *pos;
  short *put;
  int offset = INT_OF(argv[1]);
  short val = INT_OF(argv[2]);
  
  if (!is_mem(argv[0]) || !mem || !mem->rw) {
    return value_make_bool(0);
  }

  if (mem->size < offset + sizeof(short)) {
    mem->data = oom(realloc(mem->data, offset + sizeof(short)));
    mem->size = offset + sizeof(short);
  }

  pos = mem->data;
  pos += offset;
  put = (short *) pos;
  *put = val;

  return value_make_bool(1);
}

/*
 * Put integer at offset in memory resource
 */
value *mem_put_int(arena_state *s, unsigned int argc, value **argv)
{
  memblock *mem = RESDATA_OF(argv[0]);
  char *pos;
  int *put;
  int offset = INT_OF(argv[1]);
  int val = INT_OF(argv[2]);
  
  if (!is_mem(argv[0]) || !mem || !mem->rw) {
    return value_make_bool(0);
  }

  if (mem->size < offset + sizeof(int)) {
    mem->data = oom(realloc(mem->data, offset + sizeof(int)));
    mem->size = offset + sizeof(int);
  }

  pos = mem->data;
  pos += offset;
  put = (int *) pos;
  *put = val;

  return value_make_bool(1);
}

/*
 * Put float at offset in memory resource
 */
value *mem_put_float(arena_state *s, unsigned int argc, value **argv)
{
  memblock *mem = RESDATA_OF(argv[0]);
  char *pos;
  float *put;
  int offset = INT_OF(argv[1]);
  float val = FLOAT_OF(argv[2]);
  
  if (!is_mem(argv[0]) || !mem || !mem->rw) {
    return value_make_bool(0);
  }

  if (mem->size < offset + sizeof(float)) {
    mem->data = oom(realloc(mem->data, offset + sizeof(float)));
    mem->size = offset + sizeof(float);
  }

  pos = mem->data;
  pos += offset;
  put = (float *) pos;
  *put = val;

  return value_make_bool(1);
}

/*
 * Put double at offset in memory resource
 */
value *mem_put_double(arena_state *s, unsigned int argc, value **argv)
{
  memblock *mem = RESDATA_OF(argv[0]);
  char *pos;
  double *put;
  int offset = INT_OF(argv[1]);
  double val = FLOAT_OF(argv[2]);
  
  if (!is_mem(argv[0]) || !mem || !mem->rw) {
    return value_make_bool(0);
  }

  if (mem->size < offset + sizeof(double)) {
    mem->data = oom(realloc(mem->data, offset + sizeof(double)));
    mem->size = offset + sizeof(double);
  }

  pos = mem->data;
  pos += offset;
  put = (double *) pos;
  *put = val;

  return value_make_bool(1);
}

/*
 * Put pointer to another resource at offset in resource
 */
value *mem_put_pointer(arena_state *s, unsigned int argc, value **argv)
{
  memblock *mem = RESDATA_OF(argv[0]);
  memblock *ptr = RESDATA_OF(argv[2]);
  char *pos;
  void **put;
  int offset = INT_OF(argv[1]);
  
  if (!is_mem(argv[0]) || !mem || !mem->rw || !is_mem(argv[2])) {
    return value_make_bool(0);
  }

  if (mem->size < offset + sizeof(void *)) {
    mem->data = oom(realloc(mem->data, offset + sizeof(void *)));
    mem->size = offset + sizeof(void *);
  }
  
  pos = mem->data;
  pos += offset;
  put = (void **) pos;
  if (ptr) {
    *put = ptr->data;
  } else {
    *put = NULL;
  }
  
  return value_make_bool(1);
}

/*
 * Get character from offset in memory resource
 */
value *mem_get_char(arena_state *s, unsigned int argc, value **argv)
{
  memblock *mem = RESDATA_OF(argv[0]);
  char *get;
  int offset = INT_OF(argv[1]);
  
  if (!is_mem(argv[0]) || !mem ||
      (mem->size && offset + sizeof(char) > mem->size)) {
    return value_make_void();
  }
  
  get = mem->data;
  get += offset;
  
  return value_make_int((int) *get);
}

/*
 * Get short integer from offset in memory resource
 */
value *mem_get_short(arena_state *s, unsigned int argc, value **argv)
{
  memblock *mem = RESDATA_OF(argv[0]);
  char *pos;
  short *get;
  int offset = INT_OF(argv[1]);
  
  if (!is_mem(argv[0]) || !mem ||
      (mem->size && offset + sizeof(short) > mem->size)) {
    return value_make_void();
  }
  
  pos = mem->data;
  pos += offset;
  get = (short *) pos;
  
  return value_make_int((int) *get);
}

/*
 * Get integer from offset in memory resource
 */
value *mem_get_int(arena_state *s, unsigned int argc, value **argv)
{
  memblock *mem = RESDATA_OF(argv[0]);
  char *pos;
  int *get;
  int offset = INT_OF(argv[1]);
  
  if (!is_mem(argv[0]) || !mem ||
      (mem->size && offset + sizeof(int) > mem->size)) {
    return value_make_void();
  }
  
  pos = mem->data;
  pos += offset;
  get = (int *) pos;
  
  return value_make_int(*get);
}

/*
 * Get float from offset in memory resource
 */
value *mem_get_float(arena_state *s, unsigned int argc, value **argv)
{
  memblock *mem = RESDATA_OF(argv[0]);
  char *pos;
  float *get;
  int offset = INT_OF(argv[1]);
  
  if (!is_mem(argv[0]) || !mem ||
      (mem->size && offset + sizeof(float) > mem->size)) {
    return value_make_void();
  }
  
  pos = mem->data;
  pos += offset;
  get = (float *) pos;
  
  return value_make_float((double) *get);
}

/*
 * Get double from offset in memory resource
 */
value *mem_get_double(arena_state *s, unsigned int argc, value **argv)
{
  memblock *mem = RESDATA_OF(argv[0]);
  char *pos;
  double *get;
  int offset = INT_OF(argv[1]);
  
  if (!is_mem(argv[0]) || !mem ||
      (mem->size && offset + sizeof(double) > mem->size)) {
    return value_make_void();
  }
  
  pos = mem->data;
  pos += offset;
  get = (double *) pos;
  
  return value_make_float(*get);
}

/*
 * Create memory resource from direct pointer
 */
value *mem_make_pointer(void *data, int free)
{
  memblock *mem;
  value *res;
  
  res = value_make_resource(mem_init(0, data), 
    free ? mem_cleanup : mem_halfcleanup);
  mem = RESDATA_OF(res);
  mem->rw = 0;
  
  res->value_u.res_val->get = mem_get;
  
  return res;
}

/*
 * Get memory resource from offset in memory resource
 */
value *mem_get_pointer(arena_state *s, unsigned int argc, value **argv)
{
  memblock *mem = RESDATA_OF(argv[0]);
  memblock *resmem;
  char *pos;
  void **get;
  int offset = INT_OF(argv[1]);
  int cleanup = BOOL_OF(argv[2]);
  value *res;

  if (!is_mem(argv[0]) || !mem ||
      (mem->size && offset + sizeof(void *) > mem->size)) {
    return value_make_void();
  }
  
  pos = mem->data;
  pos += offset;
  get = (void **) pos;
  
  res = value_make_resource(mem_init(0, *get),
    cleanup ? mem_cleanup : mem_halfcleanup);
  resmem = RESDATA_OF(res);
  resmem->rw = 0;

  res->value_u.res_val->get = mem_get;
  
  return res;
}

/*
 * Get string value from offset in memory resource
 */
value *mem_string(arena_state *s, unsigned int argc, value **argv)
{
  memblock *mem = RESDATA_OF(argv[0]);
  char *get;
  int offset = INT_OF(argv[1]);
  
  if (!is_mem(argv[0]) || !mem ||
      (mem->size && offset + sizeof(char) > mem->size)) {
    return value_make_void();
  }
  
  get = mem->data;
  get += offset;
  
  return value_make_string(get);
}

/*
 * Copy bytes from memory resource to memory resource
 *
 * This routine will corrupt memory if source and destination
 * memory resources overlap.
 */
value *mem_cpy(arena_state *s, unsigned int argc, value **argv)
{
  memblock *dst = RESDATA_OF(argv[0]);
  int dst_off   = INT_OF(argv[1]);
  memblock *src = RESDATA_OF(argv[2]);
  int src_off   = INT_OF(argv[3]);
  int count     = INT_OF(argv[4]);
  
  if (!is_mem(argv[0]) || !dst || !is_mem(argv[2]) || !src) {
    return value_make_bool(0);
  }
  if (count == 0) {
    return value_make_bool(1);
  }
  
  if (count > src->size - src_off) {
    count = src->size - src_off;
  }
  if (dst_off + count > dst->size) {
    dst->data = oom(realloc(dst->data, dst_off + count));
    dst->size = dst_off + count;
  }
  
  memcpy(dst->data + dst_off, src->data + src_off, count);
  return value_make_bool(1);
}

/*
 * Copy bytes from memory resource to memory resource
 *
 * This routine will NOT corrupt memory when source and destination
 * memory resources overlap.
 */
value *mem_move(arena_state *s, unsigned int argc, value **argv)
{
  memblock *dst = RESDATA_OF(argv[0]);
  int dst_off   = INT_OF(argv[1]);
  memblock *src = RESDATA_OF(argv[2]);
  int src_off   = INT_OF(argv[3]);
  int count     = INT_OF(argv[4]);
  
  if (!is_mem(argv[0]) || !dst || !is_mem(argv[2]) || !src) {
    return value_make_bool(0);
  }
  if (count == 0) {
    return value_make_bool(1);
  }
  
  if (count > src->size - src_off) {
    count = src->size - src_off;
  }
  if (dst_off + count > dst->size) {
    dst->data = oom(realloc(dst->data, dst_off + count));
    dst->size = dst_off + count;
  }
  
  memmove(dst->data + dst_off, src->data + src_off, count);
  return value_make_bool(1);
}

/*
 * Compare bytes from two memory resources
 */
value *mem_cmp(arena_state *s, unsigned int argc, value **argv)
{
  memblock *one = RESDATA_OF(argv[0]);  
  int one_off   = INT_OF(argv[1]);
  memblock *two = RESDATA_OF(argv[2]);
  int two_off   = INT_OF(argv[3]);
  int count     = INT_OF(argv[4]);
  int res;
  
  if (!is_mem(argv[0]) || !one || !is_mem(argv[2]) || !two ||
      one_off + count > one->size || two_off + count > two->size) {
    return value_make_void();
  }
  
  res = memcmp(one->data + one_off, two->data + two_off, count);
  return value_make_int(res);
}

/*
 * Look for byte in memory resource
 */
value *mem_chr(arena_state *s, unsigned int argc, value **argv)
{
  memblock *mem = RESDATA_OF(argv[0]);
  int offset    = INT_OF(argv[1]);
  int search    = INT_OF(argv[2]);
  int count     = INT_OF(argv[3]);
  void *where   = NULL;
  
  if (!is_mem(argv[0]) || !mem || offset + count > mem->size) {
    return value_make_void();
  }

  where = memchr(mem->data + offset, search, count);
  if (!where) {
    return value_make_void();
  }
  return value_make_int(where - mem->data);
}

/*
 * Set bytes in memory resource
 */
value *mem_set(arena_state *s, unsigned int argc, value **argv)
{
  memblock *mem = RESDATA_OF(argv[0]);
  int offset    = INT_OF(argv[1]);
  int data      = INT_OF(argv[2]);
  int count     = INT_OF(argv[3]);
  
  if (!is_mem(argv[0]) || !mem) {
    return value_make_bool(0);
  }
  if (offset + count > mem->size) {
    mem->data = oom(realloc(mem->data, offset + count));
    mem->size = offset + count;
  }
  memset(mem->data + offset, data, count);
  return value_make_bool(1);
}

/*
 * Check whether memory resource is writeable
 */
value *mem_is_rw(arena_state *s, unsigned int argc, value **argv)
{
  memblock *mem = RESDATA_OF(argv[0]);
  
  if (!is_mem(argv[0]) || !mem || !mem->rw) {
    return value_make_bool(0);
  }
  return value_make_bool(1);
}

/*
 * Return current allocation size of memory resource (0 = unknown)
 */
value *mem_size(arena_state *s, unsigned int argc, value **argv)
{
  memblock *mem = RESDATA_OF(argv[0]);
  
  if (!is_mem(argv[0])) {
    return value_make_void();
  }
  if (!mem) {
    return value_make_int(0);
  }
  return value_make_int(mem->size);
}
