/** \file
* \brief IUP binding for Lua 5.
*
* See Copyright Notice in "iup.h"
*/

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

#include "iup.h"
#include "iup_str.h"
#include "iup_object.h"

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

#include "iuplua.h"
#include "il.h"


static int CopyUserData2String(lua_State *L)
{
  void* udata = lua_touserdata(L, 1);
  size_t size = luaL_checkinteger(L, 2);
  char* str = malloc(size);
  memcpy(str, udata, size);  /* buffer must contain the terminator */
  lua_pushlstring(L, str, size - 1); /* len */
  free(str);
  return 1;
}

static int CopyString2UserData(lua_State *L)
{
  size_t len;
  const char* str = luaL_checklstring(L, 1, &len);
  void* udata = lua_touserdata(L, 2);
  size_t size = luaL_checkinteger(L, 3);
  if (len >= size) len = size-1;
  memcpy(udata, str, len);
  ((char*)udata)[len] = 0;
  return 0;
}

static int StringCompare(lua_State *L)
{
  const char* str1 = luaL_optstring(L, 1, NULL);
  const char* str2 = luaL_optstring(L, 2, NULL);
  int casesensitive = (int)luaL_optinteger(L, 3, 0);
  int lexicographic = (int)luaL_optinteger(L, 4, 1);
  int ret = IupStringCompare(str1, str2, casesensitive, lexicographic);
  lua_pushinteger(L, ret);
  return 1;
}

static int StringChangeCase(lua_State *L)
{
  int utf8 = IupGetInt(NULL, "UTF8MODE");
  const char* str = luaL_checkstring(L, 1);
  char* dst_str = iupStrDup(str);
  const char* flag = luaL_checkstring(L, 2);
  int case_flag = IUP_CASE_UPPER;
  if (iupStrEqualNoCase(flag, "UPPER"))
    case_flag = IUP_CASE_UPPER;
  else if (iupStrEqualNoCase(flag, "LOWER"))
    case_flag = IUP_CASE_LOWER;
  else if (iupStrEqualNoCase(flag, "TOGGLE"))
    case_flag = IUP_CASE_TOGGLE;
  else if (iupStrEqualNoCase(flag, "TITLE"))
    case_flag = IUP_CASE_TITLE;
  iupStrChangeCase(dst_str, str, case_flag, utf8);
  lua_pushstring(L, dst_str);
  free(dst_str);
  return 1;
}



/*******************************  Error Handling *****************************************/


static int show_error_continue_action(Ihandle* ih)
{
  (void)ih;
  return IUP_CLOSE;
}

static int show_error_exit_action(Ihandle* ih)
{
  if (ih) /* just to avoid a warning at return */
    exit(EXIT_FAILURE);
  return IUP_DEFAULT;
}

static int show_error_copy_action(Ihandle* ih)
{
  Ihandle* multi_text = IupGetDialogChild(ih, "TEXT");
  IupSetAttribute(multi_text, "SELECTION", "ALL");
  IupSetAttribute(multi_text, "CLIPBOARD", "COPY");
  return IUP_DEFAULT;
}

IUPLUA_API void iuplua_show_error_message(const char *pname, const char* msg)
{
  Ihandle *multi_text, *lbl, *copy, *button, *box, *dlg, *abort, *buttonbox;
  char* value = IupGetGlobal("LUA_ERROR_LABEL");

  if (!pname) pname = "_@IUP_ERROR";

  lbl = IupLabel("_@IUP_LUAERROR");
  IupSetAttribute(lbl, "EXPAND", "HORIZONTAL");
  if (value) IupSetStrAttribute(lbl, "TITLE", value);

  copy = IupButton("_@IUP_COPY", NULL);
  IupSetStrAttribute(copy, "TIP", "_@IUP_COPYTOCLIPBOARD");
  IupSetStrAttribute(copy, "PADDING", IupGetGlobal("DEFAULTBUTTONPADDING"));
  IupSetCallback(copy, "ACTION", show_error_copy_action);

  button = IupButton("_@IUP_CONTINUE", NULL);
  IupSetStrAttribute(button, "PADDING", IupGetGlobal("DEFAULTBUTTONPADDING"));
  IupSetCallback(button, "ACTION", show_error_continue_action);

  abort = IupButton("_@IUP_EXIT", NULL);
  IupSetStrAttribute(abort, "PADDING", IupGetGlobal("DEFAULTBUTTONPADDING"));
  IupSetCallback(abort, "ACTION", show_error_exit_action);

  multi_text = IupMultiLine(NULL);
  IupSetAttribute(multi_text, "EXPAND", "YES");
  IupSetAttribute(multi_text, "READONLY", "YES");
  IupSetAttribute(multi_text, "FONT", "Courier, 12");
  IupSetAttribute(multi_text, "VISIBLELINES", "10");
  IupSetAttribute(multi_text, "VISIBLECOLUMNS", "50");
  IupSetAttribute(multi_text, "NAME", "TEXT");
  IupSetStrAttribute(multi_text, "VALUE", msg);

  buttonbox = IupHbox(copy, button, abort, NULL);
  IupSetAttribute(buttonbox, "GAP", "50");
  IupSetAttribute(IupNormalizer(button, abort, NULL), "NORMALIZE", "HORIZONTAL");

  box = IupVbox(lbl, multi_text, buttonbox, NULL);
  IupSetAttribute(box, "ALIGNMENT", "ACENTER");
  IupSetAttribute(box, "NMARGIN", "10x10");
  IupSetAttribute(box, "GAP", "10");

  dlg = IupDialog(box);

  IupSetStrAttribute(dlg, "TITLE", pname);
  IupSetAttribute(dlg, "MINBOX", "NO");
  IupSetAttribute(dlg, "MAXBOX", "NO");
  IupSetAttribute(dlg, "PARENTDIALOG", IupGetGlobal("PARENTDIALOG"));
  IupSetAttribute(dlg, "ICON", IupGetGlobal("ICON"));
  IupSetAttributeHandle(dlg, "DEFAULTESC", button);
  IupSetAttributeHandle(dlg, "DEFAULTENTER", button);
  IupSetAttributeHandle(dlg, "STARTFOCUS", button);

  IupPopup(dlg, IUP_CENTERPARENT, IUP_CENTERPARENT);

  IupDestroy(dlg);
}

