
local common = require "CommonStyle"
local util = require "util"

local my_style = {}
my_style.header = {}
my_style.source = {}

----------------------------------------------------
-- Global styling functions.
function my_style.WriteLargeHeading(hFile, headingName)
	hFile:write(string.rep("/", 6 + #headingName), "\n")
	hFile:write("// ", headingName, "\n")
	hFile:write(string.rep("/", 6 + #headingName), "\n")
end

function my_style.WriteSmallHeading(hFile, headingName)
	hFile:write("// ", headingName, "\n")
end

------------------------------------------------------
-- Header styling functions

function my_style.header.GetFilename(basename, options)
	return basename .. ".hpp"
end

	local function GenIncludeGuardName(hFile, spec, options)
		local str = "POINTER_CPP_GENERATED_HEADER" ..
			spec.GetIncludeGuardString() .. "_HPP"

		if(#options.prefix > 0) then
			return options.prefix:upper() .. "_" .. str
		end
		
		return str
	end

function my_style.header.WriteBlockBeginIncludeGuard(hFile, spec, options)
	local inclGuard = GenIncludeGuardName(hFile, spec, options)
	
	hFile:fmt("#ifndef %s\n", inclGuard)
	hFile:fmt("#define %s\n", inclGuard)
end

function my_style.header.WriteBlockEndIncludeGuard(hFile, spec, options)
	hFile:fmt("#endif //%s\n", GenIncludeGuardName(hFile, spec, options))
end

function my_style.header.WriteInit(hFile, spec, options)
	hFile:rawwrite(spec.GetHeaderInit())
end

function my_style.header.WriteStdTypedefs(hFile, specData, spec, options)
	local defArray = common.GetStdTypedefs()
	hFile:write("#ifndef GL_LOAD_GEN_BASIC_OPENGL_TYPEDEFS\n")
	hFile:write("#define GL_LOAD_GEN_BASIC_OPENGL_TYPEDEFS\n")
	hFile:write("\n")
	hFile:inc()
	for _, def in ipairs(defArray) do
		hFile:write(def)
	end
	hFile:dec()
	hFile:write("\n")
	hFile:write("#endif /*GL_LOAD_GEN_BASIC_OPENGL_TYPEDEFS*/\n")
end

function my_style.header.WriteSpecTypedefs(hFile, specData, spec, options)
	hFile:push()
	common.WritePassthruData(hFile, specData.funcData.passthru)
	hFile:pop()
end

	local function StartNamespace(hFile, namespaceName)
		hFile:fmt("namespace %s\n", namespaceName or "")
		hFile:write("{\n")
		hFile:inc()
	end

	local function EndNamespace(hFile, namespaceName)
		hFile:dec()
		hFile:fmt("} //namespace %s\n", namespaceName or "")
	end

function my_style.header.WriteBlockBeginDecl(hFile, spec, options)
	if(#options.prefix > 0) then
		StartNamespace(hFile, options.prefix)
	end
	StartNamespace(hFile, spec.FuncNamePrefix())
end

function my_style.header.WriteBlockEndDecl(hFile, spec, options)
	EndNamespace(hFile, spec.FuncNamePrefix())
	if(#options.prefix > 0) then
		EndNamespace(hFile, options.prefix)
	end
end

	local extBlockNamespace = "exts"
	local extVariableTypeDefinition = [[
class LoadTest
{
private:
	//Safe bool idiom. Joy!
	typedef void (LoadTest::*bool_type)() const;
	void big_long_name_that_really_doesnt_matter() const {}
	
public:
	operator bool_type() const
	{
		return m_isLoaded ? &LoadTest::big_long_name_that_really_doesnt_matter : 0;
	}
	
	int GetNumMissing() const {return m_numMissing;}
	
	LoadTest() : m_isLoaded(false), m_numMissing(0) {}
	LoadTest(bool isLoaded, int numMissing) : m_isLoaded(isLoaded), m_numMissing(numMissing) {}
private:
	bool m_isLoaded;
	int m_numMissing;
};
]]

function my_style.header.WriteBlockBeginExtVarDecl(hFile, spec, options)
	StartNamespace(hFile, extBlockNamespace)
	hFile:writeblock(extVariableTypeDefinition)
	hFile:write("\n")
end

function my_style.header.WriteBlockEndExtVarDecl(hFile, spec, options)
	EndNamespace(hFile, extBlockNamespace)
end

	local function GenExtensionVarName(extName, spec, options)
		return "var_" .. extName;
	end

function my_style.header.WriteExtVariableDecl(hFile, extName,
	specData, spec, options)
	hFile:fmt("extern LoadTest %s;\n",
		GenExtensionVarName(extName, spec, options));
end

function my_style.header.WriteBlockBeginEnumDecl(hFile, spec, options)
	hFile:write("enum\n")
	hFile:write("{\n")
	hFile:inc()
end

function my_style.header.WriteBlockEndEnumDecl(hFile, spec, options)
	hFile:dec()
	hFile:write("};\n")
end

	local function GenEnumName(enum)
		return common.GetCppEnumName(enum)
	end

function my_style.header.WriteEnumDecl(hFile, enum, enumTable, spec, options,
	enumSeen)
	if(enumSeen[enum.name]) then
		hFile:fmt("//%s taken from ext: %s\n", enum.name, enumSeen[enum.name])
	else
	
		local enumName = GenEnumName(enum)
		local lenEnum = #enumName
		local numIndent = 33
		
		local numSpaces = numIndent - lenEnum
		if(numSpaces < 1) then
			numSpaces = 1
		end

		hFile:fmt("%s%s= %s,\n",
			enumName,
			string.rep(" ", numSpaces),
			common.ResolveEnumValue(enum, enumTable))
	end
end

function my_style.header.WriteBlockBeginFuncDecl(hFile, spec, options)
	--Block containing all spec function declarations.
end

function my_style.header.WriteBlockEndFuncDecl(hFile, spec, options)
	--Block containing all spec function declarations.
end

function my_style.header.WriteBlockBeginExtFuncDecl(hFile, extName, spec, options)
	--Block containing all spec function declarations for a particular extension.
	--Useful for include-guards around extension function pointers.
end

function my_style.header.WriteBlockEndExtFuncDecl(hFile, extName, spec, options)
	--Block containing all spec function declarations for a particular extension.
end

	local function GenFuncPtrName(func, spec, options)
		return func.name
	end

	local function GenFuncPtrTypedefName(func, spec, options)
		return "PFN" .. GenFuncPtrName(func, spec, options):upper()
	end

	local function WriteFuncPtrTypedefStmt(hFile, func, spec, options)
		hFile:fmt("typedef %s (%s *%s)(%s);\n",
			common.GetFuncReturnType(func),
			spec.GetCodegenPtrType(),
			GenFuncPtrTypedefName(func, spec, options),
			common.GetFuncParamList(func))
	end

	local function GenFuncPtrDefDirect(func, spec, options)
		return string.format("%s (%s *%s)(%s)",
			common.GetFuncReturnType(func),
			spec.GetCodegenPtrType(),
			GenFuncPtrName(func, spec, options),
			common.GetFuncParamList(func, true))
	end

	local function GenFuncPtrDefTypedef(func, spec, options)
		return string.format("%s %s",
			GenFuncPtrTypedefName(func, spec, options),
			GenFuncPtrName(func, spec, options))
	end

function my_style.header.WriteFuncDecl(hFile, func, spec, options)
	hFile:write("extern ",
		GenFuncPtrDefDirect(func, spec, options),
		";\n")
end

function my_style.header.WriteBlockBeginSysDecl(hFile, spec, options)
	StartNamespace(hFile, "sys")
end

function my_style.header.WriteBlockEndSysDecl(hFile, spec, options)
	EndNamespace(hFile, "sys")
end

function my_style.header.WriteUtilityDecls(hFile, spec, options)
	--Write declarations for public utility stuff. Enums for return values, etc.
end

function my_style.header.WriteMainLoaderFuncDecl(hFile, spec, options)
	hFile:fmt("%s::LoadTest LoadFunctions(%s);\n", extBlockNamespace, spec.GetLoaderParams())
end

function my_style.header.WriteVersioningFuncDecls(hFile, spec, options)
	hFile:writeblock([[
int GetMinorVersion();
int GetMajorVersion();
bool IsVersionGEQ(int majorVersion, int minorVersion);
]])
end

--------------------------------------------------
-- Source code styling functions.
function my_style.source.GetFilename(basename, options)
	return basename .. ".cpp"
end

function my_style.source.WriteIncludes(hFile, basename, spec, options)
	hFile:writeblock([[
#include <algorithm>
#include <vector>
#include <string.h>
#include <stddef.h>
]])

	local base = util.ParsePath(my_style.header.GetFilename(basename, options))
	hFile:fmt('#include "%s"\n', base)
end

function my_style.source.WriteLoaderData(hFile, spec, options)
	hFile:writeblock(spec.GetLoaderFunc())
end

function my_style.source.WriteBlockBeginDef(hFile, spec, options)
	if(#options.prefix > 0) then
		StartNamespace(hFile, options.prefix)
	end
	StartNamespace(hFile, spec.FuncNamePrefix())
end

function my_style.source.WriteBlockEndDef(hFile, spec, options)
	EndNamespace(hFile, spec.FuncNamePrefix())
	if(#options.prefix > 0) then
		EndNamespace(hFile, options.prefix)
	end
end

function my_style.source.WriteBlockBeginExtVarDef(hFile, spec, options)
	StartNamespace(hFile, extBlockNamespace)
end

function my_style.source.WriteBlockEndExtVarDef(hFile, spec, options)
	EndNamespace(hFile, extBlockNamespace)
end

function my_style.source.WriteExtVariableDef(hFile, extName,
	specData, spec, options)
	hFile:fmt("LoadTest %s;\n",
		GenExtensionVarName(extName, spec, options));
end

function my_style.source.WriteBlockBeginExtFuncDef(hFile, extName, spec, options)
	--Block containing the extension function definitions and load function
	--for the functions in the extension `extName`.
end

function my_style.source.WriteBlockEndExtFuncDef(hFile, extName, spec, options)
	--Block containing the extension function definitions and load function
	--for the functions in the extension `extName`.
end

function my_style.source.WriteFuncDef(hFile, func, spec, options, funcSeen)
	if(not funcSeen[func.name]) then
		WriteFuncPtrTypedefStmt(hFile, func, spec, options)
		hFile:write(GenFuncPtrDefTypedef(func, spec, options),
			" = 0;\n")
	end
end

	local function GenExtLoaderFuncName(extName, spec, options)
		return "Load_" .. extName;
	end

function my_style.source.WriteBlockBeginExtLoader(hFile, extName, spec, options)
	hFile:fmt("static int %s()\n", GenExtLoaderFuncName(extName, spec, options))
	hFile:write("{\n")
	hFile:inc()
	hFile:write("int numFailed = 0;\n")
end

function my_style.source.WriteBlockEndExtLoader(hFile, extName, spec, options)
	hFile:write "return numFailed;\n"
	hFile:dec()
	hFile:write("}\n")
end

function my_style.source.WriteExtFuncLoader(hFile, func, spec, options)
	hFile:fmt('%s = reinterpret_cast<%s>(%s("%s%s"));\n',
		GenFuncPtrName(func, spec, options),
		GenFuncPtrTypedefName(func, spec, options),
		common.GetProcAddressName(spec),
		spec.FuncNamePrefix(), func.name)
	hFile:fmt('if(!%s) ++numFailed;\n', GenFuncPtrName(func, spec, options))
end

function my_style.source.WriteBlockBeginCoreFuncDef(hFile, version, spec, options)
	--Block containing the core functions for `version`.
	--The block also contains the loading function for this version.
end

function my_style.source.WriteBlockEndCoreFuncDef(hFile, version, spec, options)
	--Block containing the core functions for `version`.
end

	local function GenCoreLoaderFuncName(version, spec, options)
		return "LoadCoreFunctions"
	end

function my_style.source.WriteBlockBeginCoreLoader(hFile, version, spec, options)
	hFile:fmt("static int %s()\n", GenCoreLoaderFuncName(version, spec, options))
	hFile:write("{\n")
	hFile:inc()
	hFile:write("int numFailed = 0;\n")

end

function my_style.source.WriteBlockEndCoreLoader(hFile, version, spec, options)
	hFile:write "return numFailed;\n"
	hFile:dec()
	hFile:write("}\n")
end

function my_style.source.WriteCoreFuncLoader(hFile, func, spec, options)
	hFile:fmt('%s = reinterpret_cast<%s>(%s("%s%s"));\n',
		GenFuncPtrName(func, spec, options),
		GenFuncPtrTypedefName(func, spec, options),
		common.GetProcAddressName(spec),
		spec.FuncNamePrefix(), func.name)
		
	--Special hack for DSA_EXT functions in core functions.
	--They do not count against the loaded count.
	if(func.name:match("EXT$")) then
		hFile:write("//An EXT_direct_state_access-based function. Don't count it if it fails to load.\n")
	else
		hFile:fmt('if(!%s) ++numFailed;\n', GenFuncPtrName(func, spec, options))
	end
end

function my_style.source.WriteExtStringFuncDef(hFile, specData, spec, options, funcSeen)
	if(funcSeen[spec.GetExtStringFuncName()]) then
		return
	end
	
	--Check to see if its something we have to load.
	local function FindFuncName(funcName)
		for _, func in ipairs(specData.funcData.functions) do
			if(func.name == funcName) then
				return func
			end
		end
		
		return nil
	end
	
	local extStringFunc = FindFuncName(spec.GetExtStringFuncName())

	if(extStringFunc) then
		hFile:write("\n")
		WriteFuncPtrTypedefStmt(hFile, extStringFunc, spec, options)
		hFile:write("static ", GenFuncPtrDefTypedef(extStringFunc, spec, options),
			" = 0;\n")
		hFile:write("\n")
	end
end

function my_style.source.WriteBlockBeginSysDef(hFile, spec, options)
	StartNamespace(hFile, "sys")
end

function my_style.source.WriteBlockEndSysDef(hFile, spec, options)
	EndNamespace(hFile, "sys")
end

function my_style.source.WriteUtilityDefs(hFile, specData, spec, options)
	--Write our mapping table definitions.
	StartNamespace(hFile)
	hFile:writeblock[[
typedef int (*PFN_LOADEXTENSION)();
struct MapEntry
{
	MapEntry(const char *_extName, exts::LoadTest *_extVariable)
		: extName(_extName)
		, extVariable(_extVariable)
		, loaderFunc(0)
		{}
		
	MapEntry(const char *_extName, exts::LoadTest *_extVariable, PFN_LOADEXTENSION _loaderFunc)
		: extName(_extName)
		, extVariable(_extVariable)
		, loaderFunc(_loaderFunc)
		{}
	
	const char *extName;
	exts::LoadTest *extVariable;
	PFN_LOADEXTENSION loaderFunc;
};

struct MapCompare
{
	MapCompare(const char *test_) : test(test_) {}
	bool operator()(const MapEntry &other) { return strcmp(test, other.extName) == 0; }
	const char *test;
};

]]

	--Write the table initialization function.
	hFile:write "void InitializeMappingTable(std::vector<MapEntry> &table)\n"
	hFile:write "{\n"
	hFile:inc()
	hFile:fmt("table.reserve(%i);\n", #options.extensions)
	for _, extName in ipairs(options.extensions) do
		if(#specData.extdefs[extName].funcs > 0) then
			hFile:fmt('table.push_back(MapEntry("%s", &exts::%s, %s));\n',
				spec.ExtNamePrefix() .. extName,
				GenExtensionVarName(extName, spec, options),
				GenExtLoaderFuncName(extName, spec, options))
		else
			hFile:fmt('table.push_back(MapEntry("%s", &exts::%s));\n',
				spec.ExtNamePrefix() .. extName,
				GenExtensionVarName(extName, spec, options))
		end
	end
	hFile:dec()
	hFile:write "}\n"
	hFile:write "\n"
	
	--Write the function to clear the extension variables.
	hFile:fmt("void ClearExtensionVars()\n")
	hFile:write("{\n")
	hFile:inc()
	for _, extName in ipairs(options.extensions) do
		hFile:fmt('exts::%s = exts::LoadTest();\n',
			GenExtensionVarName(extName, spec, options))
	end
	hFile:dec()
	hFile:write("}\n")
	hFile:write "\n"
	
	--Write a function that loads an extension by name. It is called when
	--processing, so it should also set the extension variable based on the load.
	hFile:writeblock([[
void LoadExtByName(std::vector<MapEntry> &table, const char *extensionName)
{
	std::vector<MapEntry>::iterator entry = std::find_if(table.begin(), table.end(), MapCompare(extensionName));
	
	if(entry != table.end())
	{
		if(entry->loaderFunc)
			(*entry->extVariable) = exts::LoadTest(true, entry->loaderFunc());
		else
			(*entry->extVariable) = exts::LoadTest(true, 0);
	}
}
]])
	EndNamespace(hFile)
	hFile:write "\n"
end

	local function GenQualifier(spec, options)
		local ret = ""
		if(#options.prefix > 0) then
			ret = options.prefix .. "::"
		end
		ret = ret .. spec.FuncNamePrefix() .. "::"
		return ret
	end

	local function GenQualifiedEnumName(enum, spec, options)
		return GenQualifier(spec, options) .. GenEnumName(enum, spec, options)
	end
	
	local function GenQualifiedFuncPtrName(func, spec, options)
		return GenQualifier(spec, options) .. GenFuncPtrName(func, spec, options)
	end
	
	local function WriteAncillaryFuncs(hFile, specData, spec, options)
		local indexed = spec.GetIndexedExtStringFunc(options);
		if(indexed) then
			for _, func in ipairs(specData.funcData.functions) do
				if(indexed[1] == func.name) then
					indexed[1] = func
				end
				if(indexed[3] == func.name) then
					indexed[3] = func
				end
			end
			for _, enum in ipairs(specData.enumerators) do
				if(indexed[2] == enum.name) then
					indexed[2] = enum
				end
				if(indexed[4] == enum.name) then
					indexed[4] = enum
				end
			end
		
			hFile:writeblock([[
static void ProcExtsFromExtList(std::vector<MapEntry> &table)
{
	GLint iLoop;
	GLint iNumExtensions = 0;
	]] .. GenQualifiedFuncPtrName(indexed[1], spec, options)
	.. [[(]] .. GenQualifiedEnumName(indexed[2], spec, options)
	.. [[, &iNumExtensions);

	for(iLoop = 0; iLoop < iNumExtensions; iLoop++)
	{
		const char *strExtensionName = (const char *)]] ..
		GenQualifiedFuncPtrName(indexed[3], spec, options) ..
		[[(]] .. GenQualifiedEnumName(indexed[4], spec, options) .. [[, iLoop);
		LoadExtByName(table, strExtensionName);
	}
}
]])
		else
			hFile:writeblock(common.GetProcessExtsFromStringFunc(
				"LoadExtByName(table, %s)", ", std::vector<MapEntry> &table"))
		end
		
		hFile:write "\n"

		return indexed
	end

	
	local function WriteInMainFuncLoader(hFile, func, spec, options)
		hFile:fmt('%s = reinterpret_cast<%s>(%s("%s%s"));\n',
			GenFuncPtrName(func, spec, options),
			GenFuncPtrTypedefName(func, spec, options),
			common.GetProcAddressName(spec),
			spec.FuncNamePrefix(), func.name)
		hFile:fmt('if(!%s) return exts::LoadTest();\n',
			GenFuncPtrName(func, spec, options))
	end

function my_style.source.WriteMainLoaderFunc(hFile, specData, spec, options)
	StartNamespace(hFile)
	local indexed = WriteAncillaryFuncs(hFile, specData, spec, options)
	EndNamespace(hFile)
	
	hFile:write "\n"
	
	hFile:fmt("exts::LoadTest LoadFunctions(%s)\n", spec.GetLoaderParams())
	hFile:write("{\n")
	hFile:inc()
	hFile:writeblock[[
ClearExtensionVars();
std::vector<MapEntry> table;
InitializeMappingTable(table);
]]
	hFile:write("\n")
	
	if(indexed) then
		WriteInMainFuncLoader(hFile, indexed[1], spec, options)
		WriteInMainFuncLoader(hFile, indexed[3], spec, options)
		hFile:write("\n")
		hFile:write("ProcExtsFromExtList(table);\n")
	else
		local extListName, needLoad = spec.GetExtStringFuncName()
		if(needLoad) then
			for _, func in ipairs(specData.funcData.functions) do
				if(extListName == func.name) then
					extListName = func
					break
				end
			end
			
			WriteInMainFuncLoader(hFile, extListName, spec, options)
			
			extListName = GenQualifiedFuncPtrName(extListName, spec, options);
		end

		local function EnumResolve(enumName)
			return GenQualifiedEnumName(specData.enumtable[enumName], spec, options)
		end
		
		hFile:write "\n"
		hFile:fmt("ProcExtsFromExtString((const char *)%s(%s), table);\n",
			extListName,
			spec.GetExtStringParamList(EnumResolve))
	end
	
	if(options.version) then
		hFile:write "\n"
		hFile:fmt("int numFailed = %s();\n",
			GenCoreLoaderFuncName(options.version, spec, options))
		
		hFile:write("return exts::LoadTest(true, numFailed);\n")
	else
		hFile:fmt("return exts::LoadTest(true, 0);\n")
	end


	hFile:dec()
	hFile:write("}\n")
end

function my_style.source.WriteVersioningFuncs(hFile, specData, spec, options)
	hFile:fmt("static int g_major_version = 0;\n")
	hFile:fmt("static int g_minor_version = 0;\n")
	hFile:write "\n"
	
	if(tonumber(options.version) >= 3.0) then
		hFile:writeblock([[
static void GetGLVersion()
{
	GetIntegerv(MAJOR_VERSION, &g_major_version);
	GetIntegerv(MINOR_VERSION, &g_minor_version);
}
]])
	else
		hFile:writeblock(common.GetParseVersionFromString())
		hFile:write "\n"
		
		hFile:writeblock([[
static void GetGLVersion()
{
	ParseVersionFromString(&g_major_version, &g_minor_version, GetString(VERSION));
}
]])
	end
	
	hFile:write "\n"
	hFile:writeblock([[
int GetMajorVersion()
{
	if(g_major_version == 0)
		GetGLVersion();
	return g_major_version;
}
]])
	hFile:write "\n"

	hFile:writeblock([[
int GetMinorVersion()
{
	if(g_major_version == 0) //Yes, check the major version to get the minor one.
		GetGLVersion();
	return g_minor_version;
}
]])
	hFile:write "\n"
	
	hFile:writeblock([[
bool IsVersionGEQ(int majorVersion, int minorVersion)
{
	if(g_major_version == 0)
		GetGLVersion();
		
	if(majorVersion > g_major_version) return true;
	if(majorVersion < g_major_version) return false;
	if(minorVersion >= g_minor_version) return true;
	return false;
}
]])

end


--------------------------------------------------
-- Style retrieval machinery

local function Create()
	return common.DeepCopyTable(my_style), common.GetStandardStructure()
end

return { Create = Create }
