#include "shader.h"
#include "log.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char* readFileToMemory(const char* filepath) {
	FILE* f = fopen(filepath, "r");

	if (!f) {
		perror("Failed to open file");
		return NULL;
	}

	// determine file size

	fseek(f, 0L, SEEK_END);
	size_t size = ftell(f);

	rewind(f);

	// allocate buffer
	char* buf = (char*)malloc(size + 1);
	if (!buf) {
		perror("Failed to allocate memory");
		fclose(f);
		return NULL;
	}

	// read file to buffer
	fread(buf, 1, size, f);

	// set null byte at end of buffer
	buf[size] = '\0';

	fclose(f);
	return buf;
}

ShaderCompileResult readAndCompileShaderFromFile(const char* filepath, GLenum shaderType) {
	ShaderCompileResult result = { .success = false, .shader = 0 };  // Initialize result to indicate failure by default

	DEBUG("%s - Reading shader", filepath);
	const char* shaderSource = readFileToMemory(filepath);

	if (!shaderSource) {
		FATAL("Could not read shader source file");
		return result;
	}

	GLint shaderSize = strlen(shaderSource);

	DEBUG("%s - Creating shader", filepath);
	result.shader = glCreateShader(shaderType);
	
	DEBUG("%s - Applying shader source", filepath);
	glShaderSource(result.shader, 1, &shaderSource, &shaderSize);

	DEBUG("%s - Compiling shader", filepath);
	glCompileShader(result.shader);

	GLint status;
	glGetShaderiv(result.shader, GL_COMPILE_STATUS, &status);
	if (!status) {
		FATAL("Error compiling shader:");
		GLchar infoLog[1024];
		glGetShaderInfoLog(result.shader, 1024, NULL, infoLog);
		FATAL("%s", infoLog);

		result.success = false;
	} else {
		DEBUG("%s - Successfully compiled shader", filepath);
		result.success = true;
	}

	free((void*)shaderSource);

	return result; // Return the ShaderCompileResult struct
}

ProgramLinkResult linkShaderProgram(GLuint vertexShader, GLuint fragmentShader) {
	ProgramLinkResult result = { .success = false, .program = 0 };

	DEBUG("Creating program");
	result.program = glCreateProgram();

	DEBUG("Attaching Vertex Shader");
	glAttachShader(result.program, vertexShader);

	DEBUG("Attaching Fragment Shader");
	glAttachShader(result.program, fragmentShader);

	DEBUG("Linking Program");
	glLinkProgram(result.program);

	GLint status;
	glGetProgramiv(result.program, GL_LINK_STATUS, &status);
	if (!status) {
		FATAL("Error linking program:");
		GLchar infoLog[1024];
		glGetProgramInfoLog(result.program, 1024, NULL, infoLog);
		FATAL("%s", infoLog);
		return result;
	}

	DEBUG("Validating Program");
	glValidateProgram(result.program);
	glGetProgramiv(result.program, GL_VALIDATE_STATUS, &status);
	if (!status) {
		FATAL("Error validating program:");
		GLchar infoLog[1024];
		glGetProgramInfoLog(result.program, 1024, NULL, infoLog);
		FATAL("%s", infoLog);
		return result;
	}

	result.success = true;
	return result;
}

ProgramLinkResult buildShaderProgram(const char* vertexShaderPath, const char* fragmentShaderPath) {
	ProgramLinkResult result = { .success = false, .program = 0 };

	ShaderCompileResult vertexShader = readAndCompileShaderFromFile(vertexShaderPath, GL_VERTEX_SHADER);
	if (!vertexShader.success) {
		FATAL("Failed to compile vertex shader");
		return result;
	}

	ShaderCompileResult fragmentShader = readAndCompileShaderFromFile(fragmentShaderPath, GL_FRAGMENT_SHADER);
	if (!fragmentShader.success) {
		FATAL("Failed to compile fragment shader");
		return result;
	}

	result = linkShaderProgram(vertexShader.shader, fragmentShader.shader);

	glDeleteShader(vertexShader.shader);
	glDeleteShader(fragmentShader.shader);

	return result;
}
