#define GL_GLEXT_PROTOTYPES 1
#ifdef USE_OSM

#else
#include <GL/gl.h>
#include <EGL/egl.h>
#endif
#include <string.h>
#include <GL/glu.h>

#include "global.h"
#include "stb_image.h"
#include "resources.h"

GLuint allVBOs[128];
int vboCount = 0;
GLuint allVAOs[128];
int vaoCount = 0;
GLuint allShaderPrograms[128];
int shaderPgmCount = 0;
GLuint allTextures[128];
int textureCount = 0;

void resource_handler_reset() {
	vboCount = vaoCount = shaderPgmCount = textureCount = 0;
	for(int i = 0; i < 128; i++) allTextures[i] = allVAOs[i] = allVBOs[i] = allShaderPrograms[i] = 0;
}

void cleanup() {
	for(int i = 0; i < 128; i++) {
		if(allVBOs[i] != 0) glDeleteBuffers(1, allVBOs + i);
		if(allVAOs[i] != 0) glDeleteVertexArrays(1, allVAOs + i);
		if(allShaderPrograms[i] != 0) glDeleteProgram(allShaderPrograms[i]);
		if(allTextures[i] != 0) glDeleteTextures(1, allTextures + i);
	}
}

GLuint create_shader_from_file(char* filename, uint8_t frag) {
	FILE* file = fopen(filename, "r");
	if(!file) {
		printf("Failed to open shader source file %s\r\n", filename);
		return 0;
	}
	fseek(file, 0L, SEEK_END);
	size_t sz = ftell(file);
	rewind(file);
	char* sourceString = malloc((sz + 1) * sizeof(char));
	memset(sourceString, 0, (sz + 1) * sizeof(char));
	size_t read = fread(sourceString, sizeof(char), sz, file);
	if(read != sz) {
		printf("Failed to read shader source: data incomplete");
		fclose(file);
		return 0;
	}
	fclose(file);
	GLuint result = glCreateShader(frag ? GL_FRAGMENT_SHADER : GL_VERTEX_SHADER);
	glShaderSource(result, 1, &sourceString, NULL);
	glCompileShader(result);
	free(sourceString);
	int success;
	glGetShaderiv(result, GL_COMPILE_STATUS, &success);
	if(!success) {
		printf("Shader compilation failed due to the following error in %s:\r\n", filename);
		char infoLog[512];
		glGetShaderInfoLog(result, 512, NULL, infoLog);
		printf("%s\r\n", infoLog);
		glDeleteShader(result);
		return 0;
	}
	return result;
}

GLuint create_shader_program(char* vFilename, char* fFilename) {
	if(shaderPgmCount == 128) {
		printf("Too many shader programs!\r\n");
		return 0;
	}
	GLuint vShader = create_shader_from_file(vFilename, 0);
	if(vShader == 0) return 0;
	GLuint fShader = create_shader_from_file(fFilename, 1);
	if(fShader == 0) {
		glDeleteShader(vShader);
		return 0;
	}
	GLuint shaderPgm = glCreateProgram();
	glAttachShader(shaderPgm, vShader);
	glAttachShader(shaderPgm, fShader);
	glLinkProgram(shaderPgm);
	int success;
	glGetProgramiv(shaderPgm, GL_LINK_STATUS, &success);
	glDeleteShader(vShader);
	glDeleteShader(fShader);
	if(!success) {
		printf("Shader linking failed due to the following error:\r\n");
		char infoLog[512];
		glGetProgramInfoLog(shaderPgm, 512, NULL, infoLog);
		printf("%s\r\n", infoLog);
		glDeleteProgram(shaderPgm);
		return 0;
	}
	allShaderPrograms[shaderPgmCount] = shaderPgm;
	shaderPgmCount++;
	return shaderPgm;
}

GLuint create_vbo() {
	if(vboCount == 128) {
		printf("Too many VBOs!\r\n");
		return 0;
	}
	GLuint VBO;
	glGenBuffers(1, &VBO);
	allVBOs[vboCount] = VBO;
	vboCount++;
	return VBO;
}

GLuint create_vao() {
	if(vaoCount == 128) {
		printf("Too many VAOs!\r\n");
		return 0;
	}
	GLuint VAO;
	glGenVertexArrays(1, &VAO);
	allVAOs[vaoCount] = VAO;
	vaoCount++;
	return VAO;
}

GLuint create_texture() {
	if(textureCount == 128) {
		printf("Too many textures!\r\n");
		return 0;
	}
	GLuint texture;
	glGenTextures(1, &texture);
	allTextures[textureCount] = texture;
	textureCount++;
	glBindTexture(GL_TEXTURE_2D, texture);
	return texture;
}

texture texture_from_file(char* filename, uint8_t genMipmaps) {
	texture res;
	unsigned char *data = stbi_load(filename, &res.width, &res.height, &res.nrChannels, 0);
	if(!data) {
		printf("Texture file %s failed to load!\r\n", filename);
		res.id = res.width = res.height = res.nrChannels = 0;
		return res;
	}
	res.id = create_texture();
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, genMipmaps ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexImage2D(GL_TEXTURE_2D, 0, res.nrChannels == 3 ? GL_RGB : GL_RGBA, res.width, res.height, 0, res.nrChannels == 3 ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, data);
	if(!(res.nrChannels == 3 || res.nrChannels == 4)) {
		printf("Texture file %s has weird number of channels (%d)!\r\n", filename, res.nrChannels);
		res.id = 0;
		return res;
	}
	if(genMipmaps) glGenerateMipmap(GL_TEXTURE_2D);
	printf("%s: %d channels\r\n", filename, res.nrChannels);
	stbi_image_free(data);
	return res;
}

texture blank_texture(int width, int height, uint8_t hasAlpha) {
	texture res;
	res.width = width;
	res.height = height;
	res.id = create_texture();
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexImage2D(GL_TEXTURE_2D, 0, hasAlpha ? GL_RGBA : GL_RGB, res.width, res.height, 0, hasAlpha ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, NULL);
	int a = glGetError();
	if(a) {
		char *errStr;
		errStr = gluErrorString(a);
		printf("%s\r\n", errStr);
		glDeleteTextures(1, &res.id);
		res.id = 0;
		return res;
	}
	return res;
}

GLuint create_cube_texture(int cube_tex_size) {
	if(textureCount == 128) {
		printf("Too many textures!\r\n");
		return 0;
	}
	GLuint cube_texture;
    glGenTextures(1, &cube_texture);
    glBindTexture(GL_TEXTURE_CUBE_MAP, cube_texture);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, 0);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    
    for(int i = 0; i < 6; i++) {
		glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, cube_tex_size, cube_tex_size, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
		int a = glGetError();
		if(a) {
			char *errStr;
			errStr = gluErrorString(a);
			printf("%s\r\n", errStr);
			glDeleteTextures(1, &cube_texture);
			return 0;
		}
	}
    
    glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
	allTextures[textureCount] = cube_texture;
	textureCount++;
    return cube_texture;
}
