-- Purpose of this file:
-- "Function" class for the mnoofltk library, that manages one particular class functiton / object method

-- load libraries
local mnoo=require("mnoo_current/mnoo.lua")
local args_cl=require("mnoo_current/args_cl.lua")
local subst=require("mnoofltk/libBuilder/subst_fun.lua")

setfenv(1, mnoo:createPrivateEnvironment()) -- fixme

local cl=mnoo.newclass()
cl:declareObjectmethod("writeC", "writeLua", "getCFunname", "writeC_pushstore_function")
cl:declareObjectdata("name", "cname", "args_ilist", "returns", 
		     "ct", -- c type: "arrow", "call", "ocall"
		     "lt", -- lua type: "cf" or "of" (class/objectfunction)
		     "overloaded"
		  )
cl:setPrivate("getCFunname")

local superclass_new=cl:getClassfunction("new")
cl.new=
   function(actualclass, t)
      assert(mnoo.classIsA(actualclass, cl))
      local self=superclass_new(actualclass) -- mnoo base class, no args
      
      local t=args_cl:new(t) -- +++++++++++++ arg handling starts ++++++++++++++++++
      local name=t:get{name="n", type="string", default=false}
      local cname=t:get{name="c", type="string"}
      local args=t:get{name="args", type="table"}
      self.returns=t:get{name="r", type="string", nilOK=true}
      self.overloaded=t:get{name="overloaded", type="string", nilOK=true}
      assert(t:all_done())   -- +++++++++++++ arg handling ends ++++++++++++++++++++ 

      -- ########################################################################################
      -- # parse name
      -- ########################################################################################      
      self.ct="call"
      self.lt="objectmethod" -- object method
      while true do
	 local flag, _, ncname=string.find(cname, "->(.+)$") -- arrow notation for cname
	 if flag then
	    cname=ncname
	    self.ct="arrow"
	    break
	 end

	 local flag, _, ncname=string.find(cname, "=>(.+)$") -- arrow notation for cname
	 if flag then
	    cname=ncname
	    self.ct="ocall"
	    break
	 end
	 
	 local flag, _, ncname=string.find(cname, "new%s+(.+)$") -- "new Button"
	 if flag then
	    cname=ncname
	    self.ct="constructor"
	    self.lt="classfunction"
	    break
	 end

	 if cname == "delete" then
	    self.ct="destructor"
	    break
	 end

--	 local flag=string.find(cname, "::") -- Fl::wait
--	 if flag then
	 self.lt="classfunction"
	 break
--	 end	    
	 
--	 assert(nil)
      end

      if not name then name=cname end
      self.name=name
      self.cname=cname
      
      -- ########################################################################################
      -- # parse arguments
      -- ########################################################################################      
      self.args_ilist={}
      
      for _, arg in ipairs(args) do
	 if arg == "~lua" then
	    table.insert(self.args_ilist, "~lua")
	 else
	    local status, _, name, luatype=string.find(arg, "^(.+)=(.+)$")
	    assert(status)
	 
	    table.insert(self.args_ilist, {name=name, luatype=luatype})
	 end
      end
      return self
   end

local function lookup_ctype(types_ilist, luatype)
   if luatype=="int" then
      return "int"
   elseif luatype=="double" then
      return "double"
   elseif luatype=="luafun" then
      return "int"
   elseif luatype=="luaany" then
      return "int"
   elseif luatype=="string" then
      return "const char*"
   else
      for _, tp in ipairs(types_ilist) do
	 if tp.luatype==luatype then
	    return tp.ctype
	 end
      end      
      return nil
   end
end

local function returnstatement(cname, luatype, types_ilist)
   if luatype=="int" then
      return "\tlua_pushnumber(l, (int)"..cname..");"
   elseif luatype=="double" then
      return "\tlua_pushnumber(l, "..cname..");"
   elseif luatype=="string" then
      return "\tlua_pushstring(l, "..cname..");"
   else
      local rctype=lookup_ctype(types_ilist, luatype) assert(rctype, "undefined lua type "..luatype)
      return "\tlua_boxpointer(l, "..cname..");"
   end
end