static int il_error_message(lua_State *L)
{
  const char* msg = lua_tostring(L, 1);
  iuplua_show_error_message(NULL, msg);
  return 0;
}

static void show_error(lua_State *L, const char *msg)
{
  iuplua_push_name(L, "_ERRORMESSAGE");

  if (lua_isnil(L, -1))
  {
    /* Panic mode */
    iuplua_show_error_message(NULL, msg);
    return;
  }

  lua_pushstring(L, msg);
  lua_call(L, 1, 0);  /* iup._ERRORMESSAGE(msg) */
}


/**********************************************************/
/* report, traceback and docall were adapted from "lua.c" */

static int report (lua_State *L, int status)
{
  /* if there was an erro, and there is an error message on the stack */
  if (status != LUA_OK && !lua_isnil(L, -1)) 
  {
    const char *msg = lua_tostring(L, -1);
    if (msg == NULL) msg = "(error with no message)";
    show_error(L, msg);
    lua_pop(L, 1);  /* remove message */
  }
  return status;
}

#if LUA_VERSION_NUM	> 501
#if LUA_VERSION_NUM	> 502
static int traceback (lua_State *L) {
  const char *msg = lua_tostring(L, 1);
  if (msg == NULL) {  /* is error object not a string? */
    if (luaL_callmeta(L, 1, "__tostring") &&  /* does it have a metamethod */
        lua_type(L, -1) == LUA_TSTRING)  /* that produces a string? */
        return 1;  /* that is the message */
    else
      msg = lua_pushfstring(L, "(error object is a %s value)",
      luaL_typename(L, 1));
  }
  luaL_traceback(L, L, msg, 1);  /* append a standard traceback */
  return 1;  /* return the traceback */
}
#else
static int traceback(lua_State *L) {
  const char *msg = lua_tostring(L, 1);
  if (msg)
    luaL_traceback(L, L, msg, 1);
  else if (!lua_isnoneornil(L, 1)) {  /* is there an error object? */
    if (!luaL_callmeta(L, 1, "__tostring"))  /* try its 'tostring' metamethod */
      lua_pushliteral(L, "(no error message)");
  }
  return 1;
}
#endif
#else
static int traceback(lua_State *L) {
  if (!lua_isstring(L, 1))  /* 'message' not a string? */
    return 1;  /* keep it intact */
  lua_getfield(L, LUA_GLOBALSINDEX, "debug");
  if (!lua_istable(L, -1)) {
    lua_pop(L, 1);
    return 1;
  }
  lua_getfield(L, -1, "traceback");
  if (!lua_isfunction(L, -1)) {
    lua_pop(L, 2);
    return 1;
  }
  lua_pushvalue(L, 1);  /* pass error message */
  lua_pushinteger(L, 2);  /* skip this function and traceback */
  lua_call(L, 2, 1);  /* call debug.traceback */
  return 1;
}
#endif

static void push_tracefunc(lua_State *L)
{
  iuplua_get_env(L);
  lua_pushstring(L, "_TRACEBACK");
  lua_gettable(L, -2);
  lua_remove(L, -2);  /* remove global table from stack */

  if (lua_isnil(L, -1))
  {
    lua_pop(L, 1);
    lua_pushcfunction(L, traceback);  /* push traceback function */
  }
}

static int docall (lua_State *L, int narg, int nret) 
{
  int status;
  int base = lua_gettop(L) - narg;  /* function index */
  push_tracefunc(L);  /* push traceback function */
  lua_insert(L, base);  /* put it under chunk and args */
  status = lua_pcall(L, narg, nret, base);
  lua_remove(L, base);  /* remove traceback function */
  /* force a complete garbage collection in case of errors */
  if (status != 0) lua_gc(L, LUA_GCCOLLECT, 0);
  return status;
}

             /*************************************/
             /*              Utilities            */

IUPLUA_SDK_API void iuplua_push_name(lua_State *L, const char* name)
{
  /* push iup.name in stack */
  iuplua_get_env(L);
  lua_pushstring(L, name);
  lua_gettable(L, -2);
  lua_remove(L, -2);  /* remove global table from stack */
}

IUPLUA_API int iuplua_dofile(lua_State *L, const char *filename)
{
  int status = luaL_loadfile(L, filename);
  if (status == LUA_OK)
    status = docall(L, 0, LUA_MULTRET);
  else if (status == LUA_ERRFILE)
  {
    char *dir = getenv("IUPLUA_DIR");
    if (dir)
    {
      char* full_name = iupStrFileMakeFileName(dir, filename);
      if (full_name)
      {
        /* remove the error string from the stack, and try again */
        lua_remove(L, -1);

        status = luaL_loadfile(L, full_name);
        free(full_name);
        if (status == LUA_OK)
          status = docall(L, 0, LUA_MULTRET);
      }
    }
  }
  return report(L, status);
}

IUPLUA_API int iuplua_dostring(lua_State *L, const char *s, const char *chunk_name)
{
  int status = luaL_loadbuffer(L, s, strlen(s), chunk_name);
  if (status == LUA_OK)
    status = docall(L, 0, LUA_MULTRET);
  return report(L, status);
}

IUPLUA_API int iuplua_dobuffer(lua_State *L, const char *s, int len, const char *chunk_name)
{
  int status = luaL_loadbuffer(L, s, len, chunk_name);
  if (status == LUA_OK)
    status = docall(L, 0, LUA_MULTRET);
  return report(L, status);
}

static int il_dofile(lua_State *L)
{
  int old_top = lua_gettop(L);
  const char* filename = luaL_checkstring(L, 1);
  int status = iuplua_dofile(L, filename);
  if (status == LUA_OK)
  {
    int top = lua_gettop(L);
    return top - old_top;
  }
  else
    return 0;
}

static int il_dostring(lua_State *L)
{
  int old_top = lua_gettop(L);
  size_t size;
  const char* str = luaL_checklstring(L, 1, &size);
  int status = iuplua_dobuffer(L, str, (int)size, "=iup.dostring");
  if (status == LUA_OK)
  {
    int top = lua_gettop(L);
    return top - old_top;
  }
  else
    return 0;
}

