/* tolua: tag methods
** Support code for Lua bindings.
** Written by Waldemar Celes
** TeCGraf/PUC-Rio
** Jul 1998
** $Id$
*/

/* This code is free software; you can redistribute it and/or modify it. 
** The software provided hereunder is on an "as is" basis, and 
** the author has no obligation to provide maintenance, support, updates,
** enhancements, or modifications. 
*/

#include "tolua.h"
#include "tolua_tm.h"
#include "tolua_tt.h"
#include "tolua_rg.h"

#include <string.h>



/* Global tables created in Lua registry:
    tolua_tbl_class: indexed by instance tags, stores the class tables.
    tolua_tbl_clone: indexed by memory address, stores the tag indicanting 
                     it is a clone.
    tolua_tbl_mate:  indexed by memory address, stores the associate instance
                     table.

   General tags stored in Lua registry:
    tolua_tag_global;
    tolua_tag_module;
    tolua_tag_class;
    tolua_tag_instance;
    tolua_tag_linstance;
    tolua_tag_indirect;
*/

/* internal function prototype */
static void setmethods (lua_State* L);
int toluaI_gettag (lua_State* L, char* tagname);

static void settag (lua_State* L, int lo, char* tag_registry_field)
{
 toluaI_getregistry(L,tag_registry_field);
 lua_pushvalue(L,lo);
 lua_settag(L,(int)lua_tonumber(L,-2));
 lua_pop(L,2);
}

void toluaI_tm_global (lua_State* L, int lo)
{
 settag(L,lo,"tolua_tag_global");
}

void toluaI_tm_module (lua_State* L, int lo)
{
 settag(L,lo,"tolua_tag_module");
}

void toluaI_tm_setclass (lua_State* L, int lo)
{
 settag(L,lo,"tolua_tag_class");
}

void toluaI_tm_class (lua_State* L, int lo, char* name)
{
 int tag_class;
 int tag = lua_newtag(L);
 char* type = toluaI_tt_concat("class ",name);
 toluaI_getregistry(L,"tolua_tag_class");
 tag_class = (int)lua_tonumber(L,-1);
 lua_copytagmethods(L,tag,tag_class);
 toluaI_tt_register(L,tag,type);
 toluaI_tt_sethierarchy(L,tag,tag_class);
 lua_pushvalue(L,lo);
 lua_settag(L,tag);
 lua_pop(L,2);  /* tag_class and lo */
}

void toluaI_tm_instance (lua_State* L, int tag, int lo)
{
 toluaI_getregistry(L,"tolua_tbl_class");
 lua_pushnumber(L,tag);
 lua_pushvalue(L,lo);
 lua_settable(L,-3);
 toluaI_getregistry(L,"tolua_tag_instance");
 lua_copytagmethods(L,tag,(int)lua_tonumber(L,-1));
 lua_pop(L,2); /* tbl_class and tag_instance */
}

void toluaI_tm_linstance (lua_State* L, int tag, int lo)
{
 toluaI_getregistry(L,"tolua_tbl_class");
 lua_pushnumber(L,tag);
 lua_pushvalue(L,lo);
 lua_settable(L,-3);
 toluaI_getregistry(L,"tolua_tag_linstance");
 lua_copytagmethods(L,tag,(int)lua_tonumber(L,-1));
 lua_pop(L,2); /* tbl_class and tag_linstance */
}

void* tolua_doclone (lua_State* L, void* value, int tag)
{
 toluaI_getregistry(L,"tolua_tbl_clone");
 lua_pushuserdata(L,value);
 lua_pushnumber(L,tag);
 lua_settable(L,-3);
 lua_pop(L,1);
 return value;
}

void* tolua_copy (lua_State* L, void* value, unsigned int size)
{
 void* clone = (void*)malloc(size);
 if (clone)
  memcpy(clone,value,size);
 else
  tolua_error(L,"insuficient memory");
 return clone;
}

static void toluaI_tm_undoclone (lua_State* L, int tag, void* clone)
{
 toluaI_getregistry(L,"tolua_tbl_clone");
 lua_pushuserdata(L,clone);
 lua_gettable(L,-2);
 if (lua_isnumber(L,-1) && lua_tonumber(L,-1)==tag)
 {
  lua_pushuserdata(L,clone);
  lua_pushnil(L);
  lua_settable(L,-4);

  /* get base class */
  toluaI_getregistry(L,"tolua_tbl_class");
  lua_pushnumber(L,tag);
  lua_rawget(L,-2);

  /* look for destructor */
  lua_pushstring(L,"delete");
  lua_gettable(L,-2);
  if (lua_iscfunction(L,-1))
  {
   lua_pushusertag(L,clone,tag);
   lua_call(L,1,0);
  }
  else
  {
   free(clone);    /* no destructor: use raw free */
   lua_pop(L,1);   /* the nil function value */
  }
  lua_pop(L,2); /* tbl_class and class method table */
 }
 lua_pop(L,2);  /* table and value */
}

