#include <drawing_handler.hpp>

DrawingHandler::DrawingHandler(Toolbox& tb)
	:
	shader_draw_((tb.shader_path + "draw.vert").c_str(), (tb.shader_path + "draw.frag").c_str()),
	vao_(0),
	vbo_(0),
	fbo_(0)
{
	/*Shader*/
	shader_draw_.use();
	shader_draw_.setInt("source_texture", 0);
	
	/*vao, vbo*/
	glGenVertexArrays(1, &vao_);
	glGenBuffers(1, &vbo_);
	glBindVertexArray(vao_);
	glBindBuffer(GL_ARRAY_BUFFER, vbo_);
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);
	glBindVertexArray(0);
	/*fbo*/
	glGenFramebuffers(1, &fbo_);
	glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tb.tex_damp_static, 0);
	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
		std::cout << "ERROR: DRAWINGMANAGER: FRAMEBUFFER NOT COMPLETE" << std::endl;
	}
	/*wave fbo*/
	glGenFramebuffers(1, &fbo_wave_);
	glBindFramebuffer(GL_FRAMEBUFFER, fbo_wave_);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tb.tex_wave_1, 0);
	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
		std::cout << "ERROR: DRAWINGMANAGER: WAVE FRAMEBUFFER NOT COMPLETE" << std::endl;
	}
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

DrawingHandler::~DrawingHandler() {
	shader_draw_.clean_up();
	glDeleteVertexArrays(1, &vao_);
	glDeleteBuffers(1, &vbo_);
	glDeleteFramebuffers(1, &fbo_);
}

