#include "ogl.h"


BOOL LoadExtensionList(void){
	wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
	if(wglChoosePixelFormatARB == NULL){
		return FALSE;
	}
	wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");
	if(wglCreateContextAttribsARB == NULL){
		return FALSE;
	}
	wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");
	if(wglSwapIntervalEXT == NULL){
		return FALSE;
	}
	glAttachShader = (PFNGLATTACHSHADERPROC)wglGetProcAddress("glAttachShader");
	if(!glAttachShader){
		return FALSE;
	}

	glBindBuffer = (PFNGLBINDBUFFERPROC)wglGetProcAddress("glBindBuffer");
	if(!glBindBuffer){
		return FALSE;
	}

	glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)wglGetProcAddress("glBindVertexArray");
	if(!glBindVertexArray){
		return FALSE;
	}

	glBufferData = (PFNGLBUFFERDATAPROC)wglGetProcAddress("glBufferData");
	if(!glBufferData){
		return FALSE;
	}

	glCompileShader = (PFNGLCOMPILESHADERPROC)wglGetProcAddress("glCompileShader");
	if(!glCompileShader){
		return FALSE;
	}

	glCreateProgram = (PFNGLCREATEPROGRAMPROC)wglGetProcAddress("glCreateProgram");
	if(!glCreateProgram){
		return FALSE;
	}

	glCreateShader = (PFNGLCREATESHADERPROC)wglGetProcAddress("glCreateShader");
	if(!glCreateShader){
		return FALSE;
	}

	glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)wglGetProcAddress("glDeleteBuffers");
	if(!glDeleteBuffers){
		return FALSE;
	}

	glDeleteProgram = (PFNGLDELETEPROGRAMPROC)wglGetProcAddress("glDeleteProgram");
	if(!glDeleteProgram){
		return FALSE;
	}

	glDeleteShader = (PFNGLDELETESHADERPROC)wglGetProcAddress("glDeleteShader");
	if(!glDeleteShader){
		return FALSE;
	}

	glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)wglGetProcAddress("glDeleteVertexArrays");
	if(!glDeleteVertexArrays){
		return FALSE;
	}

	glDetachShader = (PFNGLDETACHSHADERPROC)wglGetProcAddress("glDetachShader");
	if(!glDetachShader){
		return FALSE;
	}

	glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)wglGetProcAddress("glEnableVertexAttribArray");
	if(!glEnableVertexAttribArray){
		return FALSE;
	}

	glGenBuffers = (PFNGLGENBUFFERSPROC)wglGetProcAddress("glGenBuffers");
	if(!glGenBuffers){
		return FALSE;
	}

	glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)wglGetProcAddress("glGenVertexArrays");
	if(!glGenVertexArrays){
		return FALSE;
	}

	glGetAttribLocation = (PFNGLGETATTRIBLOCATIONPROC)wglGetProcAddress("glGetAttribLocation");
	if(!glGetAttribLocation){
		return FALSE;
	}

	glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)wglGetProcAddress("glGetProgramInfoLog");
	if(!glGetProgramInfoLog){
		return FALSE;
	}

	glGetProgramiv = (PFNGLGETPROGRAMIVPROC)wglGetProcAddress("glGetProgramiv");
	if(!glGetProgramiv){
		return FALSE;
	}

	glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)wglGetProcAddress("glGetShaderInfoLog");
	if(!glGetShaderInfoLog){
		return FALSE;
	}

	glGetShaderiv = (PFNGLGETSHADERIVPROC)wglGetProcAddress("glGetShaderiv");
	if(!glGetShaderiv){
		return FALSE;
	}

	glLinkProgram = (PFNGLLINKPROGRAMPROC)wglGetProcAddress("glLinkProgram");
	if(!glLinkProgram){
		return FALSE;
	}

	glShaderSource = (PFNGLSHADERSOURCEPROC)wglGetProcAddress("glShaderSource");
	if(!glShaderSource){
		return FALSE;
	}

	glUseProgram = (PFNGLUSEPROGRAMPROC)wglGetProcAddress("glUseProgram");
	if(!glUseProgram){
		return FALSE;
	}

	glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)wglGetProcAddress("glVertexAttribPointer");
	if(!glVertexAttribPointer){
		return FALSE;
	}

	glBindAttribLocation = (PFNGLBINDATTRIBLOCATIONPROC)wglGetProcAddress("glBindAttribLocation");
	if(!glBindAttribLocation){
		return FALSE;
	}

	glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)wglGetProcAddress("glGetUniformLocation");
	if(!glGetUniformLocation){
		return FALSE;
	}

	glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC)wglGetProcAddress("glUniformMatrix4fv");
	if(!glUniformMatrix4fv){
		return FALSE;
	}

	glActiveTexture = (PFNGLACTIVETEXTUREPROC)wglGetProcAddress("glActiveTexture");
	if(!glActiveTexture){
		return FALSE;
	}

	glUniform1i = (PFNGLUNIFORM1IPROC)wglGetProcAddress("glUniform1i");
	if(!glUniform1i){
		return FALSE;
	}

	glGenerateMipmap = (PFNGLGENERATEMIPMAPPROC)wglGetProcAddress("glGenerateMipmap");
	if(!glGenerateMipmap){
		return FALSE;
	}

	glDisableVertexAttribArray = (PFNGLDISABLEVERTEXATTRIBARRAYPROC)wglGetProcAddress("glDisableVertexAttribArray");
	if(!glDisableVertexAttribArray){
		return FALSE;
	}

	glUniform1f = (PFNGLUNIFORM1FPROC)wglGetProcAddress("glUniform1f");
	if(!glUniform1f){
		return FALSE;
	}

	glUniform2f = (PFNGLUNIFORM2FPROC)wglGetProcAddress("glUniform2f");
	if(!glUniform2f){
		return FALSE;
	}

	glUniform3f = (PFNGLUNIFORM3FPROC)wglGetProcAddress("glUniform3f");
	if(!glUniform3f){
		return FALSE;
	}

	glUniform4f = (PFNGLUNIFORM4FPROC)wglGetProcAddress("glUniform4f");
	if(!glUniform4f){
		return FALSE;
	}

	glUniform1i = (PFNGLUNIFORM1IPROC)wglGetProcAddress("glUniform1i");
	if(!glUniform1i){
		return FALSE;
	}

	glUniform2i = (PFNGLUNIFORM2IPROC)wglGetProcAddress("glUniform2i");
	if(!glUniform2i){
		return FALSE;
	}

	glUniform3i = (PFNGLUNIFORM3IPROC)wglGetProcAddress("glUniform3i");
	if(!glUniform3i){
		return FALSE;
	}

	glUniform4i = (PFNGLUNIFORM4IPROC)wglGetProcAddress("glUniform4i");
	if(!glUniform4i){
		return FALSE;
	}

	glUniform1ui = (PFNGLUNIFORM1UIPROC)wglGetProcAddress("glUniform1ui");
	if(!glUniform1ui){
		return FALSE;
	}

	glUniform2ui = (PFNGLUNIFORM2UIPROC)wglGetProcAddress("glUniform2ui");
	if(!glUniform2ui){
		return FALSE;
	}

	glUniform3ui = (PFNGLUNIFORM3UIPROC)wglGetProcAddress("glUniform3ui");
	if(!glUniform3ui){
		return FALSE;
	}

	glUniform4ui = (PFNGLUNIFORM4UIPROC)wglGetProcAddress("glUniform4ui");
	if(!glUniform4ui){
		return FALSE;
	}

	glUniform1fv = (PFNGLUNIFORM1FVPROC)wglGetProcAddress("glUniform1fv");
	if(!glUniform1fv){
		return FALSE;
	}

	glUniform2fv = (PFNGLUNIFORM2FVPROC)wglGetProcAddress("glUniform2fv");
	if(!glUniform2fv){
		return FALSE;
	}

	glUniform3fv = (PFNGLUNIFORM3FVPROC)wglGetProcAddress("glUniform3fv");
	if(!glUniform3fv){
		return FALSE;
	}

	glUniform4fv = (PFNGLUNIFORM4FVPROC)wglGetProcAddress("glUniform4fv");
	if(!glUniform4fv){
		return FALSE;
	}

	glUniform1iv = (PFNGLUNIFORM1IVPROC)wglGetProcAddress("glUniform1iv");
	if(!glUniform1iv){
		return FALSE;
	}

	glUniform2iv = (PFNGLUNIFORM2IVPROC)wglGetProcAddress("glUniform2iv");
	if(!glUniform2iv){
		return FALSE;
	}

	glUniform3iv = (PFNGLUNIFORM3IVPROC)wglGetProcAddress("glUniform3iv");
	if(!glUniform3iv){
		return FALSE;
	}

	glUniform4iv = (PFNGLUNIFORM4IVPROC)wglGetProcAddress("glUniform4iv");
	if(!glUniform4iv){
		return FALSE;
	}

	glUniform1uiv = (PFNGLUNIFORM1UIVPROC)wglGetProcAddress("glUniform1uiv");
	if(!glUniform1uiv){
		return FALSE;
	}

	glUniform2uiv = (PFNGLUNIFORM2UIVPROC)wglGetProcAddress("glUniform2uiv");
	if(!glUniform2uiv){
		return FALSE;
	}

	glUniform3uiv = (PFNGLUNIFORM3UIVPROC)wglGetProcAddress("glUniform3uiv");
	if(!glUniform3uiv){
		return FALSE;
	}

	glUniform4uiv = (PFNGLUNIFORM4UIVPROC)wglGetProcAddress("glUniform4uiv");
	if(!glUniform4uiv){
		return FALSE;
	}

	glUniformMatrix2fv = (PFNGLUNIFORMMATRIX2FVPROC)wglGetProcAddress("glUniformMatrix2fv");
	if(!glUniformMatrix2fv){
		return FALSE;
	}

	glUniformMatrix3fv = (PFNGLUNIFORMMATRIX3FVPROC)wglGetProcAddress("glUniformMatrix3fv");
	if(!glUniformMatrix3fv){
		return FALSE;
	}

	glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC)wglGetProcAddress("glUniformMatrix4fv");
	if(!glUniformMatrix4fv){
		return FALSE;
	}

	glUniformMatrix2x3fv = (PFNGLUNIFORMMATRIX2X3FVPROC)wglGetProcAddress("glUniformMatrix2x3fv");
	if(!glUniformMatrix2x3fv){
		return FALSE;
	}

	glUniformMatrix3x2fv = (PFNGLUNIFORMMATRIX3X2FVPROC)wglGetProcAddress("glUniformMatrix3x2fv");
	if(!glUniformMatrix3x2fv){
		return FALSE;
	}

	glUniformMatrix2x4fv = (PFNGLUNIFORMMATRIX2X4FVPROC)wglGetProcAddress("glUniformMatrix2x4fv");
	if(!glUniformMatrix2x4fv){
		return FALSE;
	}

	glUniformMatrix4x2fv = (PFNGLUNIFORMMATRIX4X2FVPROC)wglGetProcAddress("glUniformMatrix4x2fv");
	if(!glUniformMatrix4x2fv){
		return FALSE;
	}

	glUniformMatrix3x4fv = (PFNGLUNIFORMMATRIX3X4FVPROC)wglGetProcAddress("glUniformMatrix3x4fv");
	if(!glUniformMatrix3x4fv){
		return FALSE;
	}

	glUniformMatrix4x3fv = (PFNGLUNIFORMMATRIX4X3FVPROC)wglGetProcAddress("glUniformMatrix4x3fv");
	if(!glUniformMatrix4x3fv){
		return FALSE;
	}

	return TRUE;
}