void toluaI_tm_pushmate (lua_State* L, int lo)
{
 toluaI_getregistry(L,"tolua_tbl_mate");
 lua_pushvalue(L,lo);
 lua_rawget(L,-2);
 lua_insert(L,-2);
 lua_pop(L,1);
}

void toluaI_tm_pushclass (lua_State* L, int lo)
{
 toluaI_getregistry(L,"tolua_tbl_class");
 lua_pushnumber(L,lua_tag(L,lo));
 lua_rawget(L,-2);
 lua_insert(L,-2);
 lua_pop(L,1);
}

int toluaI_gettag (lua_State* L, char* tagname)
{
 int tag;
 toluaI_getregistry(L,tagname);
 tag = (int)lua_tonumber(L,-1);
 lua_pop(L,1);
 return tag;
}

void toluaI_tm_init (lua_State* L)
{
 lua_newtable(L); toluaI_setregistry(L,"tolua_tbl_class");
 lua_newtable(L); toluaI_setregistry(L,"tolua_tbl_clone");
 lua_newtable(L); toluaI_setregistry(L,"tolua_tbl_mate");

 lua_pushnumber(L,lua_newtag(L)); toluaI_setregistry(L,"tolua_tag_global");
 lua_pushnumber(L,lua_newtag(L)); toluaI_setregistry(L,"tolua_tag_module");
 lua_pushnumber(L,lua_newtag(L)); toluaI_setregistry(L,"tolua_tag_class");
 lua_pushnumber(L,lua_newtag(L)); toluaI_setregistry(L,"tolua_tag_instance");
 lua_pushnumber(L,lua_newtag(L)); toluaI_setregistry(L,"tolua_tag_linstance");
 lua_pushnumber(L,lua_newtag(L)); toluaI_setregistry(L,"tolua_tag_indirect");

 toluaI_tt_register(L,toluaI_gettag(L,"tolua_tag_global"),"generic variable");
 toluaI_tt_register(L,toluaI_gettag(L,"tolua_tag_module"),"generic module");
 toluaI_tt_register(L,toluaI_gettag(L,"tolua_tag_class"),"generic class");
 toluaI_tt_register(L,toluaI_gettag(L,"tolua_tag_indirect"),"generic indirect");
 toluaI_tt_register(L,toluaI_gettag(L,"tolua_tag_instance"),"generic instance");
 toluaI_tt_register(L,toluaI_gettag(L,"tolua_tag_linstance"),"generic lua instance");

 /* allows modules and classes to be used as ordinary tables */
 toluaI_tt_sethierarchy(L,toluaI_gettag(L,"tolua_tag_module"),tolua_tag_table);
 toluaI_tt_sethierarchy(L,toluaI_gettag(L,"tolua_tag_class"),tolua_tag_table);

 setmethods(L);
}

static int map (lua_State* L)
{
 int m = lua_gettop(L);
 /* do not pass string fields starting with a dot */
 if (!lua_isstring(L,1) || *lua_tostring(L,1)!='.')
 {
  lua_getglobals(L);
  lua_pushvalue(L,1);
  lua_pushvalue(L,m);
  lua_rawset(L,-3);
  lua_pop(L,1);
 }
 return 0;
}

void toluaI_tm_using (lua_State* L, int module)
{
 lua_newtable(L);
 lua_settag(L,toluaI_gettag(L,"tolua_tag_indirect"));
 lua_pushstring(L,".module");
 lua_pushvalue(L,module);
 lua_settable(L,-3);

 lua_getglobal(L,"foreach");
 lua_pushvalue(L,module);
 lua_pushvalue(L,-3);
 lua_pushcclosure(L,map,1);
 lua_call(L,2,0);

 lua_getglobal(L,"foreach");
 lua_pushvalue(L,module);
 lua_pushstring(L,".get");
 lua_gettable(L,-2);
 lua_insert(L,-2);
 lua_pop(L,1);   /* module table */
 lua_pushvalue(L,-3);
 lua_pushcclosure(L,map,1);
 lua_call(L,2,0);
 lua_pop(L,1);   /* indirect table */
}

/********************************************************** tag methods */

/* tag methods coded in C */