IUPLUA_SDK_API Ihandle *iuplua_checkihandleornil(lua_State *L, int pos)
{
  if (lua_isnoneornil(L, pos))
    return NULL;
  else
    return iuplua_checkihandle(L, pos);
}

IUPLUA_API int iuplua_isihandle(lua_State *L, int pos)
{
  int ret = 0;
  if (lua_getmetatable(L, pos))   /* t2 = metatable(stack(pos)) */
  {
    /* TODO: luaL_getmetatable(L, "iupHandle"); */
    lua_pushstring(L, "iupHandle");
    lua_gettable(L, LUA_REGISTRYINDEX);  /* t = registry["iupHandle"] */

    if (lua_rawequal(L, -2, -1))   /* check (t2==t)? */
      ret = 1;

    lua_pop(L, 2);   /* Pop registry["iuphandle"] and the metatable */
  }

  return ret;
}

IUPLUA_API Ihandle* iuplua_checkihandle(lua_State *L, int pos)
{
  Ihandle* *ih = (Ihandle**)luaL_checkudata(L, pos, "iupHandle");

  if (!(*ih))
    luaL_argerror(L, pos, "destroyed iupHandle");

  if (!iupObjectCheck(*ih))
    luaL_argerror(L, pos, "invalid Lua object, destroyed iupHandle in C but not in Lua");

  return *ih;
}

#if 0
Ihandle* iuplua_checkihandle_OLD(lua_State *L, int pos)
{
  Ihandle* ih = NULL;

  if (lua_getmetatable(L, pos))   /* t2 = metatable(stack(pos)) */
  {
    /* TODO: luaL_getmetatable(L, "iupHandle"); */
    lua_pushstring(L, "iupHandle");
    lua_gettable(L, LUA_REGISTRYINDEX);  /* t = registry["iupHandle"] */

    if (lua_rawequal(L, -2, -1))   /* check (t2==t)? */
      ih = *(Ihandle**)lua_touserdata(L, pos);

    lua_pop(L, 2);   /* Pop registry["iupHandle"] and the metatable */
  }

  if (!ih)
  {
    const char *msg = lua_pushfstring(L, "iupHandle expected, got %s", luaL_typename(L, pos));
    luaL_argerror(L, pos, msg);
  }

  return ih;
}
#endif

IUPLUA_SDK_API void iuplua_pushihandle_raw(lua_State *L, Ihandle *ih)
{
  if (ih) 
  {
    Ihandle** new_pointer = (Ihandle**)lua_newuserdata(L, sizeof(Ihandle*));
    *new_pointer = ih;
  } 
  else 
    lua_pushnil(L);
}

IUPLUA_API void iuplua_pushihandle(lua_State *L, Ihandle *ih)
{
  if (ih) 
  {
    char* sref = IupGetAttribute(ih, "_IUPLUA_WIDGET_TABLE_REF");
    if (!sref)
    {
      /* was not created in Lua */

      iuplua_plugstate(L, ih);

      /* push iup.RegisterHandle */
      iuplua_push_name(L, "RegisterHandle");
      iuplua_pushihandle_raw(L, ih);
      lua_pushstring(L, IupGetClassName(ih));
      lua_call(L, 2, 1);  /* iup.RegisterHandle(ih, type) */
    }
    else
    {
      /* already created in Lua */
      iuplua_pushihandle_raw(L, ih);

      /* TODO: luaL_getmetatable(L, "iupHandle"); */
      lua_pushstring(L, "iupHandle");
      lua_gettable(L, LUA_REGISTRYINDEX);  /* t = registry["iupHandle"] */

      lua_setmetatable(L, -2);    /* metatable(ih) = t */
    }
  } 
  else 
    lua_pushnil(L);
}

static int il_destroy_cb(Ihandle* ih)
{
  /* called from IupDestroy. */
  char* sref = IupGetAttribute(ih, "_IUPLUA_WIDGET_TABLE_REF");
  if (sref)
  {
    lua_State *L = iuplua_getstate(ih);
    int ref = atoi(sref);

    /* removes the Ihandle* reference from the lua object */
    /* widget.ihandle = nil */
    lua_rawgeti(L, LUA_REGISTRYINDEX, ref);  /* push object */
    lua_pushstring(L, "ihandle");
    lua_pushnil(L);
    lua_settable(L, -3);
    lua_pop(L, 1);

    /* removes the association of the Ihandle* with the lua object */
    luaL_unref(L, LUA_REGISTRYINDEX, ref);  /* this is the complement of SetWidget */
    IupSetAttribute(ih, "_IUPLUA_WIDGET_TABLE_REF", NULL);

    sref = IupGetAttribute(ih, "_IUPLUA_STATE_THREAD");
    if (sref)
    {
      ref = atoi(sref);
      luaL_unref(L, LUA_REGISTRYINDEX, ref);
      IupSetAttribute(ih, "_IUPLUA_STATE_THREAD", NULL);
    }

    IupSetCallback(ih, "LDESTROY_CB", NULL);
  }

  return IUP_DEFAULT;
}

IUPLUA_SDK_API char** iuplua_checkstring_array(lua_State *L, int pos, int n)
{
  int i;
  char **v;

  luaL_checktype(L, pos, LUA_TTABLE);
  if (n == -1) 
    n = iuplua_getn(L, pos);
  else if (n != iuplua_getn(L, pos))
    luaL_argerror(L, pos, "Invalid number of elements (n!=count).");
  if (n<=0) luaL_argerror(L, pos, "Invalid number of elements (n<=0).");

  v = (char **) malloc (n*sizeof(char *));
  for(i=1; i<=n; i++)
  {
    lua_pushinteger(L,i);
    lua_gettable(L,pos);
    v[i-1] = (char*)lua_tostring(L, -1);
    lua_pop(L, 1);
  }
  return v;
}

IUPLUA_SDK_API int* iuplua_checkint_array(lua_State *L, int pos, int n)
{
  int i;
  int *v;

  luaL_checktype(L, pos, LUA_TTABLE);
  if (n == -1) 
    n = iuplua_getn(L, pos);
  else if (n != iuplua_getn(L, pos))
    luaL_argerror(L, pos, "Invalid number of elements (n!=count).");
  if (n<=0) luaL_argerror(L, pos, "Invalid number of elements (n<=0).");

  v = (int *) malloc (n*sizeof(int));
  for(i=1; i<=n; i++)
  {
    lua_pushinteger(L,i);
    lua_gettable(L,pos);
    v[i - 1] = (int)lua_tointeger(L, -1);
    lua_pop(L, 1);
  }
  return v;
}