// TODO: error handling
int InitializeOpenGLExtensions(void){
	int error = 0;

	WNDCLASS dummyClass = {
		.style = CS_OWNDC,
		.lpfnWndProc = DefWindowProc,
		.hInstance = GetModuleHandle(0),
		.lpszClassName = "dummyClass",
	};
	if(RegisterClassA(&dummyClass) == 0){
		error = 1;
		goto end_class;
	}

	HWND dummyWindow = CreateWindowEx(
		0,
		dummyClass.lpszClassName,
		"dummyWindow",
		0,
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
		NULL, NULL,
		dummyClass.hInstance,
		NULL
	);
	if(dummyWindow == NULL) {
		error = 2;
		goto end_window;
	}
	HDC dummyDC = GetDC(dummyWindow);
	if(dummyDC == NULL){
		error = 3;
		goto end_DC;
	}

	PIXELFORMATDESCRIPTOR dummyPFD = {
		.nSize = sizeof(PIXELFORMATDESCRIPTOR),
		.nVersion = 1,
		.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
		.iPixelType = PFD_TYPE_RGBA,
		.cColorBits = 32,
		.cDepthBits = 24,
		.cStencilBits = 8
	};
	int pf = ChoosePixelFormat(dummyDC, &dummyPFD);
	if(pf == 0){
		error = 4;
		goto end_DC;
	}
	if(SetPixelFormat(dummyDC, pf, &dummyPFD) == FALSE){
		error = 5;
		goto end_DC;
	}

	HGLRC dummyContext = wglCreateContext(dummyDC);
	if(dummyContext == NULL){
		error = 6;
		goto end_context;
	}
	if(wglMakeCurrent(dummyDC, dummyContext) == FALSE){
		error = 7;
		goto end_current;
	}

	if(LoadExtensionList() == FALSE){
		error = 8;
	}

end_current:
	if(wglMakeCurrent(NULL, NULL) == FALSE){
		error = 9;
	}
end_context:
	if(wglDeleteContext(dummyContext) == FALSE){
		error = 10;
	}else{
		dummyContext = NULL;
	}
end_DC:
	if(ReleaseDC(dummyWindow, dummyDC) == 0){
		error = 11;
	}else{
		dummyDC = NULL;
	}
end_window:
	if(DestroyWindow(dummyWindow) == 0){
		error = 12;
	}else{
		dummyWindow = NULL;
	}
end_class:
	if(UnregisterClass(dummyClass.lpszClassName, dummyClass.hInstance) == 0){
		error = 13;
	}

	return error;
}