void DrawingHandler::update(Toolbox& tb) {
	if (previous_mstate_ != tb.m_state) {
		drawpairs_.clear();
	}
	previous_mstate_ = tb.m_state;

	/*Handle Messages*/
	for (Message& m : tb.mailbox) {
		if (m.target == MESSAGETARGET::DRAWER) {
			DRAWERMESSAGE message = std::get<DRAWERMESSAGE>(m.message);
			switch (message) {
			case DRAWERMESSAGE::CLEAR:
				drawpairs_.clear();
				m.handled = true;
				break;
			default:
				break;
			}
		}
	}

	/*
	//This is touchscreen-only
	//Uncomment on a touchscreen
	//Catch dangling Drawers, no touches means no drawers
	if (drawpairs_.size() > 0 && tb.current_touchIDs.size() == 0 && tb.events.size() == 0) {
		drawpairs_.clear();
#ifndef NDEBUG
		std::cout << "Emergency Drawer-Release" << std::endl;
#endif
	}
	*/

	/*We can return here if MSTATE is not interesting*/
	if (tb.m_state != static_cast<int>(MSTATE::DRAW) && tb.m_state != static_cast<int>(MSTATE::ERASE)) {
		return;
	}
	/*Handle Events*/
	/*Bind*/
	shader_draw_.use();
	glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
	glBindVertexArray(vao_);
	glBindBuffer(GL_ARRAY_BUFFER, vbo_);
	glViewport(0, 0, tb.texture_w, tb.texture_h);
	for (Pevent& pev : tb.events) {
		/*Event is in wave window*/
		if (in_wave_window_(tb, pev)) {
			if (pev.type == PEVENTTYPE::DOWN) {
				if (tb.m_state == static_cast<int>(MSTATE::DRAW)) {
					/*Start drawing with new Drawer here*/
					std::array<float, 2> coords = drawerCoordinates_(pev, tb);
					Drawer d(tb);
					d.start_drawing(coords[0], coords[1]);
					drawpairs_.push_back({ d, pev.finger_id });
				}
				else if (tb.m_state == static_cast<int>(MSTATE::ERASE)) {
					/*Start erasing with new Eraser here*/
					std::array<float, 2> coords = drawerCoordinates_(pev, tb);
					Drawer d(tb);
					d.start_drawing(coords[0], coords[1]);
					drawpairs_.push_back({ d, pev.finger_id });
				}
			}
			else if (pev.type == PEVENTTYPE::UP) {
				if (tb.m_state == static_cast<int>(MSTATE::DRAW)) {
					/*Stop drawing with correct Drawer and delete it here*/
					for (auto it = drawpairs_.begin(); it != drawpairs_.end(); ++it) {
						if (it->second == pev.finger_id) {
							drawpairs_.erase(it);
							break; //only one drawer per finger exists
						}
					}
				}
				else if (tb.m_state == static_cast<int>(MSTATE::ERASE)) {
					/*Stop erasing with correct Drawer and delete it here*/
					for (auto it = drawpairs_.begin(); it != drawpairs_.end(); ++it) {
						if (it->second == pev.finger_id) {
							drawpairs_.erase(it);
							break; //only one drawer per finger exists
						}
					}
				}
			}
			else if (pev.type == PEVENTTYPE::MOVE) {
				if (tb.m_state == static_cast<int>(MSTATE::DRAW)) {
					/*Draw with correct Drawer here*/
					for (std::pair<Drawer, SDL_FingerID>& dp : drawpairs_) {
						if (dp.second == pev.finger_id) {
							glActiveTexture(GL_TEXTURE0);
							glBindTexture(GL_TEXTURE_2D, tb.tex_const_zero);
							std::array<float, 2> coords = drawerCoordinates_(pev, tb);
							bool actually_drawn = dp.first.draw(coords[0], coords[1], tb, true); //draw on damping
							if (actually_drawn) {
								glBindFramebuffer(GL_FRAMEBUFFER, fbo_wave_);
								glBindTexture(GL_TEXTURE_2D, tb.tex_wave_clean);
								dp.first.redraw(tb); //redraw on wave
								glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
							}
							break; //only one drawer per finger exists
						}
					}
				}
				else if (tb.m_state == static_cast<int>(MSTATE::ERASE)) {
					/*Erase with correct Eraser here*/
					for (std::pair<Drawer, SDL_FingerID>& dp : drawpairs_) {
						if (dp.second == pev.finger_id) {
							glActiveTexture(GL_TEXTURE0);
							glBindTexture(GL_TEXTURE_2D, tb.tex_damp_clean);
							std::array<float, 2> coords = drawerCoordinates_(pev, tb);
							dp.first.draw(coords[0], coords[1], tb, false); //erase
							break; //only one drawer per finger exists
						}
					}
				}
			}
		}
		/*Event not in wave window*/
		else {
			switch (pev.type) {
			/*Finger down outside wave area, don't care*/
			case PEVENTTYPE::DOWN:
				break;
			/*Finger up or moved outside wave area, if associated Drawer exists, clear it*/
			case PEVENTTYPE::UP:
				[[fallthrough]];
			case PEVENTTYPE::MOVE: {
				for (auto it = drawpairs_.begin(); it != drawpairs_.end(); ++it) {
					if (it->second == pev.finger_id) {
						drawpairs_.erase(it);
						break; //only one drawer per finger exists
					}
				}
				break;
			}
			}
		}
	}
	/*Unbind*/
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	/*Push info to toolbox developer region*/
	tb.num_drawers = num_drawers();
}

size_t DrawingHandler::num_drawers() {
	return drawpairs_.size();
}

void DrawingHandler::draw_(Toolbox& tb) {
	/*This probably doesn't do anything for now*/
	return;
}

bool DrawingHandler::in_wave_window_(const Toolbox& tb, const Pevent& pev) const {
	return pev.fscoord_x < 1.f - tb.gui_pos;
}

std::array<float, 2> DrawingHandler::drawerCoordinates_(const Pevent& pev, const Toolbox& tb) {
	return {	static_cast<float>(pev.itcoord_x) / static_cast<float>(tb.texture_w) * 2.f - 1.f,				//x
						- (static_cast<float>(pev.itcoord_y) / static_cast<float>(tb.texture_h) * 2.f - 1.f) }; //y
}