/** \file
 * \brief IM Lua 5 Binding
 *
 * See Copyright Notice in im_lib.h
 */

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

#include "im.h"
#include "im_raw.h"
#include "im_image.h"
#include "im_util.h"
#include "im_palette.h"

#include <lua.h>
#include <lauxlib.h>

#include "imlua.h"
#include "imlua_aux.h"
#include "imlua_image.h"
#include "imlua_palette.h"



static imFile** imlua_rawcheckfile(lua_State *L, int param)
{
  return (imFile**)luaL_checkudata(L, param, "imFile");
}

static imFile* imlua_checkfile (lua_State *L, int param)
{
  imFile** ifile_p = imlua_rawcheckfile(L, param);

  if (!(*ifile_p))
    luaL_argerror(L, param, "closed imFile");

  return *ifile_p;
}

static int imlua_pushifileerror(lua_State *L, imFile* ifile, int error)
{
  if (error)
  {
    lua_pushnil(L);
    imlua_pusherror(L, error);
    return 2;
  }
  else
  {
    imFile** ifile_p = (imFile**) lua_newuserdata(L, sizeof(imFile*));
    *ifile_p = ifile;
    luaL_getmetatable(L, "imFile");
    lua_setmetatable(L, -2);
    return 1;
  }
}


/*****************************************************************************\
 im.FileOpen(filename)
\*****************************************************************************/
static int imluaFileOpen (lua_State *L)
{
  const char *filename = luaL_checkstring(L, 1);
  int error;
  imFile *ifile = imFileOpen(filename, &error);
  return imlua_pushifileerror(L, ifile, error);
}

/*****************************************************************************\
 im.FileOpenAs(filename)
\*****************************************************************************/
static int imluaFileOpenAs (lua_State *L)
{
  const char *filename = luaL_checkstring(L, 1);
  const char *format = imlua_checkformat(L, 2);
  int error;
  imFile *ifile = imFileOpenAs(filename, format, &error);
  return imlua_pushifileerror(L, ifile, error);
}

/*****************************************************************************\
 im.FileOpenRaw(filename)
\*****************************************************************************/
static int imluaFileOpenRaw (lua_State *L)
{
  const char *filename = luaL_checkstring(L, 1);
  int error;
  imFile *ifile = imFileOpenRaw(filename, &error);
  return imlua_pushifileerror(L, ifile, error);
}

/*****************************************************************************\
 im.FileNew(filename, format)
\*****************************************************************************/
static int imluaFileNew (lua_State *L)
{
  const char *filename = luaL_checkstring(L, 1);
  const char *format = imlua_checkformat(L, 2);
  int error;

  imFile *ifile = imFileNew(filename, format, &error);
  return imlua_pushifileerror(L, ifile, error);
}

/*****************************************************************************\
 im.FileNewRaw(filename)
\*****************************************************************************/
static int imluaFileNewRaw (lua_State *L)
{
  const char *filename = luaL_checkstring(L, 1);
  int error;
  imFile *ifile = imFileNewRaw(filename, &error);
  return imlua_pushifileerror(L, ifile, error);
}

/*****************************************************************************\
 file:Handle()
\*****************************************************************************/
static int imluaFileHandle (lua_State *L)
{
  lua_pushlightuserdata(L, imFileHandle(imlua_checkfile(L, 1), luaL_checkinteger(L, 2)));
  return 1;
}

/*****************************************************************************\
 file:LoadImage()
\*****************************************************************************/
static int imluaFileLoadImage (lua_State *L)
{
  imFile *ifile = imlua_checkfile(L, 1);
  int index = luaL_optinteger(L, 2, 0);
  int error;
  imImage *image = imFileLoadImage(ifile, index, &error);
  return imlua_pushimageerror(L, image, error);
}

