-----------------------------------------------
-- script to build lua classes
-- expects lua 5.1 or luajit
-- expects "../cimgui/generator/definitions.lua" to be generated in cimgui (master_auto2 branch)
-----------------------------------------------

local cpp2ffi = require"cpp2ffi"
local ffi = require"ffi"
--utility functions
local ffi_cdef = function(code)
    local ret,err = pcall(ffi.cdef,code)
    if not ret then
        local lineN = 1
        for line in code:gmatch("([^\n\r]*)\r?\n") do
            print(lineN, line)
            lineN = lineN + 1
        end
        print(err)
        error"bad cdef"
    end
end

local strsplit = cpp2ffi.strsplit

-------------------------------------------------
-------------------------------------------------
local enumsvalues = {}



--[[ tests
require"anima.utils" --gives us prtable
-- prtable(structs.ImFontConfig)
-- prtable(fundefs.igCombo)
local defaults = {}
for fun,defs in pairs(fundefs) do
	for i,def in ipairs(defs) do
		for k,v in pairs(def.defaults) do
			defaults[v]=true
		end
	end
end
prtable(defaults)
do return end
--]]

--test correctness of generated lua code
local function testcode(codestr)
	local fl,err = loadstring(codestr)
	if not fl then
		local linenum = err:match(":(%d+):")
		linenum = tonumber(linenum)
		--print("error on:",linenum,code[linenum])
		local lineN,codelines = 1,{}
        for line in codestr:gmatch("([^\n\r]*)\r?\n") do
            print(lineN, line)
			table.insert(codelines, line)
            lineN = lineN + 1
        end
		print("error is ",err)
		print("in line: ", codelines[linenum])
		error("error in testcode",2)
	end
end

--this replaces reserved lua words and not valid tokens
local function sanitize_reserved(def)
	local words = {["in"]="_in",["repeat"]="_repeat"}
	for k,w in pairs(words) do
		local pat = "([%(,])("..k..")([,%)])"
		if def.call_args:match(pat) then
			--print("found",def.cimguiname,def.call_args,def.call_args:match(pat))
			def.call_args = def.call_args:gsub(pat,"%1"..w.."%3")
			--print(def.call_args)
			--sanitize defaults
			if def.defaults[k] then
				def.defaults[w] = def.defaults[k]
				def.defaults[k] = nil
			end
		end
	end
	--correct default vals
	for k,v in pairs(def.defaults) do
		--if type(v)=="number" then print("aver",def.cimguiname,type(v),k,v) end
		--do only if not a c string
		local is_cstring = v:sub(1,1)=='"' and v:sub(-1,-1) =='"'
		if not is_cstring then
			if v:match"::" then --could be nested enum
				local enumname = v:gsub("[%w:]-::([%w]+)","%1")
				local ok,val = pcall(cpp2ffi.parse_enum_value,enumname,enumsvalues)
				if ok then
					def.defaults[k] = val
				else
					print("deleting default ",v)
					def.defaults[k] = nil
				end
			elseif enumsvalues[v] then
				def.defaults[k] = enumsvalues[v]
			else
				local ok,val = pcall(cpp2ffi.parse_enum_value,v,enumsvalues,true)
				if ok then
				def.defaults[k] = val
				elseif def.defaults[k]:match"FLT_MAX" then
					def.defaults[k] = def.defaults[k]:gsub("FLT_MAX","M.FLT_MAX")
				elseif def.defaults[k]:match"FLT_MIN" then
					def.defaults[k] = def.defaults[k]:gsub("FLT_MIN","M.FLT_MIN")
				else
				--numbers without f in the end
				def.defaults[k] = v:gsub("([%d%.%-]+)[Ff]","%1")
				--+ in front of numbers
				def.defaults[k] = def.defaults[k]:gsub("^%+([%d%.%-]+)","%1")
				
				def.defaults[k] = def.defaults[k]:gsub("ImDrawCornerFlags_All","lib.ImDrawCornerFlags_All")
				def.defaults[k] = def.defaults[k]:gsub("sizeof%((%w+)%)",[[ffi.sizeof("%1")]])
				def.defaults[k] = def.defaults[k]:gsub("%(%(void%s*%*%)0%)","nil")
				def.defaults[k] = def.defaults[k]:gsub("NULL","nil")
				def.defaults[k] = def.defaults[k]:gsub("nullptr","nil")
				end
				--if def.defaults[k]:match"~" then
				--	def.defaults[k] = bit.bnot
				--end
			end
		end
	end
end

local function ArgumentType(def,name)
	for i,arg in ipairs(def.argsT) do
		if arg.name == name then
			return arg.type
		end
	end