IUPLUA_SDK_API float* iuplua_checkfloat_array(lua_State *L, int pos, int n)
{
  int i;
  float* v;

  luaL_checktype(L, pos, LUA_TTABLE);
  if (n == -1)
    n = iuplua_getn(L, pos);
  else if (n != iuplua_getn(L, pos))
    luaL_argerror(L, pos, "Invalid number of elements (n!=count).");
  if (n<=0) luaL_argerror(L, pos, "Invalid number of elements (n<=0).");
    
  v = (float *) malloc (n*sizeof(float));
  for(i=1; i<=n; i++)
  {
    lua_pushinteger(L,i);
    lua_gettable(L,pos);
    v[i-1] = (float)lua_tonumber(L, -1);
    lua_pop(L, 1);
  }
  return v;
}

IUPLUA_SDK_API double* iuplua_checkdouble_array(lua_State *L, int pos, int n)
{
  int i;
  double* v;

  luaL_checktype(L, pos, LUA_TTABLE);
  if (n == -1)
    n = iuplua_getn(L, pos);
  else if (n != iuplua_getn(L, pos))
    luaL_argerror(L, pos, "Invalid number of elements (n!=count).");
  if (n <= 0) luaL_argerror(L, pos, "Invalid number of elements (n<=0).");

  v = (double *)malloc(n*sizeof(double));
  for (i = 1; i <= n; i++)
  {
    lua_pushinteger(L, i);
    lua_gettable(L, pos);
    v[i - 1] = (double)lua_tonumber(L, -1);
    lua_pop(L, 1);
  }
  return v;
}

IUPLUA_SDK_API unsigned char* iuplua_checkuchar_array(lua_State *L, int pos, int n)
{
  int i;
  unsigned char *v;

  luaL_checktype(L, pos, LUA_TTABLE);
  if (n == -1)
    n = iuplua_getn(L, pos);
  else if (n != iuplua_getn(L, pos))
    luaL_argerror(L, pos, "Invalid number of elements (n!=count).");
  if (n<=0) luaL_argerror(L, pos, "Invalid number of elements (n<=0).");

  v = (unsigned char *) malloc (n*sizeof(unsigned char));
  for(i=1; i<=n; i++)
  {
    lua_pushinteger(L,i);
    lua_gettable(L,pos);
    v[i-1] = (unsigned char)lua_tointeger(L, -1);
    lua_pop(L, 1);
  }
  return v;
}

IUPLUA_SDK_API Ihandle ** iuplua_checkihandle_array(lua_State *L, int pos, int n)
{
  int i;
  Ihandle **v;

  luaL_checktype(L, pos, LUA_TTABLE);
  if (n == -1)
    n = iuplua_getn(L, pos);
  else if (n != iuplua_getn(L, pos))
    luaL_argerror(L, pos, "Invalid number of elements (n!=count).");
  if (n<=0) luaL_argerror(L, pos, "Invalid number of elements (n<=0).");

  v = (Ihandle **) malloc ((n+1)*sizeof(Ihandle *));
  for (i=1; i<=n; i++)
  {
    lua_pushinteger(L,i);
    lua_gettable(L,pos);
    v[i-1] = iuplua_checkihandle(L, -1);
    lua_pop(L, 1);
  }
  v[i-1] = NULL;
  return v;
}

             /*************************************/
             /*         used by callbacks         */

IUPLUA_SDK_API void iuplua_plugstate(lua_State *L, Ihandle *ih)
{
  IupSetAttribute(ih, "_IUPLUA_STATE_CONTEXT",(char *) L);

  if (IupGetGlobal("IUPLUA_THREADED"))
  {
    int ref;
    lua_pushthread(L);
    ref = luaL_ref(L, LUA_REGISTRYINDEX);   /* keep ref for L, L maybe a thread */
    IupSetInt(ih, "_IUPLUA_STATE_THREAD", ref);
  }
}

IUPLUA_SDK_API lua_State* iuplua_getstate(Ihandle *ih)
{
  return (lua_State *) IupGetAttribute(ih, "_IUPLUA_STATE_CONTEXT");
}

IUPLUA_SDK_API lua_State* iuplua_call_start(Ihandle *ih, const char* name)
{
  lua_State *L = iuplua_getstate(ih);

  /* prepare to call iup.CallMethod(name, ih, ...) */

  iuplua_push_name(L, "CallMethod");
  lua_pushstring(L, name);
  iuplua_pushihandle(L, ih);

  /* here there was 3 value pushed on the stack, 
     if this changes must update callback where LUA_MULTRET is used. */
  return L;
}

static lua_State* iuplua_call_global_start(const char* name)
{
  lua_State *L = (lua_State *) IupGetGlobal("_IUP_LUA_DEFAULT_STATE");

  /* prepare to call iup.CallGlobalMethod(name, ...) */

  iuplua_push_name(L, "CallGlobalMethod");
  lua_pushstring(L, name);

  return L;
}

IUPLUA_SDK_API int iuplua_call(lua_State* L, int nargs)
{
  int status = docall(L, nargs + 2, 1);  /* always 1 result */
  report(L, status);

  if (status != LUA_OK)
    return IUP_DEFAULT;
  else
  {
    int tmp = (int)lua_isnil(L, -1) ? IUP_DEFAULT : (int)lua_tointeger(L, -1);
    lua_pop(L, 1);  /* remove the result */
    return tmp;    
  }
}

int iuplua_call_global(lua_State* L, int nargs)
{
  return iuplua_call(L, nargs-1); /* remove the ih from the parameter count */
}

IUPLUA_SDK_API char* iuplua_call_ret_s(lua_State *L, int nargs)
{
  int status = docall(L, nargs + 2, 1);  /* always 1 result */
  report(L, status);

  if (status != LUA_OK)
    return NULL;
  else
  {
    char* tmp = lua_isnil(L, -1) ? NULL: (char*)lua_tostring(L,-1);
    lua_pop(L, 1);  /* remove the result */
    return tmp;    
  }
}