/*****************************************************************************\
 file:LoadImageFrame()
\*****************************************************************************/
static int imluaFileLoadImageFrame (lua_State *L)
{
  imFile *ifile = imlua_checkfile(L, 1);
  int index = luaL_checkinteger(L, 2);
  imImage *image = imlua_checkimage(L, 3);
  int error;

  imFileLoadImageFrame(ifile, index, image, &error);
  imlua_pusherror(L, error);

  return 1;
}

/*****************************************************************************\
 file:LoadImageRegion()
\*****************************************************************************/
static int imluaFileLoadImageRegion (lua_State *L)
{
  imFile *ifile = imlua_checkfile(L, 1);
  int index = luaL_checkinteger(L, 2);
  int bitmap = luaL_checkinteger(L, 3);
  int xmin = luaL_checkinteger(L, 4);
  int xmax = luaL_checkinteger(L, 5);
  int ymin = luaL_checkinteger(L, 6);
  int ymax = luaL_checkinteger(L, 7);
  int width = luaL_checkinteger(L, 8);
  int height = luaL_checkinteger(L, 9);
  int error;
  imImage *image = imFileLoadImageRegion(ifile, index, bitmap, &error, xmin, xmax, ymin, ymax, width, height);
  return imlua_pushimageerror(L, image, error);
}

/*****************************************************************************\
 file:LoadBitmap()
\*****************************************************************************/
static int imluaFileLoadBitmap (lua_State *L)
{
  imFile *ifile = imlua_checkfile(L, 1);
  int index = luaL_optinteger(L, 2, 0);
  int error;
  imImage *image = imFileLoadBitmap(ifile, index, &error);
  return imlua_pushimageerror(L, image, error);
}

/*****************************************************************************\
 file:LoadBitmapFrame()
\*****************************************************************************/
static int imluaFileLoadBitmapFrame (lua_State *L)
{
  imFile *ifile = imlua_checkfile(L, 1);
  int index = luaL_checkinteger(L, 2);
  imImage *image = imlua_checkimage(L, 3);
  int error;

  imFileLoadBitmapFrame(ifile, index, image, &error);
  imlua_pusherror(L, error);

  return 1;
}

/*****************************************************************************\
 file:SaveImage()
\*****************************************************************************/
static int imluaFileSaveImage (lua_State *L)
{
  imFile *ifile = imlua_checkfile(L, 1);
  imImage *image = imlua_checkimage(L, 2);

  imlua_pusherror(L, imFileSaveImage(ifile, image));
  return 1;
}

/*****************************************************************************\
 file:GetInfo()
\*****************************************************************************/
static int imluaFileGetInfo (lua_State *L)
{
  int image_count;
  char format[10];
  char compression[20];

  imFile *ifile = imlua_checkfile(L, 1);

  imFileGetInfo(ifile, format, compression, &image_count);

  lua_pushstring(L, format);
  lua_pushstring(L, compression);
  lua_pushnumber(L, image_count);

  return 3;
}

/*****************************************************************************\
 file:SetInfo()
\*****************************************************************************/
static int imluaFileSetInfo (lua_State *L)
{
  imFile *ifile = imlua_checkfile(L, 1);
  const char *compression = luaL_checkstring(L, 2);

  imFileSetInfo(ifile, compression);

  return 0;
}