end

local function make_function(method,def)
	if def.skipped then return "" end
	if def.is_static_function then method = nil end
	sanitize_reserved(def)
	local fname = def.ov_cimguiname or def.cimguiname --overloaded or original
	local fname_m = method and fname:match(def.stname.."_(.*)") or fname:match("^ig(.*)") or fname --drop struct name part
	fname_m = fname_m:match("(.*)_nonUDT$") or fname_m --drop "_nonUDT" suffix
	if fname_m == "end" then fname_m = "_end" end
	--dump function code
	if def.nonUDT == 1 or next(def.defaults) then
		local call_args = def.call_args:gsub("%*","")
		local code = {}
		local args, fname_lua
		local empty = call_args:match("^%(%)") --no args
		if method and not def.is_static_function then
			args = call_args:gsub("^%(","(self"..(empty and "" or ","))
			fname_lua = def.stname..":"..fname_m
			empty = false
		else
			args = call_args
			fname_lua = "M."..fname_m
		end
		table.insert(code,"function "..fname_lua..call_args)
		--set defaults
		cpp2ffi.table_do_sorted(def.defaults, function(k,v)
		--for k,v in pairs(def.defaults) do
			--special default treatment for ImStrv
			if ArgumentType(def,k) == "ImStrv" then
				--print("default for ImStrv",v)
				if v:match"ImStrv()" then
					table.insert(code,"    "..k.." = "..k.." or M.ImStrv()")
				else
					table.insert(code,"    "..k.." = "..k.." or M.ImStrv("..v..")")
				end
			else
				if v == 'true' then
					table.insert(code,"    if "..k.." == nil then "..k.." = "..v.." end")
				else
					table.insert(code,"    "..k.." = "..k.." or "..v)
				end
			end
		--end
		end)
		if def.nonUDT == 1 then
			--allocate variable for return value
			local out_type = def.argsT[1].type:gsub("*", "")
			table.insert(code,'    local nonUDT_out = ffi.new("'..out_type..'")')
			--prepend nonUDT_out to args
			args = args:gsub("%(", "(nonUDT_out" .. (empty and "" or ","), 1)
			--call cimgui and return value of out variable
			table.insert(code,"    lib."..fname..args)
			table.insert(code,"    return nonUDT_out")
		else
			--call cimgui
			table.insert(code,"    return lib."..fname..args)
		end
		table.insert(code,"end")
		return table.concat(code,"\n")
	end
	--for no nonUDT and no defaults
	return (method and def.stname or "M").."."..fname_m.." = lib."..fname
end

--struct constructor generator
local function constructor_gen(code,def)
	sanitize_reserved(def)
	--dump function code
	if def.cimguiname == def.ov_cimguiname then --default constructor
		local args = (def.call_args == "()") and "(ctype)" or "(ctype,"..def.call_args:sub(2)
		table.insert(code,"function "..def.stname..".__new"..args)
	else
		local name = def.ov_cimguiname:match(def.stname.."_(.*)") --drop struct name part
		table.insert(code,"function "..def.stname.."."..name..def.call_args)
	end
	--set defaults
	cpp2ffi.table_do_sorted(def.defaults, function(k,v)
	--for k,v in pairs(def.defaults) do
		table.insert(code,"    if "..k.." == nil then "..k.." = "..v.." end")
	--end
	end)
	local fname = def.ov_cimguiname or def.cimguiname
	table.insert(code,"    local ptr = lib."..fname..def.call_args)
	table.insert(code,"    return ffi.gc(ptr,lib."..def.stname.."_destroy)")
	table.insert(code,"end")
end

--struct function generator
local function struct_function_gen(code,def)
	table.insert(code,make_function(true,def))
end

--top level function generator (ImGui namespace)
local function function_gen(code,def)
	table.insert(code,make_function(false,def))
end

local cdefs = dofile("./imgui/cdefs.lua")
ffi_cdef(cdefs)

--

-- test for pointers with unsigned-signed confusion and * vs []
local function GENp(typ,va)
	local typ2 = typ:gsub("%*","[?]")
	return "ffi.typeof('"..typ.."') == ffi.typeof("..va..") or ffi.typeof('const "..typ.."') == ffi.typeof("..va..") or ffi.typeof('"..typ2.."') == ffi.typeof("..va..") or ffi.typeof('const "..typ2.."') == ffi.typeof("..va..")"
end
--test against type or const type
local function CHK(typ,ti)
	return ffi.typeof(typ) == ffi.typeof(ti) or ffi.typeof(typ) == ffi.typeof("const "..ti)
end

