import std.string, std.stdio, std.file, std.stream;

void main(string[] args)
{
	writefln(HEADER);
	chdir(args[1]);
	string[] functionNames;
	foreach (fileName; getCurrentDirFileNames())
	{
		scope file = new File(fileName, FileMode.In);
		writefln("");
		writefln("//" ~ file.readLine());
		writefln("//" ~ file.readLine());
		while (true)
		{
			auto line = cast(string)file.readLine();
			if (line is null) break;
			auto a = new Analyzer(line);
			if (a.isEnum) writefln(toGlenum(line));
			else if (a.isTypedef) writefln(toAlias(line));
			else if (a.isFunction)
			{
				writefln(toFunction(line));
				functionNames ~= getFunctionName(line);
			}
		}
	}
	writefln(toLoader(functionNames));
}

version (D_Version2) { const GPA = "void* wglGetProcAddress(const char* proc);"; }
else { const GPA = "void* wglGetProcAddress(char* proc);"; }

const HEADER =
"// このファイルはglew2glextで自動生成しました
module coneneko.glext;
import std.string, opengl;
extern(Windows):
private void loadGlextFunction(void** f, string name)
{
	if (!(*f)) *f = wglGetProcAddress(name.toStringz());
	if (!(*f)) throw new Error(name ~ \" load failed\");
}
" ~ GPA;

string[] getCurrentDirFileNames()
{
	string[] result;
	foreach (fileName; listdir("."))
	{
		if (isfile(fileName)) result ~= fileName;
	}
	return result;
}

class Analyzer
{
	unittest
	{
		assert((new Analyzer("GL_BUFFER_SIZE_ARB 0x8764")).isEnum);
		assert((new Analyzer("typedef ptrdiff_t GLsizeiptrARB")).isTypedef);
		assert((new Analyzer("void glBindBufferARB (GLenum target, GLuint buffer)")).isFunction);
	}
	
	const bool isEnum, isTypedef, isFunction;
	
	this(string line)
	{
		auto sl = split(line);
		if (sl.length == 2 && sl[1][0..2] == "0x") isEnum = true;
		else if (sl.length >= 3 && sl[0] == "typedef") isTypedef = true;
		else if (line.length != 0 && line[$ - 1] == ')') isFunction = true;
	}
}

unittest
{
	assert("const GLenum GL_BUFFER_SIZE_ARB = 0x8764;" == toGlenum("GL_BUFFER_SIZE_ARB 0x8764"));
}

string toGlenum(string a)
{
	auto sl = split(a);
	return format("const GLenum %s = %s;", sl[0], sl[1]);
}

unittest
{
	assert("alias ptrdiff_t GLsizeiptrARB;" == toAlias("typedef ptrdiff_t GLsizeiptrARB"));
	assert("alias uint GLhandleARB;" == toAlias("typedef unsigned int GLhandleARB"));
}

string toAlias(string a)
{
	return a.replace("typedef", "alias")
		.replace("unsigned int", "uint")
		.removechars("\t")
		~ ";";
}

unittest
{
	assert(
		"void function (GLenum target, GLuint buffer) glBindBufferARB;"
		== toFunction("void glBindBufferARB (GLenum target, GLuint buffer)")
	);
	assert(
		"GLvoid * function (GLenum target, GLenum access) glMapBufferARB;"
		== toFunction("GLvoid * glMapBufferARB (GLenum target, GLenum access)")
	);
	assert(
		"GLhandleARB function () glCreateProgramObjectARB;"
		== toFunction("\tGLhandleARB glCreateProgramObjectARB (void)")
	);
}

string toFunction(string a)
{
	auto f = getFunctionName(a);
	version (D_Version2) { auto b = a; }
	else { auto b = a.replace("const ", ""); }
	return b.stripl()
		.replace(f, "function")
		.replace("  ", " ")
		.replace("(void)", "()")
		~ " "
		~ f
		~ ";";
}

unittest
{
	assert(
		"glBindBufferARB"
		== getFunctionName("void glBindBufferARB (GLenum target, GLuint buffer)")
	);
	assert(
		"glMapBufferARB"
		== getFunctionName("GLvoid * glMapBufferARB (GLenum target, GLenum access)")
	);
}

string getFunctionName(string a)
{
	auto result = a.split(['('])[0]
		.split()[$ - 1];
	version (D_Version2) { return result.idup; }
	else { return result.dup; }
}

string toLoader(string[] functionNames)
{
	string[] result;
	result ~= "";
	result ~= format("void loadGlextFunctions() /// loader");
	result ~= "{";
	foreach (string name; functionNames)
	{
		result ~= format("\tloadGlextFunction(cast(void**)&%s, \"%s\");", name, name);
	}
	result ~= "}";
	return result.join("\n");
}
