-- tolua: container abstract class
-- 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.

-- table to store namespaced typedefs/enums in global scope
global_typedefs = {}
global_enums = {}

-- Container class
-- Represents a container of features to be bound
-- to lua.
classContainer = {
	curr = nil
}
classContainer.__index = classContainer
setmetatable(classContainer, classFeature)

-- output tags
function classContainer:decltype()
	push(self)
	local i = 1
	while self[i] do
		self[i]:decltype()
		i = i + 1
	end
	pop()
end

-- write support code
function classContainer:supcode()
	if not self:check_public_access() then
		return
	end

	push(self)
	local i = 1
	while self[i] do
		if self[i]:check_public_access() then
			self[i]:supcode()
		end
		i = i + 1
	end
	pop()
end

function classContainer:hasvar()
	local i = 1
	while self[i] do
		if self[i]:isvariable() then
			return 1
		end
		i = i + 1
	end
	return 0
end

-- Internal container constructor
function _Container(self)
	setmetatable(self, classContainer)
	self.n = 0
	self.typedefs = {tolua_n = 0}
	self.usertypes = {}
	self.enums = {tolua_n = 0}
	self.lnames = {}
	return self
end

-- push container
function push(t)
	t.prox = classContainer.curr
	classContainer.curr = t
end

-- pop container
function pop()
	--print("name",classContainer.curr.name)
	--foreach(classContainer.curr.usertypes,print)
	--print("______________")
	classContainer.curr = classContainer.curr.prox
end

-- get current namespace
function getcurrnamespace()
	return getnamespace(classContainer.curr)
end

-- append to current container
function append(t)
	return classContainer.curr:append(t)
end

-- append typedef to current container
function appendtypedef(t)
	return classContainer.curr:appendtypedef(t)
end

-- append usertype to current container
function appendusertype(t)
	return classContainer.curr:appendusertype(t)
end

-- append enum to current container
function appendenum(t)
	return classContainer.curr:appendenum(t)
end

-- substitute typedef
function applytypedef(mod, type)
	return classContainer.curr:applytypedef(mod, type)
end

-- check if is type
function findtype(type)
	local t = classContainer.curr:findtype(type)
	return t
end

-- check if is typedef
function istypedef(type)
	return classContainer.curr:istypedef(type)
end

-- get fulltype (with namespace)
function fulltype(t)
	local curr = classContainer.curr
	while curr do
		if curr then
			if curr.typedefs and curr.typedefs[t] then
				return curr.typedefs[t]
			elseif curr.usertypes and curr.usertypes[t] then
				return curr.usertypes[t]
			end
		end
		curr = curr.prox
	end
	return t
end

-- checks if it requires collection
function classContainer:requirecollection(t)
	push(self)
	local i = 1
	local r = false
	while self[i] do
		r = self[i]:requirecollection(t) or r
		i = i + 1
	end
	pop()
	return r
end

-- get namesapce
function getnamespace(curr)
	local namespace = ""
	while curr do
		if curr and (curr.classtype == "class" or curr.classtype == "namespace") then
			namespace = (curr.original_name or curr.name) .. "::" .. namespace
		--namespace = curr.name .. '::' .. namespace
		end
		curr = curr.prox
	end
	return namespace
end

-- get namespace (only namespace)
function getonlynamespace()
	local curr = classContainer.curr
	local namespace = ""
	while curr do
		if curr.classtype == "class" then
			return namespace
		elseif curr.classtype == "namespace" then
			namespace = curr.name .. "::" .. namespace
		end
		curr = curr.prox
	end
	return namespace
end

-- check if is enum
function isenum(type)
	return classContainer.curr:isenum(type)
end

-- append feature to container
function classContainer:append(t)
	self.n = self.n + 1
	self[self.n] = t
	t.parent = self
end

-- append typedef
function classContainer:appendtypedef(t)
	local namespace = getnamespace(classContainer.curr)
	self.typedefs.tolua_n = self.typedefs.tolua_n + 1
	self.typedefs[self.typedefs.tolua_n] = t
	self.typedefs[t.utype] = namespace .. t.utype
	global_typedefs[namespace .. t.utype] = t
	t.ftype = findtype(t.type) or t.type
	--print("appending typedef "..t.utype.." as "..namespace..t.utype.." with ftype "..t.ftype)
	append_global_type(namespace .. t.utype)
	if t.ftype and isenum(t.ftype) then
		global_enums[namespace .. t.utype] = true
	end
