#include "api.h"
#include "data/blob.h"
#include "util.h"

static int l_lovrBlobGetName(lua_State* L) {
  Blob* blob = luax_checktype(L, 1, Blob);
  lua_pushstring(L, blob->name);
  return 1;
}

static int l_lovrBlobGetPointer(lua_State* L) {
  Blob* blob = luax_checktype(L, 1, Blob);
  lua_pushlightuserdata(L, blob->data);
  return 1;
}

static int l_lovrBlobGetSize(lua_State* L) {
  Blob* blob = luax_checktype(L, 1, Blob);
  lua_pushinteger(L, blob->size);
  return 1;
}

static int l_lovrBlobGetString(lua_State* L) {
  Blob* blob = luax_checktype(L, 1, Blob);

  lua_Integer offset = luaL_optinteger(L, 2, 0);
  lovrCheck(offset >= 0, "Blob byte offset can not be negative");
  lovrCheck((size_t) offset < blob->size, "Blob byte offset must be less than the size of the Blob");

  lua_Integer length = luaL_optinteger(L, 3, blob->size - offset);
  lovrCheck(length >= 0, "Length can not be negative");
  lovrCheck((size_t) length <= blob->size - offset, "Blob:getString range overflows the size of the Blob");

  lua_pushlstring(L, (char*) blob->data + offset, (size_t) length);
  return 1;
}

#define l_lovrBlobGet(L, T)\
  Blob* blob = luax_checktype(L, 1, Blob);\
  lua_Integer offset = luaL_optinteger(L, 2, 0);\
  lovrCheck(offset >= 0, "Blob byte offset can not be negative");\
  lovrCheck((size_t) offset < blob->size, "Blob byte offset must be less than the size of the Blob");\
  lua_Integer count = luaL_optinteger(L, 3, 1);\
  lovrCheck(count > 0, "Count must be greater than zero");\
  lovrCheck((size_t) count * sizeof(T) <= blob->size - (size_t) offset, "Byte range overflows the size of the Blob");\
  const T* data = (const T*) ((char*) blob->data + offset);\
  for (lua_Integer i = 0; i < count; i++) lua_pushnumber(L, (lua_Number) data[i]);\
  return count;

static int l_lovrBlobGetI8(lua_State* L) { l_lovrBlobGet(L, int8_t); }
static int l_lovrBlobGetU8(lua_State* L) { l_lovrBlobGet(L, uint8_t); }
static int l_lovrBlobGetI16(lua_State* L) { l_lovrBlobGet(L, int16_t); }
static int l_lovrBlobGetU16(lua_State* L) { l_lovrBlobGet(L, uint16_t); }
static int l_lovrBlobGetI32(lua_State* L) { l_lovrBlobGet(L, int32_t); }
static int l_lovrBlobGetU32(lua_State* L) { l_lovrBlobGet(L, uint32_t); }
static int l_lovrBlobGetF32(lua_State* L) { l_lovrBlobGet(L, float); }
static int l_lovrBlobGetF64(lua_State* L) { l_lovrBlobGet(L, double); }

const luaL_Reg lovrBlob[] = {
  { "getName", l_lovrBlobGetName },
  { "getPointer", l_lovrBlobGetPointer },
  { "getSize", l_lovrBlobGetSize },
  { "getString", l_lovrBlobGetString },
  { "getI8", l_lovrBlobGetI8 },
  { "getU8", l_lovrBlobGetU8 },
  { "getI16", l_lovrBlobGetI16 },
  { "getU16", l_lovrBlobGetU16 },
  { "getI32", l_lovrBlobGetI32 },
  { "getU32", l_lovrBlobGetU32 },
  { "getF32", l_lovrBlobGetF32 },
  { "getF64", l_lovrBlobGetF64 },
  { NULL, NULL }
};
