#include <pattern_handler.hpp>

PatternHandler::PatternHandler(Toolbox& tb)
	: shader_((tb.shader_path + "copy_texture.vert").c_str(), (tb.shader_path + "copy_texture.frag").c_str())
{
	shader_.use();
	shader_.setInt("source_texture", 0);

	/*Framebuffer*/
	glGenFramebuffers(1, &fbo_static_);
	glBindFramebuffer(GL_FRAMEBUFFER, fbo_static_);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tb.tex_damp_static, 0);
	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
		std::cout << "ERROR::FRAMEBUFFER: Blockchain Framebuffer not complete!" << std::endl;
	}
	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	/*VBO data*/
	std::vector<float> vbo_data = {
		/*				x				y			u				v*/
		/*blc*/ -1.f,		-1.f,		0.f,		0.f,
		/*trc*/ 1.f,		1.f,		1.f,		1.f,
		/*brc*/	1.f,		-1.f,		1.f,		0.f,
		/*blc*/	-1.f,		-1.f,		0.f,		0.f,
		/*trc*/ 1.f,		1.f,		1.f,		1.f,
		/*tlc*/ -1.f,		1.f,		0.f,		1.f
	};

	/*vao, vbo*/
	glGenVertexArrays(1, &vao_);
	glGenBuffers(1, &vbo_);
	glBindVertexArray(vao_);
	glBindBuffer(GL_ARRAY_BUFFER, vbo_);
	glBufferData(GL_ARRAY_BUFFER, vbo_data.size() * sizeof(float), vbo_data.data(), GL_STATIC_DRAW);
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
	glEnableVertexAttribArray(1);
	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	/*Load structure textures*/
	std::cout << "Loading slit.tex" << std::endl;
	load_damping_texture_(tb, tb.texture_path + "slit", &texture_singleslit_);
	std::cout << "Loading doubleslit.tex" << std::endl;
	load_damping_texture_(tb, tb.texture_path + "doubleslit", &texture_doubleslit_);
	std::cout << "Loading square.tex" << std::endl;
	load_damping_texture_(tb, tb.texture_path + "square", &texture_lattice_);
	std::cout << "Loading channel.tex" << std::endl;
	load_damping_texture_(tb, tb.texture_path + "channel", &texture_waveguide_);
	std::cout << "Loading ssh.tex" << std::endl;
	load_damping_texture_(tb, tb.texture_path + "ssh", &texture_ssh_);
	std::cout << "Loading fresnel.tex" << std::endl;
	load_damping_texture_(tb, tb.texture_path + "fresnel", &texture_fresnel_);
}

void PatternHandler::update(Toolbox& tb) {
	/*Handle messages*/
	for (Message& m : tb.mailbox) {
		if (m.target == MESSAGETARGET::PATTERN) {
			PATTERNMESSAGE message = std::get<PATTERNMESSAGE>(m.message);
			switch (message) {
			case PATTERNMESSAGE::PATTERN_SINGLESLIT:
				queued_texture_ = texture_singleslit_;
				break;
			case PATTERNMESSAGE::PATTERN_DOUBLESLIT:
				queued_texture_ = texture_doubleslit_;
				break;
			case PATTERNMESSAGE::PATTERN_LATTICE:
				queued_texture_ = texture_lattice_;
				break;
			case PATTERNMESSAGE::PATTERN_WAVEGUIDE:
				queued_texture_ = texture_waveguide_;
				break;
			case PATTERNMESSAGE::PATTERN_SSH:
				queued_texture_ = texture_ssh_;
				break;
			case PATTERNMESSAGE::PATTERN_FRESNEL:
				queued_texture_ = texture_fresnel_;
				break;
			default:
				break;
			}
		}
	}

	/*Draw pattern, if requested*/
	if (queued_texture_ != 0) {
		glBindVertexArray(vao_);
		glBindBuffer(GL_ARRAY_BUFFER, vbo_);
		glViewport(0, 0, tb.texture_w, tb.texture_h);
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, queued_texture_);
		shader_.use();
		glBindFramebuffer(GL_FRAMEBUFFER, fbo_static_);
		glDrawArrays(GL_TRIANGLES, 0, 6);
		glBindVertexArray(0);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
		/*Reset queued texture*/
		queued_texture_ = 0;
	}
}

