-- Purpose of this file:
-- Library builder class for the mnoofltk library

-- load libaries
local mnoo=require("mnoo_current/mnoo.lua")
local args_cl=require("mnoo_current/args_cl.lua")
local type_cl=require("mnoofltk/libBuilder/libBuilderType_cl.lua")
local subst=require("mnoofltk/libBuilder/subst_fun.lua")
setfenv(1, mnoo:createPrivateEnvironment())

local cl=mnoo.newclass()
cl:declareObjectmethod(
		       "registerType", 
		       "write", "writeC", "writeLua", "set_header")
cl:declareObjectdata("cfile", "luafile", "types_ilist", "c_headers_ilist", "cpp_headers_ilist", "libmain", 
		     "sharedObject" -- excludes .so or .dll extension
		  )
cl:setPrivate("writeC", "writeLua")

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 ++++++++++++++++++
      self.cfile=t:get{name="cfile", type="string"}
      self.luafile=t:get{name="luafile", type="string"}
      self.libmain=t:get{name="libmain", type="string"}
      self.sharedObject=t:get{name="sharedObject", type="string"}
      assert(t:all_done())   -- +++++++++++++ arg handling ends ++++++++++++++++++++ 

      self.types_ilist={}
      self.c_headers_ilist={'<lua.h>', '<lualib.h>', '<lauxlib.h>', '<assert.h>'}
      self.cpp_headers_ilist={}
      return self
   end

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

      local t=args_cl:new(t) -- +++++++++++++ arg handling starts ++++++++++++++++++
      local typeo=t:get{name="type", class=type_cl}
      assert(t:all_done())   -- +++++++++++++ arg handling ends ++++++++++++++++++++ 

      table.insert(self.types_ilist, typeo)
   end

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

      local t=args_cl:new(t) -- +++++++++++++ arg handling starts ++++++++++++++++++
      local file=t:get{name="file", type="string"}
      local language=t:get{name="language", type="string"}
      assert(t:all_done())   -- +++++++++++++ arg handling ends ++++++++++++++++++++ 

      if language=="C" then
	 table.insert(self.c_headers_ilist, file)
      elseif language=="C++" then
	 table.insert(self.cpp_headers_ilist, file)
      else assert(nil) end
   end

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

      self:writeC(t)
      self:writeLua(t)
   end
      
cl.writeC=
   function(self, t)
      assert(mnoo.objectIsA(self, cl))
      
      local data_ilist={}

      -- ########################################################################################
      -- # ANSI C headers
      -- ########################################################################################      
      if table.getn(self.c_headers_ilist) > 0 then
	 table.insert(data_ilist, 'extern "C"{')
	 for _, h in ipairs(self.c_headers_ilist) do
	    table.insert(data_ilist, '\t#include '..h) -- entries must be quoted
	 end
	 table.insert(data_ilist, '} /* extern "C" */')
      end

      table.insert(data_ilist, "typedef lua_State luaState_t;")
      table.insert(data_ilist, "int argCount=0;")

      -- ########################################################################################
      -- # C++ headers
      -- ########################################################################################      
      if table.getn(self.cpp_headers_ilist) > 0 then
	 for _, h in ipairs(self.cpp_headers_ilist) do
	    table.insert(data_ilist, '#include '..h) -- entries must be quoted
	 end
      end
      
      -- ########################################################################################
      -- # function bodies
      -- ########################################################################################      
      for _, t in ipairs(self.types_ilist) do
	 table.insert(data_ilist, t:writeC_function_bodies{types_ilist=self.types_ilist})
      end

      -- ########################################################################################
      -- # Implement our own "setmetatable function"
      -- ########################################################################################      
      table.insert(data_ilist, 'static int mnoo_setmetatable(lua_State* l){assert(lua_gettop(l)==2); lua_setmetatable(l, -2); return 0;}')
      
      -- ########################################################################################
      -- # library startup function
      -- ########################################################################################      
      table.insert(data_ilist, 'extern "C" int '..self.libmain.."(lua_State* l){")

      -- ########################################################################################
      -- # Create main library table (that is returned by libmain)
      -- ########################################################################################      
      table.insert(data_ilist, "\tlua_newtable(l); /* table that holds all type tables */")
      for _, t in ipairs(self.types_ilist) do

	 -- ########################################################################################
	 -- # push type tables on stack
	 -- ########################################################################################      
	 table.insert(data_ilist, t:writeC_pushstore_typetable{types_ilist=self.types_ilist})	
      end
      
      -- ########################################################################################
      -- # Add our own "setmetatable function"
      -- ########################################################################################      
      table.insert(data_ilist, '\tlua_pushstring(l, "setmetatable");')
      table.insert(data_ilist, '\tlua_pushcfunction(l, mnoo_setmetatable);')
      table.insert(data_ilist, '\tlua_rawset(l, -3);')

      table.insert(data_ilist, "\treturn 1;")
      table.insert(data_ilist, "} /* int "..self.libmain.." */")

      -- ########################################################################################
      -- # Write to file
      -- ########################################################################################      
      local f, err=io.open(self.cfile, "w") assert(f, err)
      f:write(table.concat(data_ilist, "\n"))
      f:write("\n")
      f:close()
   end

