local codegen = require "codegen"
local idl = codegen.idl "bgfx.idl"

local template = [[
/+
+ ┌==============================┐
+ │ AUTO GENERATED! DO NOT EDIT! │
+ └==============================┘
+/
module bgfx;

import bindbc.common.types: c_int64, c_uint64, va_list;
import bindbc.bgfx.config;
static import bgfx.impl;

$version

alias ViewID = ushort;

enum invalidHandle(T) = T(ushort.max);

alias ReleaseFn = extern(C++) void function(void* ptr, void* userData) nothrow;

$types
pragma(inline,true) nothrow @nogc{
	/**
	Allocate a buffer to pass to bgfx. Data will be freed inside bgfx.
	Params:
		size = Size to allocate.
	*/
	MemoryRef alloc(uint size){
		auto mem = bgfx.impl.alloc(__traits(parameters));
		return MemoryRef(cast(ubyte[])mem.data[0..mem.size], mem);
	}
	/**
	Allocate a buffer to pass to bgfx and copy `data` into it. Data will be freed inside bgfx.
	Params:
		data = Pointer to data to be copied.
		size = Size of data to be copied.
	*/
	MemoryRef copy(const(void)* data, uint size){
		auto mem = bgfx.impl.copy(__traits(parameters));
		return MemoryRef(cast(ubyte[])mem.data[0..mem.size], mem);
	}
	
	pure @safe:
	StateBlend_ blendFuncSeparate(StateBlend_ srcRGB, StateBlend_ dstRGB, StateBlend_ srcA, StateBlend_ dstA){
		return (srcRGB | ((dstRGB) << 4)) | ((srcA | (dstA << 4)) << 8);
	}
	
	///Blend equation separate.
	StateBlendEquation_ blendEquationSeparate(StateBlendEquation_ equationRGB, StateBlendEquation_ equationA){
		return equationRGB | (equationA << 3);
	}
	
	///Blend function.
	StateBlend_ blendFunc(StateBlend_ src, StateBlend_ dst){ return blendFuncSeparate(src, dst, src, dst); }
	
	///Blend equation.
	StateBlendEquation_ blendEquation(StateBlendEquation_ equation){ return blendEquationSeparate(equation, equation); }
	
	///Utility predefined blend modes.
	enum StateBlendFunc: StateBlend_{
		///Additive blending.
		add = blendFunc(StateBlend.one, StateBlend.one),
		
		///Alpha blend.
		alpha = blendFunc(StateBlend.srcAlpha, StateBlend.invSrcAlpha),
		
		///Selects darker color of blend.
		darken = blendFunc(StateBlend.one, StateBlend.one) | blendEquation(StateBlendEquation.min),
		
		///Selects lighter color of blend.
		lighten = blendFunc(StateBlend.one, StateBlend.one) | blendEquation(StateBlendEquation.max),
		
		///Multiplies colors.
		multiply = blendFunc(StateBlend.dstColor, StateBlend.zero),
		
		///Opaque pixels will cover the pixels directly below them without any math or algorithm applied to them.
		normal = blendFunc(StateBlend.one, StateBlend.invSrcAlpha),
		
		///Multiplies the inverse of the blend and base colors.
		screen = blendFunc(StateBlend.one, StateBlend.invSrcColor),
		
		///Decreases the brightness of the base color based on the value of the blend color.
		linearBurn = blendFunc(StateBlend.dstColor, StateBlend.invDstColor) | blendEquation(StateBlendEquation.sub),
	}
	
	StateBlend_ blendFuncRTx(StateBlend_ src, StateBlend_ dst){
		return cast(uint)(src >> StateBlend.shift) | (cast(uint)(dst >> StateBlend.shift) << 4);
	}
	
	StateBlend_ blendFuncRTxE(StateBlend_ src, StateBlend_ dst, StateBlendEquation_ equation){
		return blendFuncRTx(src, dst) | (cast(uint)(equation >> StateBlendEquation.shift) << 8);
	}
	
	StateBlend_ blendFuncRT1(StateBlend_ src, StateBlend_ dst){ return blendFuncRTx(src, dst) <<  0; }
	StateBlend_ blendFuncRT2(StateBlend_ src, StateBlend_ dst){ return blendFuncRTx(src, dst) << 11; }
	StateBlend_ blendFuncRT3(StateBlend_ src, StateBlend_ dst){ return blendFuncRTx(src, dst) << 22; }
	
	StateBlend_ blendFuncRT1E(StateBlend_ src, StateBlend_ dst, StateBlendEquation_ equation){
		return blendFuncRTxE(src, dst, equation) <<  0;
	}
	StateBlend_ blendFuncRT2E(StateBlend_ src, StateBlend_ dst, StateBlendEquation_ equation){
		return blendFuncRTxE(src, dst, equation) << 11;
	}
	StateBlend_ blendFuncRT3E(StateBlend_ src, StateBlend_ dst, StateBlendEquation_ equation){
		return blendFuncRTxE(src, dst, equation) << 22;
	}
}

/**
A wrapper around `Memory` that allows you to mutate its `data`.
Do not create instances of this struct yourself.
*/
struct MemoryRef{
	ubyte[] data;
	const(Memory)* memory;
	alias memory this;
}

$structs
mixin(joinFnBinds((){
	FnBind[] ret = [
		$funcs
	];
	return ret;
}(), $membersWithFns));

static if(!staticBinding):
import bindbc.loader;

debug{
	mixin(makeDynloadFns("Bgfx", makeLibPaths(["bgfx-shared-libDebug", "bgfxDebug", "bgfx"]), [__MODULE__, "bgfx.impl"]));
}else{
	mixin(makeDynloadFns("Bgfx", makeLibPaths(["bgfx-shared-libRelease", "bgfxRelease", "bgfx"]), [__MODULE__, "bgfx.impl"]));
}
]]

