#include "../external/glad/src/gl.c"

#include "assets.cpp"

// ::Globals::

PipelineCreateInfo g_Pipeline_Create_Info[] = {
	{ .vertex_shader = "shaders/pbr.vert", .fragment_shader = "shaders/pbr.frag" },
	//{ .vertex_shader = "shaders/gui.vert", .fragment_shader = "shaders/gui.frag" },
};

// ::Init::Impl::Start::

MeshBuffer yoder;

static b32
Init(Renderer *renderer)
{
	b32 success = true;

	RGFW_setGLHint(RGFW_glMajor, 4);
	RGFW_setGLHint(RGFW_glMinor, 6);
	RGFW_setGLHint(RGFW_glProfile, RGFW_glCore);

	renderer->window = RGFW_createWindow("Video Game", RGFW_RECT(0, 0, 1280, 720), RGFW_windowCenter | RGFW_windowNoResize);
	if (renderer->window == NULL)
	{
		Printfln("Failed to create window");
		success = false;
	}

	if (success)
	{
		RGFW_window_makeCurrent(renderer->window);

		int glad_version = gladLoaderLoadGL();
		if (!glad_version)
		{
			Printfln("gladLoaderLoadGL failure");
			success = false;
		}
	}

	for (u32 i = 0; i < PIPELINE_MAX; i += 1)
	{
		renderer->pipelines[i] = BuildPipeline(g_Pipeline_Create_Info + i);
	}

	yoder = CreateMeshBuffer("models/yoda");

	return success;
}


static PipelineHandle 
BuildPipeline(PipelineCreateInfo *create_info)
{
	auto vertex_shader = CreateShader(GL_VERTEX_SHADER);
	Assert(BuildShader(&vertex_shader, create_info->vertex_shader), "BuildShader vertex failure");

	auto fragment_shader = CreateShader(GL_FRAGMENT_SHADER);
	Assert(BuildShader(&fragment_shader, create_info->fragment_shader), "BuildShader fragment failure");

	auto pipeline = CreatePipeline();
	glAttachShader(pipeline, vertex_shader);
	glAttachShader(pipeline, fragment_shader);
	glLinkProgram(pipeline);

	int success = 0;
	glGetProgramiv(pipeline, GL_LINK_STATUS, &success);
	if (!success)
	{
		char info_log[512];
		glGetProgramInfoLog(pipeline, 512, NULL, info_log);
		Assert(false, "Failed to build pipeline");
	}

	return pipeline;
}

static b32 
BuildShader(ShaderHandle *handle, char *asset)
{
	b32 success = true;

	Asset data = apLoad(asset);
	Assert(data.bytes != NULL && asset, "Shader data is NULL");

	glShaderBinary(1, handle, GL_SHADER_BINARY_FORMAT_SPIR_V, data.bytes, data.len);
	glSpecializeShader(*handle, "main", 0, 0, 0);

	int compiled = 0;
	glGetShaderiv(*handle, GL_COMPILE_STATUS, &compiled);
	if (!compiled)
	{
		char info_log[512];
		glGetShaderInfoLog(*handle, 512, NULL, info_log);
		Printfln("Unable to compile shader: %s", info_log);
		success = false;
	}

	return success;
}

static PipelineHandle 
CreatePipeline()
{
	PipelineHandle pipeline = glCreateProgram();
	return pipeline;
}

static VertexBuffer 
CreateVertexBuffer()
{
	VertexBuffer buf;
	glGenBuffers(1, &buf);

	return buf;
}

static IndexBuffer 
CreateIndexBuffer()
{
	IndexBuffer buf;
	glGenBuffers(1, &buf);

	return buf;
}

static VertexArray
CreateVertexArray()
{
	VertexArray array;
	glGenVertexArrays(1, &array);

	return array;
}

static ShaderHandle 
CreateShader(int type)
{
	return (ShaderHandle)glCreateShader(type);
}

static MeshBuffer
CreateMeshBuffer(char *asset)
{
	MeshBuffer mesh = {0};

	/*
	mesh.array = CreateVertexArray();
	mesh.vertex = CreateVertexBuffer();
	mesh.index = CreateIndexBuffer();

	glBindVertexArray(mesh.array);
	glBindBuffer(GL_ARRAY_BUFFER, mesh.vertex);

	Asset data = apLoad(asset);
	Assert(data.model != NULL, "CreateMeshBuffer failed to load asset");

	mesh.draw_count = data.model_meta.i_count;

	Printfln("v_count: %llu", data.model->v_count);

	glBufferData(
		GL_ARRAY_BUFFER, 
		data.model->v_count * sizeof(Vertex),
		data.model->vertices,
		GL_STATIC_DRAW
	);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.index);
	glBufferData(
		GL_ELEMENT_ARRAY_BUFFER,
		data.model->i_count * sizeof(u32),
		data.model->indices,
		GL_STATIC_DRAW
	);

	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);

	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, col));

	apUnload(asset);

	glBindVertexArray(0);

	*/

	return mesh;
}

static void
DrawMeshBuffer(MeshBuffer *mesh)
{
	glBindVertexArray(mesh->array);
	glDrawElements(GL_TRIANGLES, mesh->draw_count, GL_UNSIGNED_INT, 0);
	glBindVertexArray(0);
}

// ::Init::Impl::End::



// ::Textures::Impl::Start::

static u32 
CreateTexture(rawptr data, u32 width, u32 height, u32 channels)
{
	u32 tex;
	glCreateTextures(GL_TEXTURE_2D, 1, &tex);

	i32 mipmap = Mini32(5, i32(log2f(f32(Maxi32(width, height)))));

	u32 format, copy_format;
	switch (channels) 
	{
	case 1:
		format = GL_R8;
		copy_format = GL_RED;
		break;
	case 2:
		format = GL_RG8;
		copy_format = GL_RG;
	case 3:
		format = GL_RGB8;
		copy_format = GL_RGB;
	case 4:
		format = GL_RGBA8;
		copy_format = GL_RGBA;
	default:
		Assert(false, "CreateTexture failure: unsupported channel number passed in");
	}

	glTextureStorage2D(tex, mipmap, format, width, height);
	glTextureSubImage2D(tex, 0, 0, 0, width, height, copy_format, GL_UNSIGNED_BYTE, data);

	glTextureParameteri(tex, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	glTextureParameteri(tex, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTextureParameterf(tex, GL_TEXTURE_BASE_LEVEL, 0);
	glTextureParameteri(tex, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTextureParameteri(tex, GL_TEXTURE_WRAP_T, GL_REPEAT);

	glGenerateTextureMipmap(tex);

	return tex;
}

// ::Textures::Impl::End::

static b32
RunCycle(Renderer *renderer)
{
	while (RGFW_window_checkEvent(renderer->window));

	glUseProgram(renderer->pipelines[PIPELINE_PBR]);

	glClearColor(0.1f, 0.8f, 0.4f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);

	DrawMeshBuffer(&yoder);

	RGFW_window_swapBuffers(renderer->window);

	return !RGFW_window_shouldClose(renderer->window);
}