end

-- append usertype: return full type
function classContainer:appendusertype(t)
	local container
	if t == (self.original_name or self.name) then
		container = self.prox
	else
		container = self
	end
	local ft = getnamespace(container) .. t
	container.usertypes[t] = ft
	_usertype[ft] = ft
	_userltype[ft] = self.lname
	return ft
end

-- append enum
function classContainer:appendenum(t)
	local namespace = getnamespace(classContainer.curr)
	self.enums.tolua_n = self.enums.tolua_n + 1
	self.enums[self.enums.tolua_n] = t
	global_enums[namespace .. t.name] = t
end

-- determine lua function name overload
function classContainer:overload(lname)
	if not self.lnames[lname] then
		self.lnames[lname] = 0
	else
		self.lnames[lname] = self.lnames[lname] + 1
	end
	return format("%02d", self.lnames[lname])
end

-- applies typedef: returns the 'the facto' modifier and type
function classContainer:applytypedef(mod, type)
	if global_typedefs[type] then
		--print("found typedef "..global_typedefs[type].type)
		local mod1, type1 = global_typedefs[type].mod, global_typedefs[type].ftype
		local mod2, type2 = applytypedef(mod .. " " .. mod1, type1)
		--return mod2 .. ' ' .. mod1, type2
		return mod2, type2
	end
	do
		return mod, type
	end
end

-- check if it is a typedef
function classContainer:istypedef(type)
	local env = self
	while env do
		if env.typedefs then
			local i = 1
			while env.typedefs[i] do
				if env.typedefs[i].utype == type then
					return type
				end
				i = i + 1
			end
		end
		env = env.parent
	end
	return nil
end

function find_enum_var(var)
	if tonumber(var) then
		return var
	end

	local c = classContainer.curr
	while c do
		local ns = getnamespace(c)
		for k, v in pairs(_global_enums) do
			if match_type(var, v, ns) then
				return v
			end
		end
		if c.base and c.base ~= "" then
			c = _global_classes[c:findtype(c.base)]
		else
			c = nil
		end
	end

	return var
end

-- check if is a registered type: return full type or nil
function classContainer:findtype(t)
	t = string.gsub(t, "=.*", "")
	if _basic[t] then
		return t
	end

	local _, _, em = string.find(t, "([&%*])%s*$")
	t = string.gsub(t, "%s*([&%*])%s*$", "")
	p = self
	while p and type(p) == "table" do
		local st = getnamespace(p)

		for i = _global_types.n, 1, -1 do -- in reverse order
			if match_type(t, _global_types[i], st) then
				return _global_types[i] .. (em or "")
			end
		end
		if p.base and p.base ~= "" and p.base ~= t then
			--print("type is "..t..", p is "..p.base.." self.type is "..self.type.." self.name is "..self.name)
			p = _global_classes[p:findtype(p.base)]
		else
			p = nil
		end
	end

	return nil
end

function append_global_type(t, class)
	_global_types.n = _global_types.n + 1
	_global_types[_global_types.n] = t
	_global_types_hash[t] = 1
	if class then
		append_class_type(t, class)
	end
end

function append_class_type(t, class)
	if _global_classes[t] then
		class.flags = _global_classes[t].flags
		class.lnames = _global_classes[t].lnames
		if _global_classes[t].base and (_global_classes[t].base ~= "") then
			class.base = _global_classes[t].base or class.base
		end
	end
	_global_classes[t] = class
	class.flags = class.flags or {}
end

function match_type(childtype, regtype, st)
	--print("findtype "..childtype..", "..regtype..", "..st)
	local b, e = string.find(regtype, childtype, -string.len(childtype), true)
	if b then
		if
			e == string.len(regtype) and
				(b == 1 or
					(string.sub(regtype, b - 1, b - 1) == ":" and
						string.sub(regtype, 1, b - 1) == string.sub(st, 1, b - 1)))
		 then
			return true
		end
	end

	return false
end

