/* common.c */
/* See Copyright Notice in the file LICENSE */

#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "lua.h"
#include "lauxlib.h"
#include "common.h"

#define N_ALIGN sizeof(int)

/* the table must be on Lua stack top */
int get_int_field (lua_State *L, const char* field)
{
  int val;
  lua_getfield (L, -1, field);
  val = lua_tointeger (L, -1);
  lua_pop (L, 1);
  return val;
}

/* the table must be on Lua stack top */
void set_int_field (lua_State *L, const char* field, int val)
{
  lua_pushinteger (L, val);
  lua_setfield (L, -2, field);
}

void *Lmalloc(lua_State *L, size_t size) {
  void *ud;
  lua_Alloc lalloc = lua_getallocf(L, &ud);
  return lalloc(ud, NULL, 0, size);
}

void *Lrealloc(lua_State *L, void *p, size_t osize, size_t nsize) {
  void *ud;
  lua_Alloc lalloc = lua_getallocf(L, &ud);
  return lalloc(ud, p, osize, nsize);
}

void Lfree(lua_State *L, void *p, size_t osize) {
  void *ud;
  lua_Alloc lalloc = lua_getallocf(L, &ud);
  lalloc(ud, p, osize, 0);
}

/* This function fills a table with string-number pairs.
   The table can be passed as the 1-st lua-function parameter,
   otherwise it is created. The return value is the filled table.
*/
int get_flags (lua_State *L, const flag_pair **arrs) {
  const flag_pair *p;
  const flag_pair **pp;
  int nparams = lua_gettop(L);

  if(nparams == 0)
    lua_newtable(L);
  else {
    if(!lua_istable(L, 1))
      luaL_argerror(L, 1, "not a table");
    if(nparams > 1)
      lua_pushvalue(L, 1);
  }

  for(pp=arrs; *pp; ++pp) {
    for(p=*pp; p->key; ++p) {
      lua_pushstring(L, p->key);
      lua_pushinteger(L, p->val);
      lua_rawset(L, -3);
    }
  }
  return 1;
}

const char *get_flag_key (const flag_pair *fp, int val) {
  for (; fp->key; ++fp) {
    if (fp->val == val)
      return fp->key;
  }
  return NULL;
}

/* Classes */

/*
 *  class TFreeList
 *  ***************
 *  Simple array of pointers to TBuffer's.
 *  The array has fixed capacity (not expanded automatically).
 */

void freelist_init (TFreeList *fl) {
  fl->top = 0;
}

void freelist_add (TFreeList *fl, TBuffer *buf) {
  fl->list[fl->top++] = buf;
}

void freelist_free (TFreeList *fl) {
  while (fl->top > 0)
    buffer_free (fl->list[--fl->top]);
}

/*
 *  class TBuffer
 *  *************
 *  Auto-extensible array of characters for building long strings incrementally.
 *    * Differs from luaL_Buffer in that:
 *       *  its operations do not change Lua stack top position
 *       *  buffer_addvalue does not extract the value from Lua stack
 *       *  buffer_pushresult does not have to be the last operation
 *    * Uses TFreeList class:
 *       *  for inserting itself into a TFreeList instance for future clean-up
 *       *  calls freelist_free prior to calling luaL_error.
 *    * Has specialized "Z-operations" for maintaining mixed string/integer
 *      array:  bufferZ_addlstring, bufferZ_addnum and bufferZ_next.
 *       *  if the array is intended to be "mixed", then the methods
 *          buffer_addlstring and buffer_addvalue must not be used
 *          (the application will crash on bufferZ_next).
 *       *  conversely, if the array is not intended to be "mixed",
 *          then the method bufferZ_next must not be used.
 */

enum { ID_NUMBER, ID_STRING };

void buffer_init (TBuffer *buf, size_t sz, lua_State *L, TFreeList *fl) {
  buf->arr = (char*) Lmalloc(L, sz);
  if (!buf->arr) {
    freelist_free (fl);
    luaL_error (L, "malloc failed");
  }
  buf->size = sz;
  buf->top = 0;
  buf->L = L;
  buf->freelist = fl;
  freelist_add (fl, buf);
}

void buffer_free (TBuffer *buf) {
  Lfree(buf->L, buf->arr, buf->size);
}

void buffer_clear (TBuffer *buf) {
  buf->top = 0;
}

void buffer_pushresult (TBuffer *buf) {
  lua_pushlstring (buf->L, buf->arr, buf->top);
}

void buffer_addbuffer (TBuffer *trg, TBuffer *src) {
  buffer_addlstring (trg, src->arr, src->top);
}