local dKeywords = {"abstract", "alias", "align", "asm", "assert", "auto", "bool", "break", "byte", "case", "cast", "catch", "char", "class", "const", "continue", "dchar", "debug", "default", "delegate", "deprecated", "do", "double", "else", "enum", "export", "extern", "false", "final", "finally", "float", "for", "foreach", "foreach_reverse", "function", "goto", "if", "immutable", "import", "in", "inout", "int", "interface", "invariant", "is", "lazy", "long", "macro", "mixin", "module", "new", "nothrow", "null", "out", "override", "package", "pragma", "private", "protected", "public", "pure", "real", "ref", "return", "scope", "shared", "short", "static", "struct", "super", "switch", "synchronized", "template", "this", "throw", "true", "try", "typeid", "typeof", "ubyte", "uint", "ulong", "union", "unittest", "ushort", "version", "void", "wchar", "while", "with"}

local function contains(table, val)
	for i=1,#table do
		if table[i] == val then
			return true
		end
	end
	return false
end

local function hasPrefix(str, prefix)
	return prefix == "" or str:sub(1, #prefix) == prefix
end

local function hasSuffix(str, suffix)
	return suffix == "" or str:sub(-#suffix) == suffix
end

local enumTypes = {}
local membersWithFns = ""

local capsWords = {"Mip", "Id", "Rw", "Vb", "Ib", "Cb", "Rt", "Pci", "Srgb", "Pt", "Ccw", "2d", "3d", "Msaa"}
local capsRepl = {
	hidpi = "hiDPI", lineaa = "lineAA", maxanisotropy = "maxAnisotropy",
	notequal = "notEqual", gequal = "gEqual", Lequal = "LEqual", lequal = "lEqual",
	decrsat = "decrSat", incrsat = "incrSat", revsub = "revSub",
	linestrip = "lineStrip", tristrip = "triStrip",
	bstencil = "bStencil", fstencil = "fStencil",
	Rmask = "RMask",
}

local function abbrevsToUpper(name)
	if name:len() >= 3 then
		for _, abbrev in pairs(capsWords) do
			name = name:gsub(abbrev, function(s0)
				return s0:upper()
			end)
		end
		for from, to in pairs(capsRepl) do
			name =  name:gsub(from, to)
		end
	end
	return name
end

local function toCamelCase(name)
	if name:len() >= 3 then
		name = name:sub(0, 1) .. name:sub(2, -2):gsub("_", "") .. name:sub(-1)
	end
	if name:find("%u%u+%l") then
		name = name:gsub("(%u-)(%u%l)", function(s0, s1)
			return s0:lower() .. s1
		end)
	else
		name = (name:gsub("^([^%l]*)(%l?)", function(s0, s1)
			if s1 ~= nil then
				return s0:lower() .. s1
			end
			return s0:lower()
		end))
	end
	return abbrevsToUpper(name)
end

-- local function toPascalCase(name)
-- 	return (name:gsub("^%l", string.upper))
-- end

local usEnSubs = {
	color = "colour", Color = "Colour",
	rasterize = "rasterise", Rasterize = "Rasterise",
	initialize = "initialise", Initialize = "Initialise",
	normalize = "normalise", Normalize = "Normalise",
	normalized = "normalised", Normalized = "Normalised",
	ccw = "acw", CCW = "ACW",
}
local function toIntlEn(name)
	local change = false
	for us, intl in pairs(usEnSubs) do
		if name:find(us) then
			name = (name:gsub(us, intl))
			change = true
		end
	end
	if change then
		return name
	else
		return nil
	end
end

-- Unconditionally convert
local function toIntlEnUncond(name)
	local newName = toIntlEn(name)
	if newName ~= nil then
		return newName
	end
	return name
end

local function hexStr(val, bits)
	local digits = bits / 4
	local str = string.format(string.format("%%0%iX", digits), val)
	local i = 4
	while i < str:len() do
		str = str:sub(0, i) .. "_" .. str:sub(i+1)
		i = i + 5
	end
	
	return "0x" .. str
end

local function convArray(array)
	if string.find(array, "::") then
		return string.gsub(array, "::.*", ".") .. toCamelCase(string.gsub(array, ".-::", ""))
	else
		return array
	end
end

local typeSubs = {
	{"uint32_t",  "uint"},     {"int32_t", "int"},
	{"uint16_t",  "ushort"},   {"int16_t", "short"},
	{"uint64_t",  "c_uint64"}, {"int64_t", "c_int64"},
	{"uint8_t",   "ubyte"},    {"int8_t",  "byte"},
	{"uintptr_t", "size_t"}
}
local function convSomeType(arg, isFnArg)
	local type = arg.fulltype
	if type == "bx::AllocatorI*" or type == "CallbackI*" then
		type = "void*"
	else
		for _, item in ipairs(typeSubs) do
			if type:find(item[1]) then
				type = type:gsub(item[1], item[2])
				break
			end
		end
		type = type:gsub("::Enum", "") --fix enums
		type = type:gsub("%s+%*", "*") --remove spacing before `*`
		
		if isFnArg then
			for _, enum in pairs(enumTypes) do --fix C++ linkage errors
				if type == enum then
					type = string.format("bgfx.impl.%s.Enum", enum)
				else
					type = (type:gsub("(" .. enum .. ")([^A-Za-z0-9_])", function(s0, s1)
						return string.format("bgfx.impl.%s.Enum", enum) .. s1
					end))
				end
			end
			type = type:gsub("([^&]-)%s?&", "ref %1") --change `&` suffix to `ref` prefix
			if arg.array ~= nil then
				type = type .. "*" --append *
			end
		else
			type = type:gsub("([^&]-)%s?&", "%1*") --change `&` to `*`
			if arg.array ~= nil then
				type = type .. convArray(arg.array) --append [n]
			end
		end
		type = type:gsub("const%s+([A-Za-z_][A-Za-z0-9_]*)%s*%*", "const(%1)*") --change `const x*` to `const(x)*`
		type = abbrevsToUpper(type)
	end
	
	return type
end

local function convType(arg)
	return convSomeType(arg, false)
end

local function convFnArgType(arg)
	return convSomeType(arg, true)
end

local valSubs = {
	NULL = "null",
	UINT8_MAX = "ubyte.max",
	UINT16_MAX = "ushort.max",
	UINT32_MAX = "uint.max",
	
	BGFX_INVALID_HANDLE = "invalidHandle",
	
	BGFX_DISCARD_ALL = "Discard.all",
	BGFX_BUFFER_NONE = "Buffer.none",
	BGFX_STENCIL_NONE = "Stencil.none",
	BGFX_TEXTURE_NONE = "Texture.none",
	BGFX_SAMPLER_NONE = "Sampler.none",
	BGFX_RESET_NONE = "Reset.none",
	BGFX_SAMPLER_U_CLAMP = "SamplerU.clamp",
	BGFX_SAMPLER_V_CLAMP = "SamplerV.clamp",
	BGFX_RESOLVE_AUTO_GEN_MIPS = "Resolve.autoGenMIPs",
	["ViewMode::Default"] = "ViewMode.default_",
}
local function convVal(arg, type)
	local val = string.format("%s", arg)
	for from, to in pairs(valSubs) do
		if val:find(from) then
			if from == "BGFX_INVALID_HANDLE" then
				val = val:gsub(from, to .. "!" .. type)
			else
				val = val:gsub(from, to)
			end
		end
	end
	if val:find("INT32_MAX") then
		val = val:gsub("INT32_MAX", "int.max")
	end
	val = convArray(val)
	
	return val
end

local function convStructType(arg)
	return convType(arg)
end

local function convName(name)
	name = abbrevsToUpper(name)
	if contains(dKeywords, name) then
		return name .. "_"
	end
	return name
end

local function convStructMember(member)
	return convStructType(member) .. " " .. convName(member.name)
end

local allStructs = {}

local function genVersion()
	return "enum uint apiVersion = " .. (idl._version or 0) .. ";"
end

local function genStructMemberFn(func) --NOTE: this does not work on nested structs
	if func.class ~= nil and func.conly == nil and func.cppinline == nil then
		local st = allStructs[func.class]
		local attribs = ""
		if func.comments ~= nil then
			if #st.fns > 0 then
				table.insert(st.fns, "")
			end
			table.insert(st.fns, "/**")
			for _, line in ipairs(func.comments) do
				local line = line:gsub("@remarks", "Remarks:")
				line = line:gsub("@remark", "Remarks:")
				line = line:gsub("@(%l)(%l+)", function(a, b) return a:upper() .. b .. ":" end)
				table.insert(st.fns, line)
			end
			
			local hasParamsComments = false
			for _, arg in ipairs(func.args) do
				if arg.comment ~= nil then
					hasParamsComments = true
					break
				end
			end
			
			if hasParamsComments then
				table.insert(st.fns, "Params:")
			end
			
			for _, arg in ipairs(func.args) do
				if arg.comment ~= nil then
					table.insert(st.fns, "\t" .. toIntlEnUncond(convName(arg.name:sub(2))) .. " = " .. arg.comment[1])
					for i, comment in ipairs(arg.comment) do
						if i > 1 then
							table.insert(st.fns, comment)
						end
					end
				end
			end
			
			table.insert(st.fns, "*/")
		end
		
		local args = {}
		for _, arg in ipairs(func.args) do
			local def = ""
			if arg.default ~= nil then
				def = string.format("=%s", convVal(arg.default, convFnArgType(arg)))
			end
			if arg.fulltype == "..." then
				table.insert(args, "..." .. def)
			else
				table.insert(args, convFnArgType(arg) .. " " .. toIntlEnUncond(convName(arg.name:sub(2))) .. def)
			end
		end
		
		if func.const ~= nil then
			attribs = "const"
		end
		if attribs ~= "" then
			attribs = ", memAttr: q{" .. attribs .. "}"
		end
		
		table.insert(st.fns, string.format("{q{%s}, q{%s}, q{%s}, ext: `C++`%s},", convType(func.ret), func.name, table.concat(args, ", "), attribs))
	end
end

local converter = {}
local yield = coroutine.yield
local gen = {}

gen.implFile = [[
/+
+ ┌==============================┐
+ │ AUTO GENERATED! DO NOT EDIT! │
+ └==============================┘
+/
///Do NOT import this module! Use the symbols with the same names in `bgfx/package.d` instead.
module bgfx.impl;

import bindbc.bgfx.config;
import bgfx;

package:

mixin(joinFnBinds((){
	FnBind[] ret = [
		{q{const(Memory)*}, q{alloc}, q{uint size}, ext: `C++, "bgfx"`},
		{q{const(Memory)*}, q{copy}, q{const(void)* data, uint size}, ext: `C++, "bgfx"`},
	];
	return ret;
}()));

]]

function gen.gen()
	local indent = ""
	local idx = 1;
	local r = template:gsub("$([a-zA-Z_]+)", function(what)
		local tmp = {}
		
		local ind_end = template:find("$"..what, idx, true)
		local ind_start = ind_end
		for j = 1, ind_end-1 do
			local i = 1+ind_end-1-j
			local c = string.sub(template, i, i)
			if c ~= ' ' and c ~= '\t' then
				ind_start = i
				break
			end
		end
		
		indent = string.sub(template, ind_start+1, ind_end-1)
		
		if what == "version" then
			return genVersion()
		elseif what == "structs" then
			for _, fn in ipairs(idl.funcs) do
				genStructMemberFn(fn)
			end
			for _, object in ipairs(idl["types"]) do
				if object.struct ~= nil and object.namespace == nil then
					local co = coroutine.create(converter[what])
					local any
					while true do
						local ok, v = coroutine.resume(co, allStructs[object.name], object.name, object.name, true, indent:len())
						assert(ok, debug.traceback(co, v))
						if not v then
							break
						end
						table.insert(tmp, v)
						any = true
					end
					if any and tmp[#tmp] ~= "" then
						table.insert(tmp, "")
					end
				end
			end
		elseif what == "membersWithFns" then
			table.insert(tmp, "\"" .. membersWithFns .. "\"")
		else
			for _, object in ipairs(idl[what]) do
				local co = coroutine.create(converter[what])
				local any
				while true do
					local ok, v = coroutine.resume(co, object)
					assert(ok, debug.traceback(co, v))
					if not v then
						break
					end
					table.insert(tmp, v)
					any = true
				end
				if any and tmp[#tmp] ~= "" then
					table.insert(tmp, "")
				end
			end
		end
		return table.concat(tmp, "\n" .. indent)
	end)
	return r
end

function converter.structs(st, name, fullyQualifiedName, topLvl)
	for _, line in ipairs(st.comments) do
		yield(line)
	end
	
	if topLvl then
		yield("extern(C++, \"bgfx\") struct " .. name .. "{")
	else
		yield("extern(C++) struct " .. name .. "{")
	end
	
	local subN = 0
	for _, subStruct in ipairs(st.subs) do
		subN = subN + 1
		local co = coroutine.create(converter.structs)
		while true do
			local ok, v = coroutine.resume(co, subStruct, subStruct.name, name .. "." .. subStruct.name, false)
			assert(ok, debug.traceback(co, v))
			if not v then
				break
			end
			yield("\t" .. v)
		end
	end
	if subN > 0 then
		yield("\t")
	end
	
	for _, line in ipairs(st.fields) do
		yield(line)
	end
	
	if #st.fns > 0 then
		membersWithFns = membersWithFns .. fullyQualifiedName .. ", "
		yield("\textern(D) mixin(joinFnBinds((){")
		yield("\t\tFnBind[] ret = [")
		for _, line in ipairs(st.fns) do
			yield("\t\t\t" .. line)
		end
		yield("\t\t];")
		yield("\t\treturn ret;")
		yield("\t}()));")
	end

	yield("}")
end

function converter.types(typ)
	if typ.comments ~= nil and not typ.struct then
		if #typ.comments == 1 then
			yield("///" .. typ.comments[1])
		else
			yield("/**")
			for _, comment in ipairs(typ.comments) do
				yield(comment)
			end
			yield("*/")
		end
	end
	
	if typ.handle then ---hnadle
		yield("extern(C++, \"bgfx\") struct " .. typ.name .. "{")
		yield("\tushort idx;")
		yield("}")
		--yield(typ.name .. " invalidHandle(){ return " .. typ.name .. "(ushort.max); }")
		
	-- For some reason, this has never worked, so I'm commenting it out just in case it does start working suddenly. :P
	--[[
	elseif typ.funcptr then
		local args = {}
		for _, arg in ipairs(typ.args) do
			if arg.fulltype == "..." then
				table.insert(args, "..." .. def)
			else
				table.insert(args, convFnArgType(arg) .. " " .. convName(arg.name:sub(2)) .. def)
			end
		end
		
		yield(string.format("alias %s = extern(C++) %s function(%s);", typ.name, convType(typ.ret), table.concat(args, ", ")))
	--]]
	elseif typ.enum then
		local typeName = abbrevsToUpper(typ.name:gsub("::Enum", ""))
		local otherName = string.format("bgfx.impl.%s.Enum", typ.name:gsub("::Enum", ""))
		
		yield("enum " .. typeName .. ": " .. otherName .. "{")
		table.insert(enumTypes, typeName)
		
		local vals = ""
		for idx, enum in ipairs(typ.enum) do
			local comments = ""
			if enum.comment ~= nil then
				if #enum.comment == 1 then
					comments = " ///" .. enum.comment[1];
				else
					yield("\t/**")
					for _, comment in ipairs(enum.comment) do
						yield("\t" .. comment)
					end
					yield("\t*/")
				end
			end
			local name = convName(toCamelCase(enum.name))
			yield("\t" .. name .. " = " .. otherName .. "." .. name .. ",")
			vals = vals .. name .. ","
			
			local intlName = toIntlEn(enum.name)
			if intlName ~= nil then
				yield("\t" .. convName(toCamelCase(intlName)) .. " = " .. otherName .. "." .. name .. ",")
			end
		end
		
		gen.implFile = gen.implFile .. string.format([[
extern(C++, "bgfx") package final abstract class %s{
	enum Enum{
		%scount
	}
}
]], typeName, vals)
		
		yield("\t" .. "count = " .. otherName .. ".count,")
		yield("}")
		
	elseif typ.bits ~= nil then
		local typeName = convName(typ.name)
		if typeName == "Caps" then
			typeName = "CapFlags"
		end
		
		local enumType = "uint"
		if typ.bits == 64 then
			enumType = "ulong"
		elseif typ.bits == 32 then
			enumType = "uint"
		elseif typ.bits == 16 then
			enumType = "ushort"
		elseif typ.bits == 8 then
			enumType = "ubyte"
		end
		
		local maxLen = 0
		if typ.shift then
			maxLen = string.len("shift")
		elseif typ.range then
			maxLen = string.len("mask")
		end
		for _, flag in ipairs(typ.flag) do
			maxLen = math.max(maxLen, flag.name:len())
		end
		
		yield("alias " .. typeName .. "_ = " .. enumType .. ";")
		yield("enum " .. typeName .. ": " .. typeName .. "_{")
		
		local function getValOr(name)
			local t = typeName
			if typeName == "State" then
				if hasPrefix(name, "Write") then
					t = t .. name:sub(1, 5)
					name = name:sub(6)
				elseif hasPrefix(name, "DepthTest") then
					t = t .. name:sub(1, 9)
					name = name:sub(10)
				elseif hasPrefix(name, "Cull") then
					t = t .. name:sub(1, 4)
					name = name:sub(5)
				end
			elseif typeName == "Sampler" then
				if hasPrefix(name, "Min") then
					t = t .. name:sub(1, 3)
					name = name:sub(4)
				elseif hasPrefix(name, "Mag") then
					t = t .. name:sub(1, 3)
					name = name:sub(4)
				elseif hasPrefix(name, "Mip") then
					t = t .. name:sub(1, 3)
					name = name:sub(4)
				elseif hasPrefix(name, "U") then
					t = t .. name:sub(1, 1)
					name = name:sub(2)
				elseif hasPrefix(name, "V") then
					t = t .. name:sub(1, 1)
					name = name:sub(2)
				elseif hasPrefix(name, "W") then
					t = t .. name:sub(1, 1)
					name = name:sub(2)
				elseif hasPrefix(name, "Compare") then
					t = t .. name:sub(1, 7)
					name = name:sub(8)
				end
			end
			return abbrevsToUpper(t) .. "." .. convName(toCamelCase(name))
		end
		
		for idx, flag in ipairs(typ.flag) do
			local value = flag.value
			if value ~= nil then
				value = hexStr(value, typ.bits)
			else
				for _, name in ipairs(flag) do
					if value ~= nil then
						value = value .. " | " .. getValOr(name)
					else
						value = getValOr(name)
					end
				end
			end
			
			local comments = ""
			if flag.comment ~= nil then
				if #flag.comment == 1 then
					comments = " ///" .. flag.comment[1]
				else
					yield("\t/**")
					for _, comment in ipairs(flag.comment) do
						yield("\t" .. comment)
					end
					yield("\t*/")
				end
			end
			
			local name = convName(toCamelCase(flag.name))
			yield("\t" .. name .. string.rep(" ", maxLen+2 - name:len()) .. "= " .. value .. "," .. comments)
			
			local intlName = toIntlEn(name)
			if intlName ~= nil then
				intlName = intlName
				yield("\t" .. intlName .. string.rep(" ", maxLen+2 - intlName:len()) .. "= " .. name .. ",")
			end
		end
		
		if typ.shift then
			local name = convName("shift")
			local value = typ.shift
			local comments = ""
			if typ.desc then
				comments = string.format(" ///%s bit shift", typ.desc)
			end
			yield("\t" .. name .. string.rep(" ", maxLen+2 - name:len()) .. "= " .. value .. "," .. comments)
		end
		if typ.range then
			local name = convName("mask")
			local value = hexStr(typ.mask, typ.bits)
			local comments = ""
			if typ.desc then
				comments = string.format(" ///%s bit mask", typ.desc)
			end
			yield("\t" .. name .. string.rep(" ", maxLen+2 - name:len()) .. "= " .. value .. "," .. comments)
		end
		yield("}")
		
		local intlName = toIntlEn(typeName)
		if intlName ~= nil then
			yield("alias " .. intlName .. " = " .. typeName .. ";")
		end
		
		if typ.helper then
			yield(string.format(
				"%s_ to%s(%s v) nothrow @nogc pure @safe{ return (v << %s) & %s; }",
				typeName,
				typeName,
				enumType,
				(typeName .. ".shift"),
				(typeName .. ".mask")))
			if intlName ~= nil then
				yield("alias to" .. intlName .. " = to" .. typeName .. ";")
			end
		end
	elseif typ.struct ~= nil then
		local st = {name = typ.name, comments = {}, fields = {}, fns = {}, subs = {}}
		
		if typ.comments ~= nil then
			if #typ.comments == 1 then
				table.insert(st.comments, "///" .. typ.comments[1])
			else
				table.insert(st.comments, "/**")
				for _, comment in ipairs(typ.comments) do
					table.insert(st.comments, comment)
				end
				table.insert(st.comments, "*/")
			end
		end
		
		for _, member in ipairs(typ.struct) do
			local comments = ""
			if member.comment ~= nil then
				if #member.comment == 1 then
					comments = " ///" .. member.comment[1]
				else
					if #st.fields > 0 then
						table.insert(st.fields, "\t")
					end
					table.insert(st.fields, "\t/**")
					for _, comment in ipairs(member.comment) do
						table.insert(st.fields, "\t" .. comment)
					end
					table.insert(st.fields, "\t*/")
				end
			end
			table.insert(st.fields, "\t" .. convStructMember(member) .. ";" .. comments)
		end
		
		if typ.ctor ~= nil and typ.name ~= "PlatformData" then
			table.insert(st.fns, "{q{void}, q{this}, q{}, ext: `C++`},")
		end
		
		if typ.namespace ~= nil then --if this is a sub-struct
			if allStructs[typ.namespace] ~= nil then
				table.insert(allStructs[typ.namespace].subs, st)
			else
				allStructs[typ.namespace] = {subs = {st}}
			end
		else --otherwise it's top-level
			if allStructs[typ.name] ~= nil then
				st.subs = allStructs[typ.name].subs
			end
			allStructs[typ.name] = st
		end
	end
end

function converter.funcs(func)
	if func.class == nil and func.conly == nil and func.cppinline == nil then
		local extern = "C++, \"bgfx\""
		local attribs = ""
		if (func.cfunc ~= nil and func.name ~= "init") or func.name == "alloc" or func.name == "copy" then --what the is "cfunc" even meant to mean?
			return
		end
		if func.comments ~= nil then
			yield("/**")
			for _, line in ipairs(func.comments) do
				local line = line:gsub("@remarks", "Remarks:")
				line = line:gsub("@remark", "Remarks:")
				line = line:gsub("@(%l)(%l+)", function(a, b) return a:upper() .. b .. ":" end)
				yield("* " .. line)
			end
			
			local hasParamsComments = false
			for _, arg in ipairs(func.args) do
				if arg.comment ~= nil then
					hasParamsComments = true
					break
				end
			end
			
			if hasParamsComments then
				yield("Params:")
			end
			
			for _, arg in ipairs(func.args) do
				if arg.comment ~= nil then
					yield("\t" .. toIntlEnUncond(convName(arg.name:sub(2))) .. " = " .. arg.comment[1])
					for i, comment in ipairs(arg.comment) do
						if i > 1 then
							yield(comment)
						end
					end
				end
			end
			
			yield("*/")
		end
		
		local args = {}
		for _, arg in ipairs(func.args) do
			local def = ""
			if arg.default ~= nil then
				def = string.format("=%s", convVal(arg.default, convFnArgType(arg)))
			end
			if arg.fulltype == "..." then
				table.insert(args, "..." .. def)
			else
				table.insert(args, convFnArgType(arg) .. " " .. toIntlEnUncond(convName(arg.name:sub(2))) .. def)
			end
		end
		
		if attribs ~= "" then
			attribs = ", memAttr: q{" .. attribs .. "}"
		end
		
		yield(string.format("{q{%s}, q{%s}, q{%s}, ext: `%s`%s},", convType(func.ret), func.name, table.concat(args, ", "), extern, attribs))
	end
end

-- printtable("idl types", idl.types)
-- printtable("idl funcs", idl.funcs)

function gen.write(codes, outputfile)
	local out = assert(io.open(outputfile, "wb"))
	out:write(codes)
	out:close()
	print("Generating: " .. outputfile)
end

if (...) == nil then
	-- run `lua bindings-d.lua` in command line
	print(gen.gen(dTemplate))
end

return gen