IUPLUA_SDK_API double iuplua_call_ret_d(lua_State *L, int nargs)
{
  int status = docall(L, nargs + 2, 1);  /* always 1 result */
  report(L, status);

  if (status != LUA_OK)
    return 0;
  else
  {
    double tmp = lua_isnil(L, -1) ? 0: (double)lua_tonumber(L,-1);
    lua_pop(L, 1);  /* remove the result */
    return tmp;    
  }
}

IUPLUA_SDK_API int iuplua_call_raw(lua_State* L, int nargs, int nresults)
{
  int status = docall(L, nargs, nresults);  /* always n results, or LUA_MULTRET */
  report(L, status);
  return status;
}

IUPLUA_SDK_API void iuplua_register_cb(lua_State *L, const char* name, lua_CFunction func, const char* type)
{
  iuplua_push_name(L, "RegisterCallback");

  lua_pushstring(L, name);
  lua_pushcfunction(L, func);
  lua_pushstring(L, type);
  lua_call(L, 3, 0);  /* iup.RegisterCallback(name, func, type) */
}

/* iup.SetCallback(ih, name, c_func, lua_func) */
static int SetCallback(lua_State *L)
{
  Icallback c_func;

  Ihandle* ih = iuplua_checkihandle(L, 1);
  const char* name = luaL_checkstring(L, 2);

  if (!lua_iscfunction(L, 3))
    luaL_argerror(L, 3, "Invalid function when set callback.");
  c_func = (Icallback)lua_tocfunction(L, 3);

  if (lua_isnil(L, 4))  /* lua_func is only used here to remove the callback */
    IupSetCallback(ih, name, (Icallback)NULL);
  else
    IupSetCallback(ih, name, c_func);

  /* lua_func, when not nil, has always the same name of a C callback in lowercase */

  return 0;
}

static int SetFunction(lua_State *L)
{
  const char* name = luaL_checkstring(L, 1);

  if (lua_isnil(L, 2))
    IupSetFunction(name, NULL);
  else
  {
    Icallback func;

    if (!lua_iscfunction(L, 2))
      luaL_argerror(L, 2, "must be a C function");

    func = (Icallback)lua_tocfunction(L, 2);
    IupSetFunction(name, func);
  }

  return 0;
}

             /*************************************/
             /*        metatable                  */

/* iup.NewClass(class_name) 
  Calls:
    iup.NewClass("iupHandle") -- this is an Ihandle* with enhancements
    iup.NewClass("iupWidget") -- this is a Lua object for control construction, see iup.WIDGET and iup.BOX
*/
static int NewClass(lua_State *L)
{
  lua_newtable(L);     /* push t */
  lua_pushstring(L, "class");  /* push "class" */
  lua_pushvalue(L, 1); /* push again the class_name to the stack, because settable will remove it */
  lua_settable(L, -3); /* t.class = class_name */
  lua_settable(L, LUA_REGISTRYINDEX);  /* registry[class_name] = t */
  return 0;
}

/* iup.SetClass(t, class_name) 
  Calls:
    iup.SetClass(ih, "iupHandle")  --Called only in iup.RegisterHandle and WIDGET.constructor
    iup.SetClass(widget, "iupWidget")  --Called whenever a new control class is created.
*/
static int SetClass(lua_State *L)
{
  lua_gettable(L, LUA_REGISTRYINDEX);  /* t2 = registry[class_name] */
  if (lua_isnil(L, -1))
  {
    lua_pushstring(L, "Invalid class name.");
    lua_error(L);
  }
  lua_setmetatable(L, -2);  /* metatable(t) = t2 */
  return 0;
}

/* class_name = iup.GetClass(t) */
static int GetClass(lua_State *L)
{
  if (lua_istable(L, 1) || lua_isuserdata(L, 1)) 
  {
    lua_getmetatable(L, 1); /* t2 = metatable(t) */
    if (lua_istable(L, -1)) 
    {
      lua_pushstring(L, "class");
      lua_gettable(L, -2);    /* class_name = t2.class */ 
      return 1;
    }
  }
  lua_pushnil(L);
  return 1;
}

/* iup.SetMethod(class_name, method, function)
   For ex:
     iup.SetMethod("iupHandle", "__index", ihandle_gettable)
*/
static int SetMethod(lua_State *L)
{
  lua_pushvalue(L, 1); /* push class_name */
  lua_gettable(L, LUA_REGISTRYINDEX);  /* t = registry[class_name] */
  if (lua_isnil(L, -1)) 
  {
    lua_pushstring(L, "Invalid class name.");
    lua_error(L);
  }
  lua_pushvalue(L, -3); /* push method */
  lua_pushvalue(L, -3); /* push function */
  lua_settable(L, -3);  /* t.method = function */
  return 0;
}

static int IsContainer(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L, 1);
  if (ih)
  {        
    lua_pushboolean(L, ih->iclass->childtype != IUP_CHILDNONE);
    return 1;
  }
  return 0;
}

static int ihandle_tostring (lua_State *L) 
{
  Ihandle *ih = iuplua_checkihandle(L, 1);
  if (!ih)
    lua_pushstring (L, "(not an IUP handle)");
  else
    lua_pushfstring (L, "IUP(%s): %p", IupGetClassName(ih), ih);
  return 1;
}

static int ihandle_compare(lua_State *L)
{
  Ihandle *a = iuplua_checkihandle(L, 1);
  Ihandle *b = iuplua_checkihandle(L, 2);
  if(a == b)
    lua_pushboolean(L, 1);
  else
    lua_pushboolean(L, 0);
  return 1;
}

             /*************************************/
             /*       widget <-> Ihandle*         */

/* local widget = iup.GetWidget(ih) */
static int GetWidget(lua_State *L)
{
  /* Pushes a Lua object that is associated with an Ihandle* */
  /* Used by the "__index" metamethod of the iupHandle */
  Ihandle * ih = iuplua_checkihandle(L, 1);
  char* sref = IupGetAttribute(ih, "_IUPLUA_WIDGET_TABLE_REF");
  if (!sref)
    lua_pushnil(L);
  else
    lua_rawgeti(L, LUA_REGISTRYINDEX, atoi(sref));
  return 1;
}

