#ifndef TH_GENERIC_FILE
#define TH_GENERIC_FILE "generic/Storage.c"
#else

#include "luaG.h"

static int torch_Storage_(new)(lua_State *L)
{
  int index = 1;
  THStorage *storage;
  THAllocator *allocator = luaT_toudata(L, index, "torch.Allocator");
  if (allocator) index++;

  if(lua_type(L, index) == LUA_TSTRING)
  {
    if (allocator)
      THError("Passing allocator not supported when using file mapping");

    const char *fileName = luaL_checkstring(L, index);
    int isShared = 0;
    if(luaT_optboolean(L, index + 1, 0))
      isShared = TH_ALLOCATOR_MAPPED_SHARED;
    ptrdiff_t size = luaL_optinteger(L, index + 2, 0);
    if (isShared && luaT_optboolean(L, index + 3, 0))
      isShared = TH_ALLOCATOR_MAPPED_SHAREDMEM;
    storage = THStorage_(newWithMapping)(fileName, size, isShared);
  }
  else if(lua_type(L, index) == LUA_TTABLE)
  {
    ptrdiff_t size = lua_objlen(L, index);
    ptrdiff_t i;
    if (allocator)
      storage = THStorage_(newWithAllocator)(size, allocator, NULL);
    else
      storage = THStorage_(newWithSize)(size);
    for(i = 1; i <= size; i++)
    {
      lua_rawgeti(L, index, i);
      if(!lua_isnumber(L, -1))
      {
        THStorage_(free)(storage);
        luaL_error(L, "element at index %d is not a number", i);
      }
      THStorage_(set)(storage, i-1, LUA_NUMBER_TO_REAL(lua_tonumber(L, -1)));
      lua_pop(L, 1);
    }
  }
  else if(lua_type(L, index) == LUA_TUSERDATA)
  {
    if (allocator)
      THError("Passing allocator not supported when using storage views");

    THStorage *src = luaT_checkudata(L, index, torch_Storage);
    real *ptr = src->data;
    ptrdiff_t offset = luaL_optinteger(L, index + 1, 1) - 1;
    if (offset < 0 || offset >= src->size) {
      luaL_error(L, "offset out of bounds");
    }
    ptrdiff_t size = luaL_optinteger(L, index + 2, src->size - offset);
    if (size < 1 || size > (src->size - offset)) {
      luaL_error(L, "size out of bounds");
    }
    storage = THStorage_(newWithData)(ptr + offset, size);
    storage->flag = TH_STORAGE_REFCOUNTED | TH_STORAGE_VIEW;
    storage->view = src;
    THStorage_(retain)(storage->view);
  }
  else if(lua_type(L, index + 1) == LUA_TNUMBER)
  {
    ptrdiff_t size = luaL_optinteger(L, index, 0);
    real *ptr = (real *)luaL_optinteger(L, index + 1, 0);
    if (allocator)
      storage = THStorage_(newWithDataAndAllocator)(ptr, size, allocator, NULL);
    else
      storage = THStorage_(newWithData)(ptr, size);
    storage->flag = TH_STORAGE_REFCOUNTED;
  }
  else
  {
    ptrdiff_t size = luaL_optinteger(L, index, 0);
    if (allocator)
      storage = THStorage_(newWithAllocator)(size, allocator, NULL);
    else
      storage = THStorage_(newWithSize)(size);
  }
  luaT_pushudata(L, storage, torch_Storage);
  return 1;
}

static int torch_Storage_(retain)(lua_State *L)
{
  THStorage *storage = luaT_checkudata(L, 1, torch_Storage);
  THStorage_(retain)(storage);
  return 0;
}

static int torch_Storage_(free)(lua_State *L)
{
  THStorage *storage = luaT_checkudata(L, 1, torch_Storage);
  THStorage_(free)(storage);
  return 0;
}

static int torch_Storage_(resize)(lua_State *L)
{
  THStorage *storage = luaT_checkudata(L, 1, torch_Storage);
  ptrdiff_t size = luaL_checkinteger(L, 2);
/*  int keepContent = luaT_optboolean(L, 3, 0); */
  THStorage_(resize)(storage, size);/*, keepContent); */
  lua_settop(L, 1);
  return 1;
}

static int torch_Storage_(copy)(lua_State *L)
{
  THStorage *storage = luaT_checkudata(L, 1, torch_Storage);
  void *src;
  if( (src = luaT_toudata(L, 2, torch_Storage)) )
    THStorage_(copy)(storage, src);
  else if( (src = luaT_toudata(L, 2, "torch.ByteStorage")) )
    THStorage_(copyByte)(storage, src);
  else if( (src = luaT_toudata(L, 2, "torch.CharStorage")) )
    THStorage_(copyChar)(storage, src);
  else if( (src = luaT_toudata(L, 2, "torch.ShortStorage")) )
    THStorage_(copyShort)(storage, src);
  else if( (src = luaT_toudata(L, 2, "torch.IntStorage")) )
    THStorage_(copyInt)(storage, src);
  else if( (src = luaT_toudata(L, 2, "torch.LongStorage")) )
    THStorage_(copyLong)(storage, src);
  else if( (src = luaT_toudata(L, 2, "torch.FloatStorage")) )
    THStorage_(copyFloat)(storage, src);
  else if( (src = luaT_toudata(L, 2, "torch.DoubleStorage")) )
    THStorage_(copyDouble)(storage, src);
  else if( (src = luaT_toudata(L, 2, "torch.HalfStorage")) )
    THStorage_(copyHalf)(storage, src);
  else
    luaL_typerror(L, 2, "torch.*Storage");
  lua_settop(L, 1);
  return 1;
}