void buffer_addlstring (TBuffer *buf, const void *src, size_t sz) {
  size_t newtop = buf->top + sz;
  if (newtop > buf->size) {
    char *p = (char*) Lrealloc (buf->L, buf->arr, buf->size, 2 * newtop);   /* 2x expansion */
    if (!p) {
      freelist_free (buf->freelist);
      luaL_error (buf->L, "realloc failed");
    }
    buf->arr = p;
    buf->size = 2 * newtop;
  }
  if (src)
    memcpy (buf->arr + buf->top, src, sz);
  buf->top = newtop;
}

void buffer_addvalue (TBuffer *buf, int stackpos) {
  size_t len;
  const char *p = lua_tolstring (buf->L, stackpos, &len);
  buffer_addlstring (buf, p, len);
}

void bufferZ_addlstring (TBuffer *buf, const void *src, size_t len) {
  int n;
  size_t header[2] = { ID_STRING };
  header[1] = len;
  buffer_addlstring (buf, header, sizeof (header));
  buffer_addlstring (buf, src, len);
  n = len % N_ALIGN;
  if (n) buffer_addlstring (buf, NULL, N_ALIGN - n);
}

void bufferZ_addnum (TBuffer *buf, size_t num) {
  size_t header[2] = { ID_NUMBER };
  header[1] = num;
  buffer_addlstring (buf, header, sizeof (header));
}

/* 1. When called repeatedly on the same TBuffer, its existing data
      is discarded and overwritten by the new data.
   2. The TBuffer's array is never shrunk by this function.
*/
void bufferZ_putrepstring (TBuffer *BufRep, int reppos, int nsub) {
  char dbuf[] = { 0, 0 };
  size_t replen;
  const char *p = lua_tolstring (BufRep->L, reppos, &replen);
  const char *end = p + replen;
  BufRep->top = 0;
  while (p < end) {
    const char *q;
    for (q = p; q < end && *q != '%'; ++q)
      {}
    if (q != p)
      bufferZ_addlstring (BufRep, p, q - p);
    if (q < end) {
      if (++q < end) {  /* skip % */
        if (isdigit (*q)) {
          int num;
          *dbuf = *q;
          num = strtol (dbuf, NULL, 10);
          if (num == 1 && nsub == 0)
            num = 0;
          else if (num > nsub) {
            freelist_free (BufRep->freelist);
            luaL_error (BufRep->L, "invalid capture index");
          }
          bufferZ_addnum (BufRep, num);
        }
        else bufferZ_addlstring (BufRep, q, 1);
      }
      p = q + 1;
    }
    else break;
  }
}

/******************************************************************************
  The intended use of this function is as follows:
        size_t iter = 0;
        while (bufferZ_next (buf, &iter, &num, &str)) {
          if (str) do_something_with_string (str, num);
          else     do_something_with_number (num);
        }
*******************************************************************************
*/
int bufferZ_next (TBuffer *buf, size_t *iter, size_t *num, const char **str) {
  if (*iter < buf->top) {
    size_t *ptr_header = (size_t*)(buf->arr + *iter);
    *num = ptr_header[1];
    *iter += 2 * sizeof (size_t);
    *str = NULL;
    if (*ptr_header == ID_STRING) {
      int n;
      *str = buf->arr + *iter;
      *iter += *num;
      n = *iter % N_ALIGN;
      if (n) *iter += (N_ALIGN - n);
    }
    return 1;
  }
  return 0;
}

#if LUA_VERSION_NUM > 501
int luaL_typerror (lua_State *L, int narg, const char *tname) {
  const char *msg = lua_pushfstring(L, "%s expected, got %s",
                                    tname, luaL_typename(L, narg));
  return luaL_argerror(L, narg, msg);
}
#endif

#ifndef REX_NOEMBEDDEDTEST
static int ud_topointer (lua_State *L) {
  lua_pushlightuserdata (L, lua_touserdata (L, 1));
  return 1;
}

static int ud_len (lua_State *L) {
  lua_pushinteger (L, lua_objlen (L, 1));
  return 1;
}

/* for testing purposes only */
int newmembuffer (lua_State *L) {
  size_t len;
  const char* s = luaL_checklstring (L, 1, &len);
  void *ud = lua_newuserdata (L, len);
  memcpy (ud, s, len);
  lua_newtable (L); /* metatable */
  lua_pushvalue (L, -1);
  lua_setfield (L, -2, "__index"); /* metatable.__index = metatable */
  lua_pushcfunction (L, ud_topointer);
  lua_setfield (L, -2, "topointer");
  lua_pushcfunction (L, ud_len);
  lua_setfield (L, -2, "__len");
  lua_setmetatable (L, -2);
  return 1;
}
#endif /* #ifndef REX_NOEMBEDDEDTEST */