/* generic gettable */
static void oo_gettable (lua_State* L, int table, int base, int index)
{
 while (lua_istable(L,base))
 {
  lua_pushvalue(L,index); 
  lua_rawget(L,base);
  if (!lua_isnil(L,-1))
   return;            /* returned value already on the top */
  else if (lua_isnumber(L,index))
  {
   lua_pushstring(L,"operator_get");
   lua_rawget(L,base);
   if (!lua_isnil(L,-1))
   {
    lua_pushvalue(L,table);
    lua_pushvalue(L,index);
    lua_call(L,2,1);
    return;
   }
  }
  else
  {
   lua_pushstring(L,".get");
   lua_rawget(L,base);
   if (!lua_isnil(L,-1))
   {
    lua_pushvalue(L,index);
    lua_rawget(L,-2);
    if (!lua_isnil(L,-1))
    {
     lua_pushvalue(L,table);
     lua_pushvalue(L,index); /* need to access array field (?) */
     lua_call(L,2,1);
     return;
    }
   }
  }
  lua_pushstring(L,".base"); lua_rawget(L,base);
  base = lua_gettop(L);
 }
 lua_pushnil(L);
}

/* generic settable */
static int oo_settable (lua_State* L, int table, int base, int index, int value) 
{
 while (lua_istable(L,base))
 {
  lua_pushstring(L,".set");
  lua_rawget(L,base);
  if (!lua_isnil(L,-1))
  {
   lua_pushvalue(L,index);
   lua_rawget(L,-2);
   if (!lua_isnil(L,-1))
   {
    lua_pushvalue(L,table);
    lua_pushvalue(L,value);
    lua_call(L,2,0);
    return 1;
   }
  }
  lua_pushstring(L,".base"); lua_rawget(L,base);
  base = lua_gettop(L);
 }
 return 0;
}

/* class tag methods */
static int class_index (lua_State* L)
{
 int table = 1;
 int index = 2;
 oo_gettable(L,table,table,index);
 return 1;
}
static int class_settable (lua_State* L)
{
 int table = 1;
 int index = 2;
 int value = 3;
 if (oo_settable(L,table,table,index,value) == 0)
 {
  lua_pushvalue(L,table);
  lua_pushvalue(L,index);
  lua_pushvalue(L,value);
  lua_rawset(L,-3);
 }
 return 0;
}

/* instance tags */
static int instance_gettable (lua_State* L)
{
 int table = 1;
 int index = 2;
 toluaI_tm_pushmate(L,table);   /* pushes mate */
 if (!lua_isnil(L,-1))          /* if there's a mate table */
 {
  lua_pushvalue(L,index);
  lua_rawget(L,-2);
  if (!lua_isnil(L,-1))         /* if field in mate table exists */
   return 1;
 }
 toluaI_tm_pushclass(L,table);  /* pushes base */
 oo_gettable(L,table,lua_gettop(L),index);
 return 1;
}
static int instance_settable (lua_State* L)
{
 int table = 1;
 int index = 2;
 int value = 3;
 toluaI_tm_pushclass(L,table);  /* pushes base */
 if (lua_isnumber(L,index))
 {
  lua_pushstring(L,"operator_set");
  lua_rawget(L,-2);
  if (!lua_isnil(L,-1))
  {/* the stack here is: table,index,value,base,operator */
   /* call operator passing table, index, and value */
   lua_insert(L,1);
   lua_pop(L,1);       /* base */
   lua_call(L,3,0);
   return 0;
  }
 }
 if (oo_settable(L,table,4,index,value) == 0)
 {
  toluaI_tm_pushmate(L,table);    /* pushes mate */
  if (lua_isnil(L,-1))
  {
   /* creates mate table */
   lua_newtable(L);
   toluaI_getregistry(L,"tolua_tbl_mate");
   lua_pushvalue(L,table);        /* that is the userdata */
   lua_pushvalue(L,-3);
   lua_rawset(L,-3);
   lua_pop(L,1);     /* tbl_mate */
  }
  /* the mate table is on the top */
  lua_pushvalue(L,index);
  lua_pushvalue(L,value);
  lua_rawset(L,-3);
 }
 return 0;
}
static int instance_gc (lua_State* L)
{
 toluaI_tm_undoclone(L,lua_tag(L,1),lua_touserdata(L,1)); 
 return 0;
}
static int gen_operator (lua_State* L)
{
 int op1 = 1;
 int op2 = 2;
 int event = 3;
 char* name = toluaI_tt_concat("operator_",lua_tostring(L,event));
 lua_pushstring(L,name); 
 lua_gettable(L,op1);
 lua_pushvalue(L,op1);
 lua_pushvalue(L,op2);
 lua_call(L,2,1);
 return 1;
}
static int instance_operator (lua_State* L)
{
 return gen_operator(L);
}
static int instance_relational (lua_State* L)
{
 gen_operator(L);
 if ((int)lua_tonumber(L,-1)==0) lua_pushnil(L);
 return 1;
}

/* lua instance tags */
static int linstance_index (lua_State* L)
{
 toluaI_tm_pushclass(L,1);
 oo_gettable(L,1,3,2);  /* table,base,index */
 return 1;
}


