#include "Cubemap.h"

#include <vector>
#include "GL/glew.h"
#include "stb_image.h"

#include "Application.h"
#include "Shader.h"
#include "Assets.h"

namespace nf {
	Cubemap::Cubemap() :
		m_constructed(false),
		m_id(0)
	{

	}

	void Cubemap::create(Asset* cubemapAsset) {
		if (m_constructed)
			Error("Cubemap already created!");
		m_constructed = true;
		ACubemap& cm = *(ACubemap*)cubemapAsset;
		glGenTextures(1, &m_id);
		glActiveTexture(GL_TEXTURE12);
		glBindTexture(GL_TEXTURE_CUBE_MAP, m_id);
		struct CMFace {
			CMFace(unsigned char* d, unsigned int w, unsigned int h, unsigned int nc) :
				data(d),
				width(w),
				height(h),
				nChannels(nc)
			{

			}
			unsigned char* data;
			unsigned int width;
			unsigned int height;
			unsigned int nChannels;
		};
		std::vector<CMFace> faces;
		int tempWidth, tempHeight, tempNChannels;
		unsigned char* tempData;
		stbi_set_flip_vertically_on_load(false);
		tempData = stbi_load_from_memory((unsigned char*)cm.leftData, cm.leftSize, &tempWidth, &tempHeight, &tempNChannels, 0);
		faces.emplace_back(tempData, tempWidth, tempHeight, tempNChannels);
		tempData = stbi_load_from_memory((unsigned char*)cm.rightData, cm.rightSize, &tempWidth, &tempHeight, &tempNChannels, 0);
		faces.emplace_back(tempData, tempWidth, tempHeight, tempNChannels);
		stbi_set_flip_vertically_on_load(true);
		tempData = stbi_load_from_memory((unsigned char*)cm.topData, cm.topSize, &tempWidth, &tempHeight, &tempNChannels, 0);
		faces.emplace_back(tempData, tempWidth, tempHeight, tempNChannels);
		tempData = stbi_load_from_memory((unsigned char*)cm.bottomData, cm.bottomSize, &tempWidth, &tempHeight, &tempNChannels, 0);
		faces.emplace_back(tempData, tempWidth, tempHeight, tempNChannels);
		stbi_set_flip_vertically_on_load(false);
		tempData = stbi_load_from_memory((unsigned char*)cm.backData, cm.backSize, &tempWidth, &tempHeight, &tempNChannels, 0);
		faces.emplace_back(tempData, tempWidth, tempHeight, tempNChannels);
		tempData = stbi_load_from_memory((unsigned char*)cm.frontData, cm.frontSize, &tempWidth, &tempHeight, &tempNChannels, 0);
		faces.emplace_back(tempData, tempWidth, tempHeight, tempNChannels);
		for (unsigned int i = 0; i < faces.size(); i++) {
			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA, faces[i].width, faces[i].height, 0, faces[i].nChannels == 3 ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, faces[i].data);
			stbi_image_free(faces[i].data);
		}
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
		glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
		glActiveTexture(GL_TEXTURE0);

		float vb[] = {
			-1.0f,  1.0f, -1.0f,
			-1.0f, -1.0f, -1.0f,
			 1.0f, -1.0f, -1.0f,
			 1.0f, -1.0f, -1.0f,
			 1.0f,  1.0f, -1.0f,
			-1.0f,  1.0f, -1.0f,

			-1.0f, -1.0f,  1.0f,
			-1.0f, -1.0f, -1.0f,
			-1.0f,  1.0f, -1.0f,
			-1.0f,  1.0f, -1.0f,
			-1.0f,  1.0f,  1.0f,
			-1.0f, -1.0f,  1.0f,

			 1.0f, -1.0f, -1.0f,
			 1.0f, -1.0f,  1.0f,
			 1.0f,  1.0f,  1.0f,
			 1.0f,  1.0f,  1.0f,
			 1.0f,  1.0f, -1.0f,
			 1.0f, -1.0f, -1.0f,

			-1.0f, -1.0f,  1.0f,
			-1.0f,  1.0f,  1.0f,
			 1.0f,  1.0f,  1.0f,
			 1.0f,  1.0f,  1.0f,
			 1.0f, -1.0f,  1.0f,
			-1.0f, -1.0f,  1.0f,

			-1.0f,  1.0f, -1.0f,
			 1.0f,  1.0f, -1.0f,
			 1.0f,  1.0f,  1.0f,
			 1.0f,  1.0f,  1.0f,
			-1.0f,  1.0f,  1.0f,
			-1.0f,  1.0f, -1.0f,

			-1.0f, -1.0f, -1.0f,
			-1.0f, -1.0f,  1.0f,
			 1.0f, -1.0f, -1.0f,
			 1.0f, -1.0f, -1.0f,
			-1.0f, -1.0f,  1.0f,
			 1.0f, -1.0f,  1.0f
		};
		m_vao = new VertexArray;
		m_vao->addBuffer(vb, sizeof(vb));
		m_vao->push<float>(3);
		m_vao->finishBufferLayout();

		if (!Application::getApp()->getCurrentState()->isRunning())
			Application::getApp()->getCurrentState()->m_nfObjects.push_back(this);
	}

	bool Cubemap::isConstructed() {
		return m_constructed;
	}

	void Cubemap::render(Shader* shader) {
		m_vao->bind();
		glActiveTexture(GL_TEXTURE12);
		shader->setUniform("cm", 12);
		glBindTexture(GL_TEXTURE_CUBE_MAP, m_id);
		glDepthFunc(GL_LEQUAL);
		glDrawArrays(GL_TRIANGLES, 0, 36);
		glDepthFunc(GL_LESS);
	}

	void Cubemap::destroy() {
		if (m_constructed)
			glDeleteTextures(1, &m_id);
		m_constructed = false;
		m_id = 0;
		delete m_vao;
	}

	Cubemap::~Cubemap() {
		destroy();
	}
}