/*****************************************************************************\
 file:SetAttribute(attrib, data_type, data)
\*****************************************************************************/
static int imluaFileSetAttribute (lua_State *L)
{
  int i, count = 0;
  void *data = NULL;

  imFile *ifile = imlua_checkfile(L, 1);
  const char *attrib = luaL_checkstring(L, 2);
  int data_type = luaL_checkinteger(L, 3);

  if (!lua_isnil(L, 4))
  {
    if (!lua_isstring(L, 4))
    {
      luaL_checktype(L, 4, LUA_TTABLE);
      count = imlua_getn(L, 4);
      data = malloc(imDataTypeSize(data_type) * count); 
    } 
    else if (data_type != IM_BYTE) 
      luaL_argerror(L, 4, "if value is string, then data type must be byte"); 

    switch (data_type)
    {
    case IM_BYTE:
      {
        if (lua_isstring(L, 4))
        {
          const char* str = lua_tostring(L, 4);
          count = (int)strlen(str)+1;
          data = malloc(imDataTypeSize(data_type) * count);
          memcpy(data, str, count);
        }
        else
        {
          imbyte *d = (imbyte*) data;
          for (i = 0; i < count; i++)
          {
            lua_rawgeti(L, 4, i+1);
            d[i] = (imbyte) luaL_checkinteger(L, -1);
            lua_pop(L, 1);
          }
        }
      }
      break;

    case IM_SHORT:
      {
        short *d = (short*) data;
        for (i = 0; i < count; i++)
        {
          lua_rawgeti(L, 4, i+1);
          d[i] = (short) luaL_checkinteger(L, -1);
          lua_pop(L, 1);
        }
      }
      break;

    case IM_USHORT:
      {
        imushort *d = (imushort*) data;
        for (i = 0; i < count; i++)
        {
          lua_rawgeti(L, 4, i+1);
          d[i] = (imushort) luaL_checkinteger(L, -1);
          lua_pop(L, 1);
        }
      }
      break;

    case IM_INT:
      {
        int *d = (int*) data;
        for (i = 0; i < count; i++)
        {
          lua_rawgeti(L, 4, i+1);
          d[i] = luaL_checkinteger(L, -1);
          lua_pop(L, 1);
        }
      }
      break;

    case IM_FLOAT:
      {
        float *d = (float*) data;
        for (i = 0; i < count; i++)
        {
          lua_rawgeti(L, 4, i+1);
          d[i] = (float) luaL_checknumber(L, -1);
          lua_pop(L, 1);
        }
      }
      break;

    case IM_CFLOAT:
      {
        float *data_float = (float*) data;
        for (i = 0; i < count; i++)
        {
          int two;
          float *value = imlua_toarrayfloat(L, -1, &two, 1);
          if (two != 2)
          {
            free(value);
            luaL_argerror(L, 4, "invalid value");
          }

          data_float[i] = value[0];
          data_float[i+1] = value[1];
          free(value);
          lua_pop(L, 1);
        }        
      }
      break;
    case IM_DOUBLE:
      {
        double *d = (double*) data;
        for (i = 0; i < count; i++)
        {
          lua_rawgeti(L, 4, i+1);
          d[i] = (double)luaL_checknumber(L, -1);
          lua_pop(L, 1);
        }
      }
      break;

    case IM_CDOUBLE:
      {
        double *data_double = (double*) data;
        for (i = 0; i < count; i++)
        {
          int two;
          double *value = imlua_toarraydouble(L, -1, &two, 1);
          if (two != 2)
          {
            free(value);
            luaL_argerror(L, 4, "invalid value");
          }

          data_double[i] = value[0];
          data_double[i+1] = value[1];
          free(value);
          lua_pop(L, 1);
        }        
      }
      break;
    }
  }

  imFileSetAttribute(ifile, attrib, data_type, count, data);
  return 0;
}

static int imluaFileSetAttribInteger(lua_State *L)
{
  imFile *ifile = imlua_checkfile(L, 1);
  const char *attrib = luaL_checkstring(L, 2);
  int data_type = luaL_checkinteger(L, 3);
  int value = luaL_checkinteger(L, 4);
  imFileSetAttribInteger(ifile, attrib, data_type, value);
  return 0;
}

static int imluaFileSetAttribReal(lua_State *L)
{
  imFile *ifile = imlua_checkfile(L, 1);
  const char *attrib = luaL_checkstring(L, 2);
  int data_type = luaL_checkinteger(L, 3);
  double value = luaL_checknumber(L, 4);
  imFileSetAttribReal(ifile, attrib, data_type, value);
  return 0;
}