static int torch_Storage_(fill)(lua_State *L)
{
  THStorage *storage = luaT_checkudata(L, 1, torch_Storage);
  real value = luaG_(checkreal)(L, 2);
  THStorage_(fill)(storage, value);
  lua_settop(L, 1);
  return 1;
}

static int torch_Storage_(elementSize)(lua_State *L)
{
  luaT_pushinteger(L, THStorage_(elementSize)());
  return 1;
}

static int torch_Storage_(__len__)(lua_State *L)
{
  THStorage *storage = luaT_checkudata(L, 1, torch_Storage);
  luaT_pushinteger(L, storage->size);
  return 1;
}

static int torch_Storage_(__newindex__)(lua_State *L)
{
  if(lua_isnumber(L, 2))
  {
    THStorage *storage = luaT_checkudata(L, 1, torch_Storage);
    ptrdiff_t index = luaL_checkinteger(L, 2) - 1;
    real number = luaG_(checkreal)(L, 3);
    THStorage_(set)(storage, index, number);
    lua_pushboolean(L, 1);
  }
  else
    lua_pushboolean(L, 0);

  return 1;
}

static int torch_Storage_(__index__)(lua_State *L)
{
  if(lua_isnumber(L, 2))
  {
    THStorage *storage = luaT_checkudata(L, 1, torch_Storage);
    ptrdiff_t index = luaL_checkinteger(L, 2) - 1;
    luaG_(pushreal)(L, THStorage_(get)(storage, index));
    lua_pushboolean(L, 1);
    return 2;
  }
  else
  {
    lua_pushboolean(L, 0);
    return 1;
  }
}

#if defined(TH_REAL_IS_CHAR) || defined(TH_REAL_IS_BYTE)
static int torch_Storage_(string)(lua_State *L)
{
  THStorage *storage = luaT_checkudata(L, 1, torch_Storage);
  if(lua_isstring(L, -1))
  {
    size_t len = 0;
    const char *str = lua_tolstring(L, -1, &len);
    THStorage_(resize)(storage, len);
    memmove(storage->data, str, len);
    lua_settop(L, 1);
  }
  else
    lua_pushlstring(L, (char*)storage->data, storage->size);

  return 1; /* either storage or string */
}
#endif

static int torch_Storage_(text)(lua_State *L)
{
  THStorage *storage = luaT_checkudata(L, 1, torch_Storage);
  struct _rspamd_lua_text *t;

  if(lua_type(L, -1) == LUA_TUSERDATA)
  {
    t = lua_touserdata(L, -1);
    THStorage_(resize)(storage, t->len);
    memmove(storage->data, t->start, t->len);
    lua_settop(L, 1);
  }
  else {
    t = lua_newuserdata (L, sizeof (*t));
    t->start = (const char *)storage->data;
    t->len = t->len;
    t->flags = 0;
  }

  return 1; /* either storage or string */
}

static int torch_Storage_(totable)(lua_State *L)
{
  THStorage *storage = luaT_checkudata(L, 1, torch_Storage);
  ptrdiff_t i;

  lua_newtable(L);
  for(i = 0; i < storage->size; i++)
  {
    luaG_(pushreal)(L, storage->data[i]);
    lua_rawseti(L, -2, i+1);
  }
  return 1;
}

static int torch_Storage_(factory)(lua_State *L)
{
  THStorage *storage = THStorage_(new)();
  luaT_pushudata(L, storage, torch_Storage);
  return 1;
}

static int torch_Storage_(write)(lua_State *L)
{
  THStorage *storage = luaT_checkudata(L, 1, torch_Storage);
  THFile *file = luaT_checkudata(L, 2, "torch.File");

#ifdef DEBUG
  THAssert(storage->size < LONG_MAX);
#endif
  THFile_writeLongScalar(file, storage->size);
  THFile_writeRealRaw(file, storage->data, storage->size);

  return 0;
}

static int torch_Storage_(read)(lua_State *L)
{
  THStorage *storage = luaT_checkudata(L, 1, torch_Storage);
  THFile *file = luaT_checkudata(L, 2, "torch.File");
  ptrdiff_t size = THFile_readLongScalar(file);

  THStorage_(resize)(storage, size);
  THFile_readRealRaw(file, storage->data, storage->size);

  return 0;
}

static const struct luaL_Reg torch_Storage_(_) [] = {
  {"retain", torch_Storage_(retain)},
  {"free", torch_Storage_(free)},
  {"size", torch_Storage_(__len__)},
  {"elementSize", torch_Storage_(elementSize)},
  {"__len__", torch_Storage_(__len__)},
  {"__newindex__", torch_Storage_(__newindex__)},
  {"__index__", torch_Storage_(__index__)},
  {"resize", torch_Storage_(resize)},
  {"fill", torch_Storage_(fill)},
  {"copy", torch_Storage_(copy)},
  {"totable", torch_Storage_(totable)},
  {"write", torch_Storage_(write)},
  {"read", torch_Storage_(read)},
  {"text", torch_Storage_(text)},
#if defined(TH_REAL_IS_CHAR) || defined(TH_REAL_IS_BYTE)
  {"string", torch_Storage_(string)},
#endif
  {NULL, NULL}
};

void torch_Storage_(init)(lua_State *L)
{
  luaT_newmetatable(L, torch_Storage, NULL,
                    torch_Storage_(new), torch_Storage_(free), torch_Storage_(factory));
  luaT_setfuncs(L, torch_Storage_(_), 0);
  lua_pop(L, 1);
}

#endif