cl.writeC=
   function(self, t)
      assert(mnoo.objectIsA(self, cl))

      local t=args_cl:new(t) -- +++++++++++++ arg handling starts ++++++++++++++++++
      local type_cl=require("mnoofltk/libBuilder/libBuilderType_cl.lua")
      local otype=t:get{name="type", class=type_cl}
      local types_ilist=t:get{name="types_ilist", type="table"}
      assert(t:all_done())   -- +++++++++++++ arg handling ends ++++++++++++++++++++ 

      local data_ilist={}
      local cFunName=self:getCFunname{type=otype}
      table.insert(data_ilist, subst("static int @FN@(luaState_t* l){", {FN=cFunName}))
      
      local returns_ilist={}
      if self.returns then
	 table.insert(returns_ilist, returnstatement("retval", self.returns, types_ilist))
      end

      local acount=1
      local callargs_ilist={}

      -- ########################################################################################
      -- # First arg is object
      -- ########################################################################################      
      if self.ct=="arrow" or self.ct == "ocall" or self.ct == "destructor" then
	 local ctype=lookup_ctype(types_ilist, otype.luatype) assert(ctype, "unknown luatype "..otype.luatype)
	 table.insert(data_ilist, subst("\tassert(lua_isuserdata(l, @N@)); @CTYPE@ obj=(@CTYPE@)lua_unboxpointer(l, @N@); /* @NAME@ */", {N=acount, CTYPE=ctype}))
	 acount=acount+1

	 if self.ct == "ocall" then
	    table.insert(callargs_ilist, "obj")
	 end
      end
      
      -- ########################################################################################
      -- # All arguments into variables
      -- ########################################################################################      
      for i, arg in ipairs(self.args_ilist) do
	 if arg == "~lua" then
	    table.insert(callargs_ilist, "l")
	 else
	    local n=arg.name assert(n)
	    local luatype=arg.luatype assert(luatype) 
	    local ctype=lookup_ctype(types_ilist, luatype) assert(ctype, "unknown luatype "..luatype)
	    if n=="RETVAL" then
	       -- ########################################################################################
	       -- # Return value argument
	       -- ########################################################################################      
	       table.insert(returns_ilist, returnstatement("a"..acount, luatype, types_ilist))
	       if luatype=="int" then
		  table.insert(data_ilist, subst("\tint a@N@; /* @NAME@ */", {N=acount, NAME=n}))
		  table.insert(callargs_ilist, "a"..acount)
		  acount=acount+1
	       elseif luatype=="double" then
		  table.insert(data_ilist, subst("\tdouble a@N@; /* @NAME@ */", {N=acount, NAME=n}))
		  table.insert(callargs_ilist, "a"..acount)
		  acount=acount+1
	       elseif luatype=="string" then
		  assert(nil, "invalid!")
		  acount=acount+1
	       else
		  local ctype=lookup_ctype(types_ilist, luatype) assert(ctype, "unknown luatype "..luatype)
		  table.insert(data_ilist, subst("\t@CTYPE@ a@N@; /* return value */", {CTYPE=ctype}))
		  table.insert(callargs_ilist, "a"..acount)
		  acount=acount+1	    
	       end -- switch type
	    else	    
	       -- ########################################################################################
	       -- # Ordinary (non-returnvalue) argument
	       -- ########################################################################################      
	       if luatype=="int" then
		  table.insert(data_ilist, subst("\tassert(lua_isnumber(l, @N@)); int a@N@=(int)lua_tonumber(l, @N@); /* @NAME@ */", {N=acount, NAME=n}))
		  table.insert(callargs_ilist, "a"..acount)
		  acount=acount+1
	       elseif luatype=="double" then
		  table.insert(data_ilist, subst("\tassert(lua_isnumber(l, @N@)); double a@N@=lua_tonumber(l, @N@); /* @NAME@ */", {N=acount, NAME=n}))
		  table.insert(callargs_ilist, "a"..acount)
		  acount=acount+1
	       elseif luatype=="string" then
		  table.insert(data_ilist, subst("\tassert(lua_isstring(l, @N@)); const char* a@N@=lua_tostring(l, @N@); /* @NAME@ */", {N=acount, NAME=n}))
		  table.insert(callargs_ilist, "a"..acount)
		  acount=acount+1
	       elseif luatype=="luafun" then
		  -- store lua object in registry 
		  table.insert(data_ilist, subst('\tassert(lua_isfunction(l, @N@)); PUSH_FUNARG_KEY(l, argCount); lua_pushvalue(l, @N@); lua_rawset(l, LUA_REGISTRYINDEX); int a@N@=argCount++;/* @NAME@ */;', {N=acount, NAME=n}))
		  table.insert(callargs_ilist, "a"..acount)
		  acount=acount+1
	       elseif luatype=="luaany" then
		  -- store lua object in registry 
		  table.insert(data_ilist, subst('\tPUSH_FUNARG_KEY(l, argCount); lua_pushvalue(l, @N@); lua_rawset(l, LUA_REGISTRYINDEX); int a@N@=argCount++;/* @NAME@ */', {N=acount, NAME=n}))
		  table.insert(callargs_ilist, "a"..acount)
		  acount=acount+1
	       else
		  table.insert(data_ilist, subst("\tassert(lua_isuserdata(l, @N@)); @CTYPE@ a@N@=(@CTYPE@)lua_unboxpointer(l, @N@); /* @NAME@ */", {N=acount, NAME=n, CTYPE=ctype}))
		  table.insert(callargs_ilist, "a"..acount)
		  acount=acount+1	    
	       end -- switch type	       
	    end -- if not retval
	 end -- if not ~lua
      end -- for arg

      -- ########################################################################################
      -- # Generate function call
      -- ########################################################################################      
      if self.returns then
	 local rctype=lookup_ctype(types_ilist, self.returns)
	 if self.ct == "arrow" then
	    table.insert(data_ilist, subst("\t@CTYPE@ retval=obj->@FN@(@ARGS@);", {CTYPE=rctype, FN=self.cname, ARGS=table.concat(callargs_ilist, ", ")}))
	 elseif self.ct == "constructor" then
	    table.insert(data_ilist, subst("\t@CTYPE@ retval=new @FN@(@ARGS@);", {CTYPE=rctype, FN=self.cname, ARGS=table.concat(callargs_ilist, ", ")}))
	 elseif self.ct == "call" or self.ct == "ocall" then
	    table.insert(data_ilist, subst("\t@CTYPE@ retval=@FN@(@ARGS@);", {CTYPE=rctype, FN=self.cname, ARGS=table.concat(callargs_ilist, ", ")}))
	 else assert(nil, self.ct)
	 end
      else
	 if self.ct=="arrow" then
	    table.insert(data_ilist, subst("\tobj->@FN@(@ARGS@);", {FN=self.cname, ARGS=table.concat(callargs_ilist, ", ")}))
	 elseif self.ct=="destructor" then
	    table.insert(data_ilist, subst("\tdelete obj;", {}))
	 else
	    table.insert(data_ilist, subst("\t@FN@(@ARGS@);", {FN=self.cname, ARGS=table.concat(callargs_ilist, ", ")}))
	 end
      end

      -- ########################################################################################
      -- # Push return values on stack
      -- ########################################################################################      
      table.insert(data_ilist, "\tlua_settop(l, 0);")
      for _, ret in ipairs(returns_ilist) do
	 table.insert(data_ilist, ret)
      end
      
      -- ########################################################################################
      -- # Number of return values on stack
      -- ########################################################################################      
      table.insert(data_ilist, subst("\treturn @NUM@;", {NUM=table.getn(returns_ilist)}))
      
      table.insert(data_ilist, subst("} /* static int @FN@ */", {FN=cFunName}))
      return table.concat(data_ilist, "\n")
   end

   cl.getCFunname=
      function(self, t)
	 assert(mnoo.objectIsA(self, cl))

	 local t=args_cl:new(t) -- +++++++++++++ arg handling starts ++++++++++++++++++
	 local type_cl=require("mnoofltk/libBuilder/libBuilderType_cl.lua")
	 local otype=t:get{name="type", class=type_cl}
	 assert(t:all_done())   -- +++++++++++++ arg handling ends ++++++++++++++++++++ 
	 
	 return otype.luatype.."_"..self.name

      end

   cl.writeC_pushstore_function=
      function(self, t)
	 assert(mnoo.objectIsA(self, cl))
	 
	 local t=args_cl:new(t) -- +++++++++++++ arg handling starts ++++++++++++++++++
	 local type_cl=require("mnoofltk/libBuilder/libBuilderType_cl.lua")
	 local otype=t:get{name="type", class=type_cl}
	 assert(t:all_done())   -- +++++++++++++ arg handling ends ++++++++++++++++++++ 
	 
	 local data_ilist={}
	 local cFunName=self:getCFunname{type=otype}
	 table.insert(data_ilist, '\t\tlua_pushstring(l, "'..self.name..'");')
	 table.insert(data_ilist, '\t\tlua_pushcfunction(l, '..cFunName..');')
	 table.insert(data_ilist, "\t\tlua_rawset(l, -3); /* write "..self.name.." function into "..otype.luatype.." type table */")

	 return table.concat(data_ilist, "\n")
      end
   
   -- writes:
   -- cl[name]=function(self, t) ... end
   cl.writeLua=
      function(self, t)
	 assert(mnoo.objectIsA(self, cl))
	 local data_ilist={}

	 local t=args_cl:new(t) -- +++++++++++++ arg handling starts ++++++++++++++++++
	 local type_cl=require("mnoofltk/libBuilder/libBuilderType_cl.lua")
	 local otype=t:get{name="type", class=type_cl}
	 local types_ilist=t:get{name="types_ilist", type="table"}
	 assert(t:all_done())   -- +++++++++++++ arg handling ends ++++++++++++++++++++ 
	 
	 -- ########################################################################################
	 -- # Function head
	 -- ########################################################################################      	 
	 if self.lt=="classfunction" then
	    table.insert(data_ilist, subst("cl.@NAME@=function(actualclass, t)", {NAME=self.name}));
	    table.insert(data_ilist, "\tif not mnoo.isClass(actualclass) then error(errorClassExpected, 2) end");
	    table.insert(data_ilist, "\tassert(actualclass:classIsA(cl))");

	 elseif self.lt=="objectmethod" then
	    table.insert(data_ilist, subst("cl.@NAME@=function(self, t)", {NAME=self.name}));
	    table.insert(data_ilist, "\tif not mnoo.isObject(self) then error(errorObjectExpected, 2) end");
	    table.insert(data_ilist, "\tassert(self:objectIsA(cl))");

	 else assert(nil) end

	 -- ########################################################################################
	 -- # Retrieve arguments
	 -- ########################################################################################      
	 table.insert(data_ilist, '\tlocal t=args_cl:new(t) -- arg handling begins')
	 
	 local callargs_ilist={}
	 local returnstype_ilist={}
	 local returnsname_ilist={}
	 if self.returns then table.insert(returnstype_ilist, self.returns) end

	 for i, arg in ipairs(self.args_ilist) do
	    
	    if arg=="~lua" then
	    else
	       local n=arg.name assert(n)
	       local luatype=arg.luatype assert(luatype) 
	       local ctype=lookup_ctype(types_ilist, luatype) assert(ctype, "unknown luatype "..luatype)
	       if n == "RETVAL" then
		  table.insert(returnstype_ilist, luatype)
	       else
		  table.insert(callargs_ilist, n)
		  if luatype=="int" or luatype=="double" then	       
		     table.insert(data_ilist, subst("\tlocal @NAME@=t:get{name='@NAME@', type='number'}", {NAME=n}));
		  elseif luatype=="luafun" then	       
		     table.insert(data_ilist, subst("\tlocal @NAME@=t:get{name='@NAME@', type='function'}", {NAME=n}));
		  elseif luatype=="luaany" then	       
		     table.insert(data_ilist, subst("\tlocal @NAME@=t:get{name='@NAME@', type='any'}", {NAME=n}));
		  elseif luatype=="string" then
		     table.insert(data_ilist, subst("\tlocal @NAME@=t:get{name='@NAME@', type='string'}", {NAME=n}));
		  else
		     table.insert(data_ilist, subst("\tlocal @NAME@=t:get{name='@NAME@', class=classtable['@ARGCLASS@']}", {NAME=n, ARGCLASS=luatype}));
		  end
	       end -- if not retval
	    end -- for arg
	 end -- if not ~lua
	 table.insert(data_ilist, '\tassert(t:all_done()) -- arg handling ends')
	 
	 -- ########################################################################################
	 -- # Call
	 -- ########################################################################################      
	 if self.lt=="objectmethod" then
	    table.insert(callargs_ilist, 1, "self")
	 end
	 
	 -- ########################################################################################
	 -- # Return values
	 -- ########################################################################################      
	 for index, _ in ipairs(returnstype_ilist) do
	    table.insert(returnsname_ilist, "r"..index)
	 end
	 
	 if table.getn(returnsname_ilist) > 0 then
	    table.insert(data_ilist, subst("\tlocal @RET@=clib.@TYPE@.@FUN@(@ARGS@)", {FUN=self.name, TYPE=otype.luatype, ARGS=table.concat(callargs_ilist, ", "), RET=table.concat(returnsname_ilist, ", ")}));
	 else
	    table.insert(data_ilist, subst("\tclib.@TYPE@.@FUN@(@ARGS@)", {FUN=self.name, TYPE=otype.luatype, ARGS=table.concat(callargs_ilist, ", ")}));
	 end

	 for index, rettype in ipairs(returnstype_ilist) do
	    if rettype=="int" or rettype=="double" or rettype=="string" then
	       -- no conversion necessary
	    else
	       table.insert(data_ilist, "\tlocal rclass=classtable['"..rettype.."']; assert(rclass)")
	       table.insert(data_ilist, "\tlocal cmeta=mnoo.getObjectMetatable(rclass)")
	       table.insert(data_ilist, "\tclib.setmetatable("..returnsname_ilist[index]..", cmeta)")
	    end
	 end
	 
	 if table.getn(returnsname_ilist) > 0 then
	    table.insert(data_ilist, "\treturn "..table.concat(returnsname_ilist, ", "))
	 end

	 -- ########################################################################################
	 -- # Close function
	 -- ########################################################################################      	 
	 table.insert(data_ilist, "end");
	 return table.concat(data_ilist, "\n")
      end
   return cl