/* iup.SetWidget(ih, widget) */
static int SetWidget(lua_State *L)
{
  /* Saves the Lua object reference as an attribute, 
     i.e. associates a Lua object with the Ihandle*.
     Used by the "iupWidget" constructor and by RegisterHandle */
  Ihandle * ih = iuplua_checkihandle(L, 1);
  char* sref = IupGetAttribute(ih, "_IUPLUA_WIDGET_TABLE_REF");
  if (!sref)
  {
    int ref = luaL_ref(L, LUA_REGISTRYINDEX);
    IupSetInt(ih, "_IUPLUA_WIDGET_TABLE_REF", ref);
    IupSetCallback(ih, "LDESTROY_CB", il_destroy_cb);
  }
  return 0;
}

             /*************************************/
             /*          registration             */

IUPLUA_SDK_API int iuplua_opencall_internal(lua_State * L)
{
  int ret = 0;
  const char* s;
  iuplua_get_env(L);
  lua_pushliteral(L,"_IUPOPEN_CALL");
  lua_gettable(L, -2);
  s = lua_tostring(L, -1);
  if (s && strcmp(s, "INTERNAL")==0)
    ret = 1;
  lua_pop(L, 2);  /* remove global table and <global table>._IUPOPEN_CALL from stack */
  return ret;
}

/* iup[name] = func */ 
IUPLUA_SDK_API void iuplua_register(lua_State *L, lua_CFunction func, const char* name)
{
  lua_pushcfunction(L, func);
  lua_setfield(L, -2, name);
}

/* iup[name] = s */ 
IUPLUA_SDK_API void iuplua_regstring(lua_State *L, const char* s, const char* name)
{
  lua_pushstring(L, s); 
  lua_setfield(L, -2, name);
}

/* global table */
static const char* iup_globaltable = "iup";

IUPLUA_SDK_API void iuplua_get_env(lua_State *L)
{
  lua_getglobal(L, iup_globaltable);
}

IUPLUA_SDK_API void iuplua_register_lib(lua_State *L, const luaL_Reg* funcs)
{
#if LUA_VERSION_NUM < 502
  luaL_register(L, iup_globaltable, funcs);
#else
  iuplua_get_env(L);
  if (lua_istable(L, -1))
    luaL_setfuncs(L, funcs, 0);
  else
  {
    if (!lua_isnil(L, -1))
      luaL_error(L, "name conflict for module \"%s\"", iup_globaltable);

    lua_newtable(L);
    luaL_setfuncs(L, funcs, 0);
    lua_pushvalue(L, -1);
    lua_setglobal(L, iup_globaltable);
  }
#endif
}

IUPLUA_SDK_API void iuplua_register_funcs(lua_State *L, const luaL_Reg* funcs)
{
#if LUA_VERSION_NUM < 502
  luaL_register(L, NULL, funcs);
#else
  luaL_setfuncs(L, funcs, 0);
#endif
}


/*****************************************************************************
* Common Callbacks                                                     *
****************************************************************************/

static int help_cb(Ihandle *self)
{
  lua_State *L = iuplua_call_start(self, "help_cb");
  return iuplua_call(L, 0);
}

static int getfocus_cb(Ihandle *self)
{
  lua_State *L = iuplua_call_start(self, "getfocus_cb");
  return iuplua_call(L, 0);
}

static int k_any(Ihandle *self, int c)
{
  lua_State *L = iuplua_call_start(self, "k_any");
  lua_pushinteger(L, c);
  return iuplua_call(L, 1);
}

static int killfocus_cb(Ihandle *self)
{
  lua_State *L = iuplua_call_start(self, "killfocus_cb");
  return iuplua_call(L, 0);
}

static int multitouch_cb(Ihandle *ih, int count, int* pid, int* px, int* py, int* pstate)
{
  int i;
  lua_State *L = iuplua_call_start(ih, "multitouch_cb");
  lua_pushinteger(L, count);
  
  lua_createtable(L, count, 0);
  for (i = 0; i < count; i++)
  {
    lua_pushinteger(L,i+1);
    lua_pushinteger(L,pid[i]);
    lua_settable(L,-3);
  }
  
  lua_createtable(L, count, 0);
  for (i = 0; i < count; i++)
  {
    lua_pushinteger(L,i+1);
    lua_pushinteger(L,px[i]);
    lua_settable(L,-3);
  }
  
  lua_createtable(L, count, 0);
  for (i = 0; i < count; i++)
  {
    lua_pushinteger(L,i+1);
    lua_pushinteger(L,py[i]);
    lua_settable(L,-3);
  }
  
  
  lua_createtable(L, count, 0);
  for (i = 0; i < count; i++)
  {
    lua_pushinteger(L,i+1);
    lua_pushinteger(L,pstate[i]);
    lua_settable(L,-3);
  }
  
  return iuplua_call(L, 5);
}

static int attribchanged_cb(Ihandle *self, char * p0)
{
  lua_State *L = iuplua_call_start(self, "attribchanged_cb");
  lua_pushstring(L, p0);
  return iuplua_call(L, 1);
}

static int layoutchanged_cb(Ihandle *self, Ihandle* elem)
{
  lua_State *L = iuplua_call_start(self, "layoutchanged_cb");
  iuplua_pushihandle(L, elem);
  return iuplua_call(L, 1);
}

static void entry_point(void)
{
  lua_State *L = iuplua_call_global_start("entry_point");
  iuplua_call_global(L, 0);
}

static void exit_cb(void)
{
  lua_State *L = iuplua_call_global_start("exit_cb");
  iuplua_call_global(L, 0);
}

static void globalwheel_cb(float delta, int x, int y, char* status)
{
  lua_State *L = iuplua_call_global_start("globalwheel_cb");
  lua_pushnumber(L, delta);
  lua_pushinteger(L, x);
  lua_pushinteger(L, y);
  lua_pushstring(L, status);
  iuplua_call_global(L, 4);
}