// TODO: abstract away to a platform-independent library
static long getFileSize(FILE *fp){
	long curr = ftell(fp);
	fseek(fp, 0, SEEK_END);
	long size = ftell(fp);
	fseek(fp, curr, SEEK_SET);
	return size;
}

GLuint LoadShaders(char *vertex_file_path, char *fragment_file_path){
	GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
	GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);

	// use str.h
	char *VertexShaderCode;
	FILE *VertexShaderStream = fopen(vertex_file_path, "rb");
	if(VertexShaderStream != NULL){
		long fsize = getFileSize(VertexShaderStream);
		VertexShaderCode = calloc(fsize, sizeof(char));
		fread(VertexShaderCode, sizeof(char), fsize, VertexShaderStream);
		fclose(VertexShaderStream);
	}else{
		return 0;
	}

	// use str.h
	char *FragmentShaderCode;
	FILE *FragmentShaderStream = fopen(fragment_file_path, "rb");
	if(FragmentShaderStream != NULL){
		long fsize = getFileSize(VertexShaderStream);
		FragmentShaderCode = calloc(fsize, sizeof(char));
		fread(FragmentShaderCode, sizeof(char), fsize, FragmentShaderStream);
		fclose(FragmentShaderStream);
	}else{
		return 0;
	}

	GLint Result;
	
	glShaderSource(VertexShaderID, 1, &VertexShaderCode, NULL);
	glCompileShader(VertexShaderID);
	glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
	if(Result != GL_TRUE){
		return 0;
	}

	glShaderSource(FragmentShaderID, 1, &FragmentShaderCode , NULL);
	glCompileShader(FragmentShaderID);
	glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
	if(Result != GL_TRUE){
		return 0;
	}

	GLuint ProgramID = glCreateProgram();
	glAttachShader(ProgramID, VertexShaderID);
	glAttachShader(ProgramID, FragmentShaderID);
	glLinkProgram(ProgramID);

	glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
	if(Result != GL_TRUE){
		return 0;
	}
	
	glDetachShader(ProgramID, VertexShaderID);
	glDetachShader(ProgramID, FragmentShaderID);
	glDeleteShader(VertexShaderID);
	glDeleteShader(FragmentShaderID);

	return ProgramID;
}