static int imluaFileSetAttribString(lua_State *L)
{
  imFile *ifile = imlua_checkfile(L, 1);
  const char *attrib = luaL_checkstring(L, 2);
  const char* value = luaL_checkstring(L, 3);
  imFileSetAttribString(ifile, attrib, value);
  return 0;
}

/*****************************************************************************\
 file:GetAttribute(attrib)
\*****************************************************************************/
static int imluaFileGetAttribute (lua_State *L)
{
  int data_type;
  int i, count;
  const void *data;
  int as_string = 0;

  imFile *ifile = imlua_checkfile(L, 1);
  const char *attrib = luaL_checkstring(L, 2);

  data = imFileGetAttribute(ifile, attrib, &data_type, &count);
  if (!data)
  {
    lua_pushnil(L);
    return 1;
  }

  if (data_type == IM_BYTE && lua_isboolean(L, 3))
    as_string = lua_toboolean(L, 3);

  if (!as_string)
    lua_newtable(L);

  switch (data_type)
  {
  case IM_BYTE:
    {
      if (as_string)
      {
        lua_pushstring(L, (const char*)data);
      }
      else
      {
        imbyte *data_byte = (imbyte*) data;
        for (i = 0; i < count; i++, data_byte++)
        {
          lua_pushnumber(L, *data_byte);
          lua_rawseti(L, -2, i+1);
        }
      }
    }
    break;

  case IM_SHORT:
    {
      short *data_short = (short*) data;
      for (i = 0; i < count; i++, data_short += 2)
      {
        lua_pushnumber(L, *data_short);
        lua_rawseti(L, -2, i+1);
      }
    }
    break;

  case IM_USHORT:
    {
      imushort *data_ushort = (imushort*) data;
      for (i = 0; i < count; i++, data_ushort += 2)
      {
        lua_pushnumber(L, *data_ushort);
        lua_rawseti(L, -2, i+1);
      }
    }
    break;

  case IM_INT:
    {
      int *data_int = (int*) data;
      for (i = 0; i < count; i++, data_int++)
      {
        lua_pushnumber(L, *data_int);
        lua_rawseti(L, -2, i+1);
      }
    }
    break;

  case IM_FLOAT:
    {
      float *data_float = (float*) data;
      for (i = 0; i < count; i++, data_float++)
      {
        lua_pushnumber(L, *data_float);
        lua_rawseti(L, -2, i+1);
      }
    }
    break;

  case IM_CFLOAT:
    {
      float *data_float = (float*) data;
      for (i = 0; i < count; i++, data_float += 2)
      {
        imlua_newarrayfloat(L, data_float, 2, 1);
        lua_rawseti(L, -2, i+1);
      }        
    }
    break;

  case IM_DOUBLE:
    {
      double *data_double = (double*) data;
      for (i = 0; i < count; i++, data_double++)
      {
        lua_pushnumber(L, *data_double);
        lua_rawseti(L, -2, i+1);
      }
    }
    break;

  case IM_CDOUBLE:
    {
      double *data_double = (double*) data;
      for (i = 0; i < count; i++, data_double += 2)
      {
        imlua_newarraydouble(L, data_double, 2, 1);
        lua_rawseti(L, -2, i+1);
      }        
    }
    break;
  }

  lua_pushinteger(L, data_type);

  return 2;
}

static int imluaFileGetAttribInteger(lua_State *L)
{
  imFile *ifile = imlua_checkfile(L, 1);
  const char *attrib = luaL_checkstring(L, 2);
  int index = luaL_optinteger(L, 3, 0);
  int value = imFileGetAttribInteger(ifile, attrib, index);
  lua_pushinteger(L, value);
  return 1;
}

static int imluaFileGetAttribReal(lua_State *L)
{
  imFile *ifile = imlua_checkfile(L, 1);
  const char *attrib = luaL_checkstring(L, 2);
  int index = luaL_optinteger(L, 3, 0);
  double value = imFileGetAttribReal(ifile, attrib, index);
  lua_pushnumber(L, value);
  return 1;
}