static void globalbutton_cb(int button, int pressed, int x, int y, char* status)
{
  lua_State *L = iuplua_call_global_start("globalbutton_cb");
  lua_pushinteger(L, button);
  lua_pushinteger(L, pressed);
  lua_pushinteger(L, x);
  lua_pushinteger(L, y);
  lua_pushstring(L, status);
  iuplua_call_global(L, 5);
}

static void globalmotion_cb(int x, int y, char* status)
{
  lua_State *L = iuplua_call_global_start("globalmotion_cb");
  lua_pushinteger(L, x);
  lua_pushinteger(L, y);
  lua_pushstring(L, status);
  iuplua_call_global(L, 3);
}

static void globalkeypress_cb(int key, int pressed)
{
  lua_State *L = iuplua_call_global_start("globalkeypress_cb");
  lua_pushinteger(L, key);
  lua_pushinteger(L, pressed);
  iuplua_call_global(L, 2);
}

static void globalctrlfunc_cb(int key)
{
  lua_State *L = iuplua_call_global_start("globalctrlfunc_cb");
  lua_pushinteger(L, key);
  iuplua_call_global(L, 1);
}

static int globalidle_cb(void)
{
  lua_State *L = iuplua_call_global_start("idle_action");
  return iuplua_call_global(L, 0);
}

static int idle_cb(void)
{
  int ret = 0;
  lua_State *L = (lua_State *) IupGetGlobal("_IUP_LUA_DEFAULT_STATE");
  lua_getglobal(L, "_IUP_LUA_IDLE_FUNC_"); 
  lua_call(L, 0, 1);  /* _IUP_LUA_IDLE_FUNC_() */
  ret = (int)lua_tointeger(L, -1);
  lua_pop(L, 1);
  return ret;
}

static int SetIdle(lua_State *L)
{
  if (lua_isnoneornil(L,1))
    IupSetFunction("IDLE_ACTION", NULL);
  else
  {
    luaL_checktype(L, 1, LUA_TFUNCTION);
    lua_pushvalue(L,1);
    lua_setglobal(L, "_IUP_LUA_IDLE_FUNC_");
    IupSetFunction("IDLE_ACTION", (Icallback)idle_cb);
  }
  return 0;
}


/*****************************************************************************
* Iuplua bind functions                                                     *
****************************************************************************/


static int GetFromC(lua_State *L)
{
  Ihandle *n;
  const char *a;
  if (!lua_istable(L, -1)) 
  {
    lua_pushstring(L, "iup.GetFromC: wrong arguments to function"); 
    lua_error(L);
    return 0;
  }
  lua_pushinteger(L, 1);
  lua_gettable(L, -2);
  if (!lua_isstring(L, -1)) 
  {
    lua_pushstring(L, "iup.GetFromC: wrong arguments to function"); 
    lua_error(L);
    return 0;
  }
  a = lua_tostring(L, -1);
  n = IupGetHandle((char*)a);
  if(n)
    iuplua_pushihandle(L, n);
  else
    lua_pushnil(L);
  return 1;
}


/*****************************************************************************
 *   iupkey                                                                  *
 ****************************************************************************/


static void register_key(char *name, int code, void* user_data)
{
  lua_State *L = (lua_State*)user_data;
  lua_pushinteger(L, code); 
  lua_setfield(L, -2, name);
}

/* from iupkey.c */
void iupKeyForEach(void (*func)(char *name, int code, void* user_data), void* user_data);

IUPLUA_API int iupkey_open(lua_State *L)
{
  (void)L;
  /* does nothing, kept for backward compatibility */
  return 0;
}


/*****************************************************************************
* iuplua_open                                                               *
****************************************************************************/


static int il_open(lua_State * L)
{
  int ret, argc = 0; 
  char **argv = NULL;

  lua_getglobal(L, "arg");
  if (lua_istable(L, -1))
  {
    int i;
    argc = iuplua_getn(L, -1);
    argv = malloc(sizeof(char*)*argc);
    for(i=1; i<=argc; i++)
    {
      lua_pushinteger(L,i);
      lua_gettable(L,-2);
      argv[i-1] = (char*)lua_tostring(L, -1);
      lua_pop(L, 1);
    }
  }
  lua_pop(L, 1);

  ret = IupOpen(&argc, &argv);
  if (ret == IUP_ERROR)
  {
    if (argv) free(argv);
    lua_pushstring(L, "IupOpen: could not initialize"); 
    lua_error(L);
    return 0;
  }

  if (argv) free(argv);
  lua_pushinteger(L, ret);
  return 1;
}

IUPLUA_API int iuplua_close(lua_State * L)
{
  if (iuplua_opencall_internal(L))
    IupClose();
  return 0; /* nothing in stack */
}

static void setinfo (lua_State *L) 
{
  /* global table is at the stack */

  lua_pushliteral (L, IUP_COPYRIGHT);
  lua_setfield(L, -2, "_COPYRIGHT");

  lua_pushliteral (L, IUP_DESCRIPTION);
  lua_setfield(L, -2, "_DESCRIPTION");

  lua_pushliteral (L, IUP_NAME);
  lua_setfield(L, -2, "_NAME");

  lua_pushstring (L, IupVersion());
  lua_setfield(L, -2, "_VERSION");

  lua_pushliteral (L, IUP_VERSION_DATE);
  lua_setfield(L, -2, "_VERSION_DATE");

  lua_pushinteger (L, IupVersionNumber());
  lua_setfield(L, -2, "_VERSION_NUMBER");
}

