#include "RayExt.h"

#include <GLES2/gl2.h>

#include <raylib.h>
#include <raymath.h>
#include <rlgl.h>

static char const *SKYBOX_VS
    = "#version 300 es\n"
      "precision mediump float;\n"
      "layout(location=0) in vec3 vertexPosition;\n"
      "uniform mat4 mvp;\n"
      "out vec3 vDir;\n"
      "void main(){\n"
      "\tvDir = vertexPosition;\n"
      "\tvec4 pos = mvp * vec4(vertexPosition, 1.0);\n"
      "\tgl_Position = vec4(pos.xy, pos.w, pos.w); // z := 1 after division\n"
      "}\n";
static char const *SKYBOX_FS
    = "#version 300 es\n"
      "precision highp float;\n"
      "in vec3 vDir;\n"
      "uniform samplerCube environmentMap;\n"
      "out vec4 finalColor;\n"
      "void main(){ vec3 dir=normalize(vDir); "
      "finalColor=vec4(texture(environmentMap, normalize(vDir)).rgb, 1.0); }\n";

void Skybox_init(Skybox *skybox, char const *fp)
{
	if (skybox->ok) {
		Skybox_destroy(skybox);
	}

	// 1) Load cubemap from a 3x4 cross image
	Image img = LoadImage(fp);
	if (img.width == 0 || img.height == 0) {
		TraceLog(LOG_ERROR, "Skybox: failed to load image: %s", fp);
		skybox->ok = false;
		return;
	}

	TextureCubemap cubemap
	    = LoadTextureCubemap(img, CUBEMAP_LAYOUT_AUTO_DETECT);
	UnloadImage(img);
	if (cubemap.id == 0) {
		TraceLog(LOG_ERROR, "Skybox: failed to create cubemap from %s", fp);
		skybox->ok = false;
		return;
	}

	// 2) Make an inward-facing cube mesh
	Mesh m = GenMeshCube(
	    2.0f, 2.0f, 2.0f); // size doesn't matter; depth writes off
	// Invert winding so we see the inside
	for (int i = 0; i < m.triangleCount; ++i) {
		unsigned short *idx = &m.indices[i * 3];
		unsigned short tmp = idx[1];
		idx[1] = idx[2];
		idx[2] = tmp;
	}
	UploadMesh(&m, false);
	Model cube = LoadModelFromMesh(m);

	Shader sh = LoadShaderFromMemory(SKYBOX_VS, SKYBOX_FS);

	// make raylib aware which sampler is the cubemap
	sh.locs[SHADER_LOC_MAP_CUBEMAP] = GetShaderLocation(sh, "environmentMap");

	cube.materials[0].shader = sh;

	// put the cubemap in the expected material slot
	cube.materials[0].maps[MATERIAL_MAP_CUBEMAP].texture = cubemap;

	// nicer defaults
	SetTextureWrap(cubemap, TEXTURE_WRAP_CLAMP);
	SetTextureFilter(cubemap, TEXTURE_FILTER_BILINEAR);

	skybox->cubemap = cubemap;
	skybox->shader = sh;
	skybox->cube = cube;
	skybox->ok = true;
}

void Skybox_destroy(Skybox *skybox)
{
	if (!skybox->ok)
		return;

	UnloadModel(skybox->cube); // also unloads Mesh
	UnloadTexture(skybox->cubemap);
	UnloadShader(skybox->shader);

	*skybox = (Skybox) { 0 };
}

void Skybox_draw(Skybox const skybox, Vector3 camPos)
{
	if (!skybox.ok)
		return;

	rlDisableBackfaceCulling();
	rlDisableDepthMask();
	rlDisableColorBlend();

	rlDrawRenderBatchActive();
	GLint oldFunc = 0;
	glGetIntegerv(GL_DEPTH_FUNC, &oldFunc);
	glDepthFunc(GL_LEQUAL);

	DrawModel(skybox.cube, camPos, 500.0f, (Color) { 255, 255, 255, 255 });
	rlDrawRenderBatchActive();

	glDepthFunc(oldFunc ? oldFunc : GL_LESS);
	rlEnableColorBlend();
	rlEnableDepthMask();
	rlEnableBackfaceCulling();
}