--not confused by ffi.istype
local types = {"void *","int8_t","uint8_t","int16_t","uint16_t","int32_t","uint32_t","int64_t","uint64_t","float","double","bool"}
local function CHK_types(typ,va)
	for i=1,#types do
		if CHK(typ,types[i]) then
			local typ1 = types[i]
			if typ1=="void *" then
				return "ffi.istype('"..typ1.."',"..va..")"
			elseif typ1== "bool" then
				return "(ffi.istype('"..typ1.."',"..va..") or type("..va..")=='boolean')"
			else -- let it be a number
				return "(ffi.istype('"..typ1.."',"..va..") or type("..va..")=='number')"
			end
		end
	end
end

--signed unsigned confusion of ffi.istype with pointers
local UStypes = {"int8_t*","uint8_t*","int16_t*","uint16_t*","int32_t*","uint32_t*","int64_t*","uint64_t*"}
local function CHK_types_p(typ,va)
	for i=1,#UStypes do
		if CHK(typ,UStypes[i]) then
			return GENp(UStypes[i],va)
		end
	end
end

--not signed unsigned confusion of ffi.istype with pointers
local Ptypes = {"float*","double*","bool*"}
local function CHK_types_p2(typ,va)
	for i=1,#Ptypes do
		if CHK(typ,Ptypes[i]) then
			local typ1 = Ptypes[i]
			local typ2 = typ1:gsub("%*","[]")
			return "(ffi.istype('"..typ1.."',"..va..") or ffi.istype('"..typ2.."',"..va.."))"
		end
	end
end

local badtypes = {}
local function checktype(typ,va,fname)
	local cond = CHK_types(typ,va)
	if cond then return cond end
	
	if ffi.typeof(typ)==ffi.typeof"const char*" then
		return "(ffi.istype('"..typ.."',"..va..") or ffi.istype('char[]',"..va..") or type("..va..")=='string')"
	end
	
	cond = CHK_types_p(typ,va)
	if cond then return cond end
	
	cond = CHK_types_p2(typ,va)
	if cond then return cond end
	
	if not tostring(ffi.typeof(typ)):find"struct" then
		badtypes[typ] = fname or true
	end
	if typ:match"%*" and not typ:match"%(%*%)" then --pointer not function pointer
		local typsinptr = typ:gsub("(%*)","")
		local extra = " or ffi.istype('"..typsinptr.."',"..va..")"
		local extra2 = " or ffi.istype('"..typsinptr.."[]',"..va..")"
		return "(ffi.istype('"..typ.."',"..va..")"..extra..extra2..")"
	end
	return "ffi.istype('"..typ.."',"..va..")"

end

local function gen_args(method,def,minvararg)
	local n = #def.argsT
	local args = ""
	local isvararg
	if minvararg < math.huge and n >= minvararg then
		n = minvararg - 1
		isvararg = true
	end
	local ini = 1
	if method then ini = ini + 1 end
	if def.nonUDT then ini = ini + 1 end
	for i=ini,n do
		args = args.."a"..i..","
	end
	if isvararg then
		args = args .. "...,"
	end
	args = args:sub(1,-2) --drop last 
	return args
end