IUPLUA_API int iuplua_open(lua_State * L)
{
  int ret;

  struct luaL_Reg funcs[] = {
    {"key_open", iupkey_open},
    {"Open", il_open},
    {"Close", iuplua_close},
    {"SetIdle", SetIdle},
    {"GetFromC", GetFromC},
    {"GetWidget", GetWidget},
    {"SetWidget", SetWidget},
    {"NewClass", NewClass},
    {"SetClass", SetClass},
    {"GetClass", GetClass},
    {"IsContainer", IsContainer},
    {"SetMethod", SetMethod},
    {"SetCallback", SetCallback},
    {"SetFunction", SetFunction},
    {"ihandle_compare", ihandle_compare},
    {"ihandle_tostring", ihandle_tostring},
    {"_ERRORMESSAGE", il_error_message},
    {"dostring", il_dostring},
    {"dofile", il_dofile},
    { "StringCompare", StringCompare },
    { "StringChangeCase", StringChangeCase },
    { "CopyUserData2String", CopyUserData2String },
    { "CopyString2UserData", CopyString2UserData },

    { NULL, NULL },
  };

  if (!il_open(L))
    return 0;

  ret = (int)lua_tointeger(L, -1); /* retrieve IupOpen return value */
  lua_pop(L, -1);

  /* Registers functions in iup namespace */
  iuplua_register_lib(L, funcs);  /* leave global table at the top of the stack */
  iupluaapi_open(L);

  /* set version info */
  setinfo(L);

  /* register if IupOpen was called here or from outside IupLua */
  /* iup._IUPOPEN_CALL = EXTERNAL|INTERNAL                      */
  if (ret == IUP_OPENED) 
    lua_pushliteral (L, "EXTERNAL");
  else                   
    lua_pushliteral (L, "INTERNAL");
  lua_setfield(L, -2, "_IUPOPEN_CALL");

  /* used by Idle in Lua */
  IupSetGlobal("_IUP_LUA_DEFAULT_STATE", (char *) L);  

#ifdef IUPLUA_USELOH        
#include "iuplua.loh"
#include "constants.loh"
#else
#ifdef IUPLUA_USELH
#include "iuplua.lh"
#include "constants.lh"
#else
  iuplua_dofile(L, "iuplua.lua");
  iuplua_dofile(L, "constants.lua");
#endif
#endif

  /* Register the common callbacks */
  iuplua_register_cb(L, "HELP_CB", (lua_CFunction)help_cb, NULL);
  iuplua_register_cb(L, "GETFOCUS_CB", (lua_CFunction)getfocus_cb, NULL);
  iuplua_register_cb(L, "K_ANY", (lua_CFunction)k_any, NULL);
  iuplua_register_cb(L, "KILLFOCUS_CB", (lua_CFunction)killfocus_cb, NULL);
  iuplua_register_cb(L, "MULTITOUCH_CB", (lua_CFunction)multitouch_cb, NULL);

  /* Register global callbacks */
  iuplua_register_cb(L, "ENTRY_POINT", (lua_CFunction)entry_point, NULL);
  iuplua_register_cb(L, "EXIT_CB", (lua_CFunction)exit_cb, NULL);
  iuplua_register_cb(L, "GLOBALWHEEL_CB", (lua_CFunction)globalwheel_cb, NULL);
  iuplua_register_cb(L, "GLOBALBUTTON_CB", (lua_CFunction)globalbutton_cb, NULL);
  iuplua_register_cb(L, "GLOBALMOTION_CB", (lua_CFunction)globalmotion_cb, NULL);
  iuplua_register_cb(L, "GLOBALKEYPRESS_CB", (lua_CFunction)globalkeypress_cb, NULL);
  iuplua_register_cb(L, "GLOBALCTRLFUNC_CB", (lua_CFunction)globalctrlfunc_cb, NULL);
  iuplua_register_cb(L, "IDLE_ACTION", (lua_CFunction)globalidle_cb, NULL);

  /* Other callbacks */
  iuplua_register_cb(L, "ATTRIBCHANGED_CB", (lua_CFunction)attribchanged_cb, NULL);
  iuplua_register_cb(L, "LAYOUTCHANGED_CB", (lua_CFunction)layoutchanged_cb, NULL);

  /* Register Keys */
  iupKeyForEach(register_key, (void*)L);

  /* Iup Modules initialization */
  iupbuttonlua_open(L);
  iupluadraw_open(L);  /* must be before canvas */
  iupcanvaslua_open(L);
  iupdialoglua_open(L);
  iupfilllua_open(L);
  iupframelua_open(L);
  iupfiledlglua_open(L);
  iuphboxlua_open(L);
  iupitemlua_open(L);
  iupimagelua_open(L);
  iuplabellua_open(L);
  iuplistlua_open(L);
  iupmenulua_open(L);
  iupmultilinelua_open(L);
  iupradiolua_open(L);
  iupseparatorlua_open(L);
  iupsubmenulua_open(L);
  iuptextlua_open(L);
  iuptogglelua_open(L);
  iupvboxlua_open(L);
  iupzboxlua_open(L);
  iuptimerlua_open(L);
  iupsboxlua_open(L);
  iupsplitlua_open(L);
  iupspinlua_open(L);
  iupspinboxlua_open(L);
  iupscrollboxlua_open(L);
  iupgridboxlua_open(L);
  iupmultiboxlua_open(L);
  iupexpanderlua_open(L);
  iuplinklua_open(L);
  iupcboxlua_open(L);
  iupdetachboxlua_open(L);
  iupbackgroundboxlua_open(L);
  iupgclua_open(L);
  iupgetparamlua_open(L);
  iupvallua_open(L);
  iuptabslua_open(L);
  iupfontdlglua_open(L);
  iupmessagedlglua_open(L);
  iupcolordlglua_open(L);
  iupimagergbalua_open(L);
  iupimagergblua_open(L);
  iupprogressbarlua_open(L);
  iupnormalizerlua_open(L);
  iupuserlua_open(L);
  iupthreadlua_open(L);
  iuptreelua_open(L);
  iupclipboardlua_open(L);
  iupprogressdlglua_open(L);
  iupflatlabellua_open(L);
  iupflatbuttonlua_open(L);
  iupflattogglelua_open(L);
  iupdropbuttonlua_open(L);
  iupflatframelua_open(L);
  iupflatseparatorlua_open(L);
  iupflatlistlua_open(L);
  iupflatvallua_open(L);
  iupflattreelua_open(L);
  iupspacelua_open(L);
  iupconfiglua_open(L);
  iupanimatedlabellua_open(L);
  iupcalendarlua_open(L);
  iupdatepicklua_open(L);
  iupflattabslua_open(L);
  iupflatscrollboxlua_open(L);
  iupgaugelua_open(L);
  iupdiallua_open(L);
  iupcolorbarlua_open(L);
  iupcolorbrowserlua_open(L);

  return 0; /* nothing in stack */
}

/* obligatory to use require"iuplua" */
IUPLUA_SDK_API int luaopen_iuplua(lua_State* L)
{
  return iuplua_open(L);
}