bool PatternHandler::load_damping_texture_(const Toolbox& tb, const std::string filename, GLuint* texture_target) {
	/*Load info about the texture from configuration file*/
	int screen_width, screen_height, texture_width, texture_height;
	int texoffset_left, texoffset_right, texoffset_bottom, texoffset_top;
	std::string config_filename = filename + ".conf";
	std::fstream cfg_file;
	cfg_file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
	try {
		cfg_file.open(config_filename);
		cfg_file >> screen_width;
		cfg_file >> screen_height;
		cfg_file >> texture_width;
		cfg_file >> texture_height;
		cfg_file >> texoffset_left;
		cfg_file >> texoffset_right;
		cfg_file >> texoffset_bottom;
		cfg_file >> texoffset_top;
		cfg_file.close();
	}
	catch (std::ifstream::failure e) {
		std::cout << "Failed to open file " + config_filename << std::endl;
		return false;
	}
#ifndef NDEBUG
	std::cout << "Loaded configuration from " + config_filename;
	std::cout << "\n\tScreen Resolution: " << screen_width << "x" << screen_height;
	std::cout << "\n\tTexture Resolution: " << texture_width << "x" << texture_height;
	std::cout << "\n\tOffset Left: " << texoffset_left;
	std::cout << "\n\tOffset Right: " << texoffset_right;
	std::cout << "\n\tOffset Bottom: " << texoffset_bottom;
	std::cout << "\n\tOffset Top: " << texoffset_top << std::endl;
#endif
	/*Check if configuration is compatible with toolbox settings*/
	if (tb.screen_h != screen_height || tb.screen_w != screen_width) {
		std::cout << "Incompatible Toolbox Screen Resolution: " << tb.screen_w << "x" << tb.screen_h << std::endl;
		return false;
	}
	else if (tb.texture_h != texture_height || tb.texture_w != texture_width) {
		std::cout << "Incompatible Toolbox Texture Resolution: " << tb.texture_w << "x" << tb.texture_h << std::endl;
		return false;
	}
	else if (tb.texoffset_left != texoffset_left || tb.texoffset_right != texoffset_right || tb.texoffset_bottom != texoffset_bottom || tb.texoffset_top != texoffset_top) {
		std::cout << "Incompatible Offsets" << std::endl;
		return false;
	}

	/*Load texture data*/
#ifndef NDEBUG
	else {
		std::cout << "Texture is compatible" << std::endl;
	}
	std::cout << "Loading Texture" << std::endl;
#endif

	std::fstream dmp_file;
	std::string damping_filename = filename + ".texture";
	std::vector<float> data_target;
	data_target.reserve(4 * texture_width * texture_height);
	dmp_file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
	try {
		dmp_file.open(damping_filename);

		float value;
		for (size_t i = 0; i < 4 * texture_width * texture_height; ++i) {
			/*If the file is finished at this point, we're fucked*/
			if (dmp_file.peek() == EOF) {
				std::cout << "Unexpected End Of Damping File Encountered" << std::endl;
				data_target.clear();
				return false;
			}
			dmp_file >> value;
			data_target.push_back(value);
		}

		dmp_file.close();
	}
	catch (std::ifstream::failure e) {
		std::cout << "Failed to open file " + damping_filename << std::endl;
		return false;
	}

	/*Generate and upload texture*/
	glGenTextures(1, texture_target);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, *texture_target);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, texture_width, texture_height, 0, GL_RGBA, GL_FLOAT, data_target.data());
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glBindTexture(GL_TEXTURE_2D, 0);


	return true;
}