static int imluaFileGetAttribString(lua_State *L)
{
  imFile *ifile = imlua_checkfile(L, 1);
  const char *attrib = luaL_checkstring(L, 2);
  const char *value = imFileGetAttribString(ifile, attrib);
  lua_pushstring(L, value);
  return 1;
}

/*****************************************************************************\
 file:GetAttributeList()
\*****************************************************************************/
static int imluaFileGetAttributeList (lua_State *L)
{
  int i, attrib_count;
  char **attrib;

  imFile* ifile = imlua_checkfile(L, 1);

  imFileGetAttributeList(ifile, NULL, &attrib_count);

  attrib = (char**) malloc(attrib_count * sizeof(char*));

  imFileGetAttributeList(ifile, attrib, &attrib_count);

  lua_createtable(L, attrib_count, 0);
  for (i = 0; i < attrib_count; i++)
  {
    lua_pushstring(L, attrib[i]);
    lua_rawseti(L, -2, i+1);
  }

  return 1;
}

/*****************************************************************************\
 file:GetPalette()
\*****************************************************************************/
static int imluaFileGetPalette (lua_State *L)
{
  imFile *ifile = imlua_checkfile(L, 1);
  long* palette = imPaletteNew(256);
  int count;
  imFileGetPalette(ifile, palette, &count);
  imlua_pushpalette(L, palette, count);
  return 1;
}

/*****************************************************************************\
 file:SetPalette(pal)
\*****************************************************************************/
static int imluaFileSetPalette (lua_State *L)
{
  imFile *ifile = imlua_checkfile(L, 1);
  imluaPalette *pal = imlua_checkpalette(L, 2);
  imFileSetPalette(ifile, pal->color, pal->count);
  return 0;
}

/*****************************************************************************\
 file:ReadImageInfo()
\*****************************************************************************/
static int imluaFileReadImageInfo (lua_State *L)
{
  int width, height;
  int file_color_mode, file_data_type;
  int error;

  imFile *ifile = imlua_checkfile(L, 1);
  int index = luaL_optinteger(L, 2, 0);

  error = imFileReadImageInfo(ifile, index, &width, &height, &file_color_mode, &file_data_type);

  imlua_pusherror(L, error);
  if (error)
    return 1;

  lua_pushnumber(L, width);
  lua_pushnumber(L, height);
  lua_pushnumber(L, file_color_mode);
  lua_pushnumber(L, file_data_type);
  return 5;
}

/*****************************************************************************\
 file:WriteImageInfo(width, height, user_color_mode, user_data_type)
\*****************************************************************************/
static int imluaFileWriteImageInfo (lua_State *L)
{
  imFile *ifile = imlua_checkfile(L, 1);
  int width = luaL_checkinteger(L, 2);
  int height = luaL_checkinteger(L, 3);
  int user_color_mode = luaL_checkinteger(L, 4);
  int user_data_type = luaL_checkinteger(L, 5);

  imlua_pusherror(L, imFileWriteImageInfo(ifile, width, height, user_color_mode, user_data_type));
  return 1;
}

/*****************************************************************************\
 file:ReadImageData(data)
\*****************************************************************************/
static int imluaFileReadImageData (lua_State *L)
{
  imFile *ifile = imlua_checkfile(L, 1);
  void* data = lua_touserdata(L, 2);
  int convert2bitmap = lua_toboolean(L, 3);
  int color_mode_flags = luaL_checkinteger(L, 4);
  imlua_pusherror(L, imFileReadImageData(ifile, data, convert2bitmap, color_mode_flags));
  return 1;
}

/*****************************************************************************\
 file:WriteImageData(data)
\*****************************************************************************/
static int imluaFileWriteImageData (lua_State *L)
{
  imFile *ifile = imlua_checkfile(L, 1);
  void* data = lua_touserdata(L, 2);
  imlua_pusherror(L, imFileWriteImageData(ifile, data));
  return 1;
}