function findtype_on_childs(self, t)
	local tchild
	if self.classtype == "class" or self.classtype == "namespace" then
		for k, v in ipairs(self) do
			if v.classtype == "class" or v.classtype == "namespace" then
				if v.typedefs and v.typedefs[t] then
					return v.typedefs[t]
				elseif v.usertypes and v.usertypes[t] then
					return v.usertypes[t]
				end
				tchild = findtype_on_childs(v, t)
				if tchild then
					return tchild
				end
			end
		end
	end
	return nil
end

function classContainer:isenum(type)
	if global_enums[type] then
		return type
	else
		return false
	end

	local basetype = gsub(type, "^.*::", "")
	local env = self
	while env do
		if env.enums then
			local i = 1
			while env.enums[i] do
				if env.enums[i].name == basetype then
					return true
				end
				i = i + 1
			end
		end
		env = env.parent
	end
	return false
end

methodisvirtual = false -- a global

-- parse chunk
function classContainer:doparse(s)
	--print ("parse "..s)

	-- try the parser hook
	do
		local sub = parser_hook(s)
		if sub then
			return sub
		end
	end

	-- try the null statement
	do
		local b, e = string.find(s, "^%s*;")
		if b then
			return strsub(s, e + 1)
		end
	end

	-- try empty verbatim line
	do
		local b, e = string.find(s, "^%s*$\n")
		if b then
			return strsub(s, e + 1)
		end
	end

	-- try Lua code
	do
		local b, e, code = strfind(s, "^%s*(%b\1\2)")
		if b then
			Code(strsub(code, 2, -2))
			return strsub(s, e + 1)
		end
	end

	-- try C code
	do
		local b, e, code = strfind(s, "^%s*(%b\3\4)")
		if b then
			code = "{" .. strsub(code, 2, -2) .. "\n}\n"
			Verbatim(code, "r") -- verbatim code for 'r'egister fragment
			return strsub(s, e + 1)
		end
	end

	-- try C code for preamble section
	do
		local b, e, code = string.find(s, "^%s*(%b\5\6)")
		if b then
			code = string.sub(code, 2, -2) .. "\n"
			Verbatim(code, "")
			return string.sub(s, e + 1)
		end
	end

	-- try default_property directive
	do
		local b, e, ptype = strfind(s, "^%s*TOLUA_PROPERTY_TYPE%s*%(+%s*([^%)%s]*)%s*%)+%s*;?")
		if b then
			if not ptype or ptype == "" then
				ptype = "default"
			end
			self:set_property_type(ptype)
			return strsub(s, e + 1)
		end
	end

	-- try protected_destructor directive
	do
		local b, e = string.find(s, "^%s*TOLUA_PROTECTED_DESTRUCTOR%s*;?")
		if b then
			if self.set_protected_destructor then
				self:set_protected_destructor(true)
			end
			return strsub(s, e + 1)
		end
	end

	-- try 'extern' keyword
	do
		local b, e = string.find(s, "^%s*extern%s+")
		if b then
			-- do nothing
			return strsub(s, e + 1)
		end
	end

	-- try 'virtual' keyworkd
	do
		local b, e = string.find(s, "^%s*virtual%s+")
		if b then
			methodisvirtual = true
			return strsub(s, e + 1)
		end
	end

	-- try labels (public, private, etc)
	do
		local b, e = string.find(s, "^%s*%w*%s*:[^:]")
		if b then
			return strsub(s, e) -- preserve the [^:]
		end
	end

	-- try module
	do
		local b, e, name, body = strfind(s, "^%s*module%s%s*([_%w][_%w]*)%s*(%b{})%s*")
		if b then
			_curr_code = strsub(s, b, e)
			Module(name, body)
			return strsub(s, e + 1)
		end
	end

	-- try namesapce
	do
		local b, e, name, body = strfind(s, "^%s*namespace%s%s*([_%w][_%w]*)%s*(%b{})%s*;?")
		if b then
			_curr_code = strsub(s, b, e)
			Namespace(name, body)
			return strsub(s, e + 1)
		end
	end

	-- try define
	do
		local b, e, name = strfind(s, "^%s*#define%s%s*([^%s]*)[^\n]*\n%s*")
		if b then
			_curr_code = strsub(s, b, e)
			Define(name)
			return strsub(s, e + 1)
		end
	end

	-- try enumerates

	do
		local b, e, name, body, varname = strfind(s, "^%s*enum%s+(%S*)%s*(%b{})%s*([^%s;]*)%s*;?%s*")
		if b then
			--error("#Sorry, declaration of enums and variables on the same statement is not supported.\nDeclare your variable separately (example: '"..name.." "..varname..";')")
			_curr_code = strsub(s, b, e)
			Enumerate(name, body, varname)
			return strsub(s, e + 1)
		end
	end

	-- do
	--  local b,e,name,body = strfind(s,"^%s*enum%s+(%S*)%s*(%b{})%s*;?%s*")
	--  if b then
	--   _curr_code = strsub(s,b,e)
	--   Enumerate(name,body)
	--  return strsub(s,e+1)
	--  end
	-- end

	do
		local b, e, body, name = strfind(s, "^%s*typedef%s+enum[^{]*(%b{})%s*([%w_][^%s]*)%s*;%s*")
		if b then
			_curr_code = strsub(s, b, e)
			Enumerate(name, body)
			return strsub(s, e + 1)
		end
	end

	-- try operator
	do
		local b, e, decl, kind, arg, const =
			strfind(s, "^%s*([_%w][_%w%s%*&:<>,]-%s+operator)%s*([^%s][^%s]*)%s*(%b())%s*(c?o?n?s?t?)%s*;%s*")
		if not b then
			-- try inline
			b, e, decl, kind, arg, const =
				strfind(
				s,
				"^%s*([_%w][_%w%s%*&:<>,]-%s+operator)%s*([^%s][^%s]*)%s*(%b())%s*(c?o?n?s?t?)[%s\n]*%b{}%s*;?%s*"
			)
		end
		if not b then
			-- try cast operator
			b, e, decl, kind, arg, const = strfind(s, "^%s*(operator)%s+([%w_:%d<>%*%&%s]+)%s*(%b())%s*(c?o?n?s?t?)")
			if b then
				local _, ie = string.find(s, "^%s*%b{}", e + 1)
				if ie then
					e = ie
				end
			end
		end
		if b then
			_curr_code = strsub(s, b, e)
			Operator(decl, kind, arg, const)
			return strsub(s, e + 1)
		end
	end

	-- try function
	do
		--local b,e,decl,arg,const = strfind(s,"^%s*([~_%w][_@%w%s%*&:<>]*[_%w])%s*(%b())%s*(c?o?n?s?t?)%s*=?%s*0?%s*;%s*")
		local b, e, decl, arg, const, virt = strfind(s, "^%s*([^%(\n]+)%s*(%b())%s*(c?o?n?s?t?)%s*(=?%s*0?)%s*;%s*")
		if not b then
			-- try function with template
			b, e, decl, arg, const =
				strfind(s, "^%s*([~_%w][_@%w%s%*&:<>]*[_%w]%b<>)%s*(%b())%s*(c?o?n?s?t?)%s*=?%s*0?%s*;%s*")
		end
		if not b then
			-- try a single letter function name
			b, e, decl, arg, const = strfind(s, "^%s*([_%w])%s*(%b())%s*(c?o?n?s?t?)%s*;%s*")
		end
		if not b then
			-- try function pointer
			b, e, decl, arg, const = strfind(s, "^%s*([^%(;\n]+%b())%s*(%b())%s*;%s*")
			if b then
				decl = string.gsub(decl, "%(%s*%*([^%)]*)%s*%)", " %1 ")
			end
		end
		if b then
			if virt and string.find(virt, "[=0]") then
				if self.flags then
					self.flags.pure_virtual = true
				end
			end
			_curr_code = strsub(s, b, e)
			Function(decl, arg, const)
			return strsub(s, e + 1)
		end
	end

	-- try inline function
	do
		local b, e, decl, arg, const = strfind(s, "^%s*([^%(\n]+)%s*(%b())%s*(c?o?n?s?t?)[^;{]*%b{}%s*;?%s*")
		--local b,e,decl,arg,const = strfind(s,"^%s*([~_%w][_@%w%s%*&:<>]*[_%w>])%s*(%b())%s*(c?o?n?s?t?)[^;]*%b{}%s*;?%s*")
		if not b then
			-- try a single letter function name
			b, e, decl, arg, const = strfind(s, "^%s*([_%w])%s*(%b())%s*(c?o?n?s?t?).-%b{}%s*;?%s*")
		end
		if b then
			_curr_code = strsub(s, b, e)
			Function(decl, arg, const)
			return strsub(s, e + 1)
		end
	end

	-- try class
	do
		local b, e, name, base, body
		base = ""
		body = ""
		b, e, name = strfind(s, "^%s*class%s*([_%w][_%w@]*)%s*;") -- dummy class
		local dummy = false
		if not b then
			b, e, name = strfind(s, "^%s*struct%s*([_%w][_%w@]*)%s*;") -- dummy struct
			if not b then
				b, e, name, base, body = strfind(s, "^%s*class%s*([_%w][_%w@]*)%s*([^{]-)%s*(%b{})%s*")
				if not b then
					b, e, name, base, body = strfind(s, "^%s*struct%s+([_%w][_%w@]*)%s*([^{]-)%s*(%b{})%s*")
					if not b then
						b, e, name, base, body = strfind(s, "^%s*union%s*([_%w][_%w@]*)%s*([^{]-)%s*(%b{})%s*")
						if not b then
							base = ""
							b, e, body, name =
								strfind(s, "^%s*typedef%s%s*struct%s%s*[_%w]*%s*(%b{})%s*([_%w][_%w@]*)%s*;")
						end
					end
				end
			else
				dummy = 1
			end
		else
			dummy = 1
		end
		if b then
			if base ~= "" then
				--local b,e
				--b,e,base = strfind(base,".-([_%w][_%w<>,:]*)$")
				base = string.gsub(base, "^%s*:%s*", "")
				base = string.gsub(base, "%s*public%s*", "")
				base = split(base, ",")
			else
				base = {}
			end
			_curr_code = strsub(s, b, e)
			Class(name, base, body)
			if not dummy then
				varb, vare, varname = string.find(s, "^%s*([_%w]+)%s*;", e + 1)
				if varb then
					Variable(name .. " " .. varname)
					e = vare
				end
			end
			return strsub(s, e + 1)
		end
	end

	-- try typedef
	do
		local b, e, types = strfind(s, "^%s*typedef%s%s*(.-)%s*;%s*")
		if b then
			_curr_code = strsub(s, b, e)
			Typedef(types)
			return strsub(s, e + 1)
		end
	end

	-- try variable
	do
		local b, e, decl = strfind(s, "^%s*([_%w][_@%s%w%d%*&:<>,]*[_%w%d])%s*;%s*")
		if b then
			_curr_code = strsub(s, b, e)

			local list = split_c_tokens(decl, ",")
			Variable(list[1])
			if list.n > 1 then
				local _, _, type = strfind(list[1], "(.-)%s+([^%s]*)$")

				local i = 2
				while list[i] do
					Variable(type .. " " .. list[i])
					i = i + 1
				end
			end
			--Variable(decl)
			return strsub(s, e + 1)
		end
	end

	-- try string
	do
		local b, e, decl = strfind(s, "^%s*([_%w]?[_%s%w%d]-char%s+[_@%w%d]*%s*%[%s*%S+%s*%])%s*;%s*")
		if b then
			_curr_code = strsub(s, b, e)
			Variable(decl)
			return strsub(s, e + 1)
		end
	end

	-- try array
	do
		local b, e, decl = strfind(s, "^%s*([_%w][][_@%s%w%d%*&:<>]*[]_%w%d])%s*;%s*")
		if b then
			_curr_code = strsub(s, b, e)
			Array(decl)
			return strsub(s, e + 1)
		end
	end

	-- no matching
	if gsub(s, "%s%s*", "") ~= "" then
		_curr_code = s
		error("#parse error")
	else
		return ""
	end
end

function classContainer:parse(s)
	--self.curr_member_access = nil

	while s ~= "" do
		s = self:doparse(s)
		methodisvirtual = false
	end
end

-- property types

function get_property_type()
	return classContainer.curr:get_property_type()
end

function classContainer:set_property_type(ptype)
	ptype = string.gsub(ptype, "^%s*", "")
	ptype = string.gsub(ptype, "%s*$", "")

	self.property_type = ptype
end

function classContainer:get_property_type()
	return self.property_type or (self.parent and self.parent:get_property_type()) or "default"
end