--require"anima.utils" --gives us prtable
local function create_generic(code,defs,method)

	if defs[1].skipped then return end
	if defs[1].is_static_function then
		method = nil
	end
	
	if defs[1].nonUDT then print("create_generic nonUTD",defs[1].cimguiname) end
	
	local methodnotconst = method and not defs[1].constructor
	--find max number of arguments
	local maxnargs = -1
	local minvararg = math.huge
	local is_vararg
	for i,def in ipairs(defs) do
		if #def.argsT> 0 and def.argsT[#def.argsT].type == "..." then
			minvararg = minvararg > #def.argsT and #def.argsT or minvararg
			--print("...",def.cimguiname,#def.argsT)
		end
		maxnargs = maxnargs < #def.argsT and #def.argsT or maxnargs
	end
	is_vararg = minvararg < math.huge
	--if is_vararg then print("maxnargs",defs[1].cimguiname,maxnargs,minvararg) end
	--[[
	for i,def in ipairs(defs) do
		io.write(def.ov_cimguiname," , ")
	end
	print()
	for i=1,maxnargs do
		io.write(i," ")
		for j,def in ipairs(defs) do
			io.write(def.argsT[i] and def.argsT[i].type or "nil")
			io.write", "
		end 
		print()
	end
	--]]
	--if methodnotconst and defs[1].nonUDT then print("zzzzz",defs[1].cimguiname) end
	--find first different arg
	local keys = {}
	local done = {}
	local check = {}
	local maxnargs2 = is_vararg and minvararg-1 or maxnargs
	local ini_i= methodnotconst and 2 or 1
	ini_i= defs[1].nonUDT and ini_i + 1 or ini_i
	for i=ini_i,maxnargs2 do
		keys[i] = {}
		for j=1,#defs do
			if not done[j] then
				local tt = defs[j].argsT[i] and defs[j].argsT[i].type or "nil"
				--keys[i][tt] = (keys[i][tt] or 0) + 1
				keys[i][tt] = keys[i][tt] or {}
				table.insert(keys[i][tt],j)
			end
		end
		local keycount = 0
		for k,v in pairs(keys[i]) do keycount = keycount + 1 end
		--print("keycount",i,keycount,#defs)
		for j=1,#defs do
			if not done[j] then
				local tt = defs[j].argsT[i] and defs[j].argsT[i].type or "nil"
				if keycount > 1 then  -- if more than one posible type then keep check
					check[j] = check[j] or {}
					check[j][i]=tt
				end
				if #keys[i][tt] == 1 then -- if one type is exclusive of one overload we are done
					done[j]= true;
				end
			end
		end
	end
	--for j=1,#defs do if not done[j] then print("not done",defs[1].cimguiname) end end
	--[[
	--for decision tree
	local ordered_check = {}
	for i=1,#check do
		ordered_check[i] = {}
		for k,v in pairs(check[i]) do
			table.insert(ordered_check[i],{k,v})
		end
		table.sort(ordered_check[i],function(a,b) return a[1] < b[1] end)
	end
	local Tree = {}
	
	
	--if is_vararg then cpp2ffi.prtable(keys,done,check) end
	if defs[1].cimguiname == "igImLerp" then cpp2ffi.prtable(defs,keys,done,check, ordered_check) end
	--]]
	--if defs[1].cimguiname == "ImPlot_PlotLine" then cpp2ffi.prtable(defs,keys,done,check) end
	--for decision tree by variable
	
	--do generic--------------
	local code2 = {}
	--create args
	if is_vararg then maxnargs = minvararg-1 end
	local args = "" --method and "self," or ""
	if methodnotconst then
		if defs[1].nonUDT then
			for i=3,maxnargs do
				args = args.."a"..i..","
			end
		else		
			for i=2,maxnargs do
				args = args.."a"..i..","
			end
		end
	elseif method then --constructor
		args = maxnargs==0 and "ctype" or "ctype,"
		for i=1,maxnargs do args = args.."a"..i.."," end
	else
		if defs[1].nonUDT then
			--print("mmmm",defs[1].cimguiname)
			for i=2,maxnargs do
				args = args.."a"..i..","
			end
		else		
			for i=1,maxnargs do
				args = args.."a"..i..","
			end
		end
	end
	if is_vararg then args = args.."...," end
	args = args:sub(1,-2) --drop last ,
	
	local fname = defs[1].cimguiname 
	local fname_e = method and fname:match(defs[1].stname.."_(.*)") or fname:match("^ig(.*)") or fname--drop struct name part
	fname = method and (methodnotconst and defs[1].stname..":"..fname_e or defs[1].stname..".__new") or "M."..fname_e

	table.insert(code2, "function "..fname.."("..args..") -- generic version")
	
	for i=1,#check do
		local chk = check[i]
		table.insert(code2,"\n    if ")
		local addand = false
		for k,v in pairs(chk) do
			assert(k < minvararg, fname)
			if addand then table.insert(code2," and ") end
			if v=="nil" then
				table.insert(code2,"a"..k.."==nil")
			else
			---------------------which function is using this overloading type
				-- if v:find"short" and not v:find"%*" then
					-- print("short",defs[1].cimguiname)
				-- end
			-----------------
				local strcode = checktype(v,"a"..k,fname)
				--if has a default take nil as valid check
				--print("defs[i].defaults[k]",defs[i].ov_cimguiname,defs[i].defaults[defs[i].argsT[k].name])
				if defs[i].defaults[defs[i].argsT[k].name]~=nil then
					--if defs[i].argsT[k].type~="ImStrv" then 
					--print("---overload with default",defs[i].ov_cimguiname)
					strcode = "("..strcode.." or type(a"..k..")=='nil')"
					--end
				end
				table.insert(code2 , strcode)
				---table.insert(code2,"ffi.istype('"..v.."',a"..k..")")
			end
			addand = true
		end
		local fname2 = defs[i].ov_cimguiname 
		local fname2_e = method and fname2:match(defs[1].stname.."_(.*)") or fname2:match("^ig(.*)") or fname2 --drop struct name part
		fname2 = method and (methodnotconst and "self:"..fname2_e or defs[1].stname.."."..fname2_e) or "M."..fname2_e
		table.insert(code2," then return "..fname2.."("..gen_args(methodnotconst,defs[i],minvararg)..") end")
		if fname_e == fname2_e then
			print("--------error cimguiname equals ov_cimguiname in overloaded function",fname)
			--error"cimguiname equals ov_cimguiname"
		end
	end
	table.insert(code2,"\n    print("..args..")")
	table.insert(code2,"\n    error'"..fname.." could not find overloaded'\nend")
	table.insert(code, table.concat(code2))
end

--struct code generator
local function code_for_struct(st,fundefs,structs)
	local funs = structs[st]
	table.sort(funs)
	local code = {}
	table.insert(code,"--------------------------"..st.."----------------------------")
	--declare struct
	table.insert(code,"local "..st.."= {}")
	table.insert(code,st..".__index = "..st)
	for _,f in ipairs(funs) do
		local defs = fundefs[f]
		for _,def in ipairs(defs) do
			if not def.destructor then
				if def.constructor then
					constructor_gen(code,def)
				else
					struct_function_gen(code,def)
				end
			end
		end
		if #defs > 1 then
			create_generic(code,defs,true)
		end
	end
	table.insert(code,[[M.]]..st..[[ = ffi.metatype("]]..st..[[",]]..st..")")
	local codestr = table.concat(code,"\n")
	--test correctness of code
	testcode(codestr)
	return codestr
end
local FREETYPE = false
--ImGui namespace generator
local function code_for_imguifuns(st,fundefs,structs)
	local funs = structs[st]
	table.sort(funs)
	local code = {}
	table.insert(code,"--------------------------"..st.."----------------------------")
	for _,f in ipairs(funs) do
		local defs = fundefs[f]
		for _,def in ipairs(defs) do
			if FREETYPE or (not def.location:match"imgui_freetype") then
			def.stname = "M"
			function_gen(code,def)
			end
		end
		--if has overloading create the generic
		if #defs > 1 then
			create_generic(code, defs)
		end
	end
	local codestr = table.concat(code,"\n")
	--test correctness of code
	testcode(codestr)
	return codestr
end

local function make_enums(sources)
	for i,v in ipairs(sources) do
		local standenu = dofile([[../]]..v..[[/generator/output/structs_and_enums.lua]])
		for k,enu in pairs(standenu.enums) do
			for i,v in ipairs(enu) do
				assert(v.calc_value)
				enumsvalues[v.name] = v.calc_value
			end
		end
	end
end

local function make_funcdefs(sources)
	local fundefs = {}
	for i,v in ipairs(sources) do
		local fundefs1 = dofile([[../]]..v..[[/generator/output/definitions.lua]])
		for fun,defs in pairs(fundefs1) do
			fundefs[fun] = defs
		end
	end
	return fundefs
end
--------------------------------------------------------------

local function class_gen(sources, ft_gen)
	FREETYPE = ft_gen
	--firs get enumsvalues table
	make_enums(sources)
	local fundefs = make_funcdefs(sources)
	--group them by structs
	local structs = {}
	local skipped = {}
	for fun,defs in pairs(fundefs) do
		local stname = defs[1].stname
		if not defs[1].templated then
			structs[stname] = structs[stname] or {}
			table.insert(structs[stname],fun)
		else
			skipped[stname] = true
		end
	end
	print"---skipped templated structs"
	for k,_ in pairs(skipped) do
		print(k)
	end
	--delete templated
	structs.ImVec4 = nil
	structs.ImVector = nil
	structs.ImChunkStream = nil
	structs.ImPool = nil
	---------------------------------------------------------------
	local strout = {}
	--Do generation
	table.insert(strout,"----------BEGIN_AUTOGENERATED_LUA---------------------------")
	cpp2ffi.table_do_sorted(structs, function(struct,funs)
		if struct ~= "ImGui" and struct ~= "ImVec2" and struct ~= "ImStrv" and struct ~= "" then
			table.insert(strout,code_for_struct(struct,fundefs, structs))
		end
	end)
	
	table.insert(strout,code_for_imguifuns("",fundefs, structs))--("ImGui"))
	
	------------------------------------
	print"----------------------------"
	for k,v in pairs(badtypes) do
		print("badtype",k,ffi.typeof(k),v)
	end
	print"----------------------------"
	------------------------------------
	
	
	table.insert(strout,"return M")
	table.insert(strout,"----------END_AUTOGENERATED_LUA-----------------------------")
	return table.concat(strout,"\n")
	end

return class_gen