/*****************************************************************************\
 file:Close()
\*****************************************************************************/
static int imluaFileClose (lua_State *L)
{
  imFile** ifile_p = imlua_rawcheckfile(L, 1);
  if (!(*ifile_p))
    luaL_argerror(L, 1, "closed imFile");

  imFileClose(*ifile_p);
  *ifile_p = NULL;  /* mark as closed */
  return 0;
}

/*****************************************************************************\
 gc
\*****************************************************************************/
static int imluaFile_gc (lua_State *L)
{
  imFile **ifile_p = (imFile **)lua_touserdata(L, 1);
  if (ifile_p && *ifile_p)
  {
    imFileClose(*ifile_p);
    *ifile_p = NULL;  /* mark as closed */
  }
  return 0;
}

/*****************************************************************************\
 tostring
\*****************************************************************************/
static int imluaFile_tostring (lua_State *L)
{
  imFile **ifile_p = (imFile **)lua_touserdata(L, 1);
  lua_pushfstring(L, "imFile(%p)%s", ifile_p, (*ifile_p)? "": "-closed");
  return 1;
}

/*****************************************************************************\
\*****************************************************************************/
static const luaL_Reg imfile_lib[] = {
  {"FileOpen", imluaFileOpen},
  {"FileOpenAs", imluaFileOpenAs},
  {"FileOpenRaw", imluaFileOpenRaw},
  {"FileNew", imluaFileNew},
  {"FileNewRaw", imluaFileNewRaw},
  {"FileClose", imluaFileClose},
  {NULL, NULL}
};

static const luaL_Reg imfile_metalib[] = {
  {"Handle", imluaFileHandle},
  {"Close", imluaFileClose},
  {"LoadImage", imluaFileLoadImage},
  {"LoadImageFrame", imluaFileLoadImageFrame},
  {"LoadImageRegion", imluaFileLoadImageRegion},
  {"LoadBitmap", imluaFileLoadBitmap},
  {"LoadBitmapFrame", imluaFileLoadBitmapFrame},
  {"SaveImage", imluaFileSaveImage},
  {"GetInfo", imluaFileGetInfo},
  {"SetInfo", imluaFileSetInfo},
  {"SetAttribute", imluaFileSetAttribute},
  {"SetAttribInteger", imluaFileSetAttribInteger},
  {"SetAttribReal", imluaFileSetAttribReal},
  {"SetAttribString", imluaFileSetAttribString},
  {"GetAttribute", imluaFileGetAttribute},
  {"GetAttributeList", imluaFileGetAttributeList},
  {"GetAttribInteger", imluaFileGetAttribInteger},
  {"GetAttribReal", imluaFileGetAttribReal},
  {"GetAttribString", imluaFileGetAttribString},
  {"GetPalette", imluaFileGetPalette},
  {"SetPalette", imluaFileSetPalette},
  {"ReadImageInfo", imluaFileReadImageInfo},
  {"WriteImageInfo", imluaFileWriteImageInfo},
  {"ReadImageData", imluaFileReadImageData},
  {"WriteImageData", imluaFileWriteImageData},

  {"__gc", imluaFile_gc},
  {"__tostring", imluaFile_tostring},

  {NULL, NULL}
};

static void createmeta (lua_State *L) 
{
  /* Object Oriented Access */
  luaL_newmetatable(L, "imFile");  /* create new metatable for imFile handles */
  lua_pushliteral(L, "__index");
  lua_pushvalue(L, -2);  /* push metatable */
  lua_rawset(L, -3);  /* metatable.__index = metatable */
  imlua_register_funcs(L, imfile_metalib);  /* register methods */
  lua_pop(L, 1);  /* removes the metatable from the top of the stack */
}

void imlua_open_file (lua_State *L)
{
  /* im table is at the top of the stack */
  createmeta(L);
  imlua_register_funcs(L, imfile_lib);
}