cl.writeLua=
   function(self, t)
      assert(mnoo.objectIsA(self, cl))
      
      local data_ilist={}

      -- ########################################################################################
      -- # Create main library table (that is returned by libmain)
      -- ########################################################################################      
      local startupcode=
	 [[
	    -- automatically generated. Do not edit.")
	    -- Two options:")
	    -- 1) The C library is compiled into the executable: Then the global function "..self.libmain.." returns the C functions")
	    -- 2) A shared library is used and defines "..self.libmain..", which returns the C functions")
	    local clib, err
	    if @LIBMAIN@ then 
	       -- case 1)
	       clib=@LIBMAIN@ -- table type
	    else
	       -- case 2)

	       -- when using dynamic loading it is the user's responsibility to copy the right library version
	       -- at the right place. You can customize the path below:
	       while true do
		  -- windows, linked against 5.0.2
		  if _VERSION=="Lua 5.0.2" then
		     clib, err=loadlib("./lib/@SHAREDOBJECT@50.dll", '@LIBMAIN@')
		     if clib then break end
		  end
		  		  
--		  -- windows, linked against 5.1 (beta)
--		  if _VERSION=="Lua 5.1 (beta)" then
--		     clib, err=loadlib("./lib/@SHAREDOBJECT@51.dll", '@LIBMAIN@')
--		     if clib then break end
--		  end

		  -- linux: No versioning yet
		  clib, err=loadlib("./lib/@SHAREDOBJECT@.so", '@LIBMAIN@')
		  if clib then break end
		  		  
		  break
	       end
	       if clib then
		  -- call library body startup function	    
		  clib, err=clib() assert(clib, err)
	       end
	    end
	    if not clib then error("failed to load C level library") end
	    	    
	    -- mnoo and args_cl may be precompiled into the library. In such a case, @LIBMAIN@ restored them into the _LOADED table,
	    -- so that require() looks up the precompiled code

	    -- _LOADED['mnoo_current/mnoo.lua']=nil -- uncomment this to force loading mnoo.lua from source
	    local mnoo=require('mnoo_current/mnoo.lua')

	    -- _LOADED['mnoo_current/args_cl.lua']=nil -- uncomment this to force loading args_cl.lua from source
	    local args_cl=require('mnoo_current/args_cl.lua')
	    
	    -- common error function for calling default "new" constructor, when it is not available
	    local nonew=function()error('Forbidden to call new(), because this class does not implement a new() constructor') end
	    local errorClassExpected='class expected as 1st arg (use cl:fun{} syntax)'
	    local errorObjectExpected='object expected as 1st arg (use obj:fun{} syntax)'

	    -- library body
	    local classtable={}

	    -- mimic require() behavior, even if this file is executed as precompiled bytecode from c level
	    _LOADED["@LUAFILE@"]=classtable
      ]]

      startupcode=subst(startupcode, {SHAREDOBJECT=self.sharedObject, LIBMAIN=self.libmain, LUAFILE=self.luafile})
      table.insert(data_ilist, startupcode)

      -- ########################################################################################
      -- # Write all types into classtable
      -- ########################################################################################      
      for _, t in ipairs(self.types_ilist) do
	 table.insert(data_ilist, t:writeLua{types_ilist=self.types_ilist})
      end
      
      table.insert(data_ilist, "return classtable;")
      
      -- ########################################################################################
      -- # Write to file
      -- ########################################################################################      
      local f, err=io.open(self.luafile, "w") assert(f, err)
      f:write(table.concat(data_ilist, "\n"))
      f:write("\n")
      f:close()
   end

return cl