/* module tag methods */
static int module_index (lua_State* L)
{
 int table = 1;
 int index = 2;
 lua_pushstring(L,".get");
 lua_rawget(L,table);
 if (!lua_isnil(L,-1))
 {
  lua_pushvalue(L,index);
  lua_rawget(L,-2);
  if (!lua_isnil(L,-1))
  {
   lua_call(L,0,1);
   return 1;
  }
 }
 lua_pushnil(L);
 return 1;
}
static int module_settable (lua_State* L)
{
 int table = 1;
 int index = 2;
 int value = 3;
 lua_pushstring(L,".set");
 lua_rawget(L,table);
 if (!lua_isnil(L,-1))
 {
  lua_pushvalue(L,index);
  lua_rawget(L,-2);
  if (!lua_isnil(L,-1))
  {
   lua_pushvalue(L,value);
   lua_call(L,1,0);
   return 0;
  }
 }
 lua_pushvalue(L,index);
 lua_pushvalue(L,value);
 lua_rawset(L,table);
 return 0;
}

/* global variable tag methods */
static int global_getglobal (lua_State* L)
{
 int value = 2;
 lua_pushstring(L,".get");
 lua_rawget(L,value);
 lua_call(L,0,1);
 return 1;
}
static int global_setglobal (lua_State* L)
{
 int value = 2;
 int newvalue = 3;
 lua_pushstring(L,".set");
 lua_rawget(L,value);
 if (lua_isnil(L,-1))
  lua_error(L,"value of const variable cannot be changed");
 else
 {
  lua_pushvalue(L,newvalue);
  lua_call(L,1,0);
 }
 return 0;
}

/* indirect tag methods */
static int indirect_getglobal (lua_State* L)
{
 int varname = 1;
 int value = 2;
 lua_pushstring(L,".module");
 lua_gettable(L,value);
 lua_pushvalue(L,varname);
 lua_gettable(L,-2);
 return 1;
}
static int indirect_setglobal (lua_State* L)
{
 int varname = 1;
 int value = 2;
 int newvalue = 3;
 lua_pushstring(L,".module");
 lua_gettable(L,value);
 lua_pushvalue(L,varname);
 lua_pushvalue(L,newvalue);
 lua_settable(L,-3);
 return 0;
}

static void setmethods (lua_State* L)
{
 /* global variable */
 lua_pushcfunction(L,global_getglobal);
 lua_settagmethod(L,toluaI_gettag(L,"tolua_tag_global"),"getglobal");
 lua_pushcfunction(L,global_setglobal);
 lua_settagmethod(L,toluaI_gettag(L,"tolua_tag_global"),"setglobal");

 /* module */
 lua_pushcfunction(L,module_index);
 lua_settagmethod(L,toluaI_gettag(L,"tolua_tag_module"),"index");
 lua_pushcfunction(L,module_settable);
 lua_settagmethod(L,toluaI_gettag(L,"tolua_tag_module"),"settable");

 /* class */
 lua_pushcfunction(L,class_index);
 lua_settagmethod(L,toluaI_gettag(L,"tolua_tag_class"),"index");
 lua_pushcfunction(L,class_settable);
 lua_settagmethod(L,toluaI_gettag(L,"tolua_tag_class"),"settable");

 /* instance */
 lua_pushcfunction(L,instance_gettable);
 lua_settagmethod(L,toluaI_gettag(L,"tolua_tag_instance"),"gettable");
 lua_pushcfunction(L,instance_settable);
 lua_settagmethod(L,toluaI_gettag(L,"tolua_tag_instance"),"settable");
 lua_pushcfunction(L,instance_operator);
 lua_settagmethod(L,toluaI_gettag(L,"tolua_tag_instance"),"add");
 lua_pushcfunction(L,instance_operator);
 lua_settagmethod(L,toluaI_gettag(L,"tolua_tag_instance"),"sub");
 lua_pushcfunction(L,instance_operator);
 lua_settagmethod(L,toluaI_gettag(L,"tolua_tag_instance"),"mul");
 lua_pushcfunction(L,instance_operator);
 lua_settagmethod(L,toluaI_gettag(L,"tolua_tag_instance"),"div");
 lua_pushcfunction(L,instance_relational);
 lua_settagmethod(L,toluaI_gettag(L,"tolua_tag_instance"),"lt");
 lua_pushcfunction(L,instance_gc);
 lua_settagmethod(L,toluaI_gettag(L,"tolua_tag_instance"),"gc");

 /* lua instance */
 lua_pushcfunction(L,linstance_index);
 lua_settagmethod(L,toluaI_gettag(L,"tolua_tag_linstance"),"index");

 /* indirect */
 lua_pushcfunction(L,indirect_getglobal);
 lua_settagmethod(L,toluaI_gettag(L,"tolua_tag_indirect"),"getglobal");
 lua_pushcfunction(L,indirect_setglobal);
 lua_settagmethod(L,toluaI_gettag(L,"tolua_tag_indirect"),"setglobal");
}



