#pragma once
#include "WEntity.h"
#include "gl/Buffer.h"
#include "gl/ShaderProgram.h"
#include "model/Mesh.h"
#include "model/ModelNode.h"


struct DrawArraysIndirectCommand{
	uint32_t  count;
	uint32_t  instanceCount = 1;
	uint32_t  first = 0;
	uint32_t  baseInstance = 0;
};

struct MDIUniforms {
	glm::mat4 m;
	glm::mat4 mb;
	glm::mat4 mc;

	int a;
	int b;
	int c;
	int d;

	int e;
	int f;
	int g;
	int h;
};

class DrawerMDI {

public:

	Buffer* mdi_buff;
	Buffer* mdi_param_buff;
	Buffer* uniforms_buff;

	int draw_cnt_running = 0;
	int max_draw_cnt = 0;
	// DrawArraysIndirectCommand first_cmd;

	DrawerMDI() {
		int mdi_buff_byte_len = megabyte_in_bytes * 50;

		max_draw_cnt = mdi_buff_byte_len / sizeof(DrawArraysIndirectCommand) - 1;

		this->mdi_buff = new Buffer(BuffDesc{
			.byte_len = megabyte_in_bytes * 50,
			.subdata_disabled = false,
			.write_from_shader = false,
			.name = "MDI buff"
		});
		this->uniforms_buff = new Buffer(BuffDesc{
			.byte_len = megabyte_in_bytes * 50,
			.subdata_disabled = false,
			.write_from_shader = false,
			.name = "MDI uniforms buff"
		});

		this->mdi_param_buff = new Buffer(BuffDesc{.byte_len = 4*4, .subdata_disabled = false, .write_from_shader = false, .name = "MDI Param buff"});

		this->mdi_buff->bind_as_MDI_buff();
		this->mdi_param_buff->bind_as_MDI_PARAM_buff();

		// this->mdi_buff->bind_as_SSBO(3);
		// this->mdi_param_buff->bind_as_SSBO(4);

	}

	void push(WEntity* game_object, bool shadow_map) {
		ShaderProgram* shader_p = game_object->shader_prog;
		if(!shader_p) {
			return;
		}
		if (shadow_map > 0.0) {
			if (game_object->shader_prog->shadow_map_variation) {
				shader_p = game_object->shader_prog->shadow_map_variation;
			}
		}

		// shader_p->use();
		// shader_p->setUniform("rot_mat", game_object->rot_matrix);


		for (auto mesh : game_object->model_node->meshes) {
			MDIUniforms uniforms = MDIUniforms{
				game_object->rot_matrix,
				game_object->matrix,
				game_object->prev_matrix,

				mesh->buff_normals_idx / 2,
				mesh->buff_tex_coords_idx / 4,
				mesh->buff_vert_indices_idx,
				mesh->bufF_verts_idx / 4,

				mesh->textures.size() > 0 ? mesh->textures[0]->bindless_idx : 0, // tex_albedo
				mesh->textures.size() > 0 ? 1 : 0, // has_albedo_tex
				0,
				0
			};

			int elem_cnt = mesh->indicesList.size();


			DrawArraysIndirectCommand cmd;
			cmd.count = elem_cnt;
			cmd.first = 0;


			((DrawArraysIndirectCommand*)(this->mdi_buff->cpu_data))[this->draw_cnt_running] = cmd;
			((MDIUniforms*)this->uniforms_buff->cpu_data)[this->draw_cnt_running] = uniforms;

			this->draw_cnt_running++;

		}


		// for (auto mesh : game_object->model_node->meshes) {
		// 	// auto indices_buff = mesh->buff_vert_indices;
		// 	// auto verts_buff = mesh->bufF_verts;
		// 	// auto texcoords_buff = mesh->buff_tex_coords;
		// 	// auto normals_buff = mesh->buff_normals;
		//
		// 	shader_p->setUniform("buff_normals_offs", mesh->buff_normals_idx / 2);
		// 	shader_p->setUniform("buff_tex_coords_offs", mesh->buff_tex_coords_idx / 4);
		// 	shader_p->setUniform("buff_vert_indices_offs", mesh->buff_vert_indices_idx);
		// 	shader_p->setUniform("buff_verts_offs", mesh->bufF_verts_idx / 4);
		//
		// 	if (mesh->textures.size() > 0) {
		// 		shader_p->setUniform("tex_albedo", mesh->textures[0]);
		// 		shader_p->setUniform("has_albedo_tex", true);
		// 	} else {
		// 		shader_p->setUniform("has_albedo_tex", false);
		// 	}
		// 	shader_p->setUniform("mat", game_object->matrix);
		// 	shader_p->setUniform("prev_mat", game_object->prev_matrix);
		//
		// 	// shader_p->setUniform("indices_buff", WEngine->scene_model->buff_vert_indices);
		// 	// shader_p->setUniform("verts_buff", WEngine->scene_model->buff_verts);
		// 	// shader_p->setUniform("texcoords_buff", WEngine->scene_model->buff_tex_coords);
		// 	// shader_p->setUniform("normals_buff", WEngine->scene_model->buff_normals);
		//
		// 	//
		//
		// 	shader_p->setUniform("has_texcoords", mesh->texCoordList.size() > 0);
		// 	// glMultiDrawArraysIndirectCount()
		// 	//
		// 	// int elem_cnt = indices_buff->elem_cnt / 3 ;
		// 	// int elem_cnt = indices_buff->elem_cnt;
		// 	// mesh->element_count
		// 	// this->elem_cnt = byte_len / 4; // TODO: useless
		// 	// int elem_cnt = indices_buff->elem_cnt;
		// 	int elem_cnt = mesh->indicesList.size();
		//
		// 	static Buffer* indirect_buff = new Buffer(BuffDesc{.byte_len = 1000, .immutable = false, .name = "MDI buff"});
		// 	static Buffer* indirect_param_buff = new Buffer(BuffDesc{.byte_len = 1000, .immutable = false, .name = "MDI Param buff"});
		//
		//
		// 	struct DrawArraysIndirectCommand{
		// 		uint32_t  count;
		// 		uint32_t  instanceCount = 1;
		// 		uint32_t  first = 0;
		// 		uint32_t  baseInstance = 0;
		// 	};
		//
		// 	DrawArraysIndirectCommand cmd;
		// 	cmd.count = elem_cnt;
		// 	cmd.first = 0;
		//
		// 	indirect_buff->upload_sub_data(&cmd,
		// 		4 * 4
		// 		// * 4
		// 		, 0
		// 	);
		// 	// indirect_buff->upload_sub_data(&cmd,
		// 	// 	4 * 4
		// 	// 	// * 4
		// 	// 	, 4 * 4
		// 	// );
		// 	// indirect_buff->upload_sub_data(&cmd,
		// 	// 	4 * 4
		// 	// 	// * 4
		// 	// 	, 4 * 4 * 2
		// 	// );
		// 	// indirect_buff->upload_sub_data(&cmd,
		// 	// 	4 * 4
		// 	// 	// * 4
		// 	// 	, 4 * 4 * 3
		// 	// );
		//
		//
		// 	int total_draws_count = 1;
		// 	indirect_param_buff->upload_sub_data( &total_draws_count, 4, 0);
		//
		// 	indirect_buff->bind_as_MDI_buff();
		// 	indirect_param_buff->bind_as_MDI_PARAM_buff();
		//
		// 	// glMultiDrawArraysIndirectBindlessNV
		// 	// elem_cnt = 16;
		// 	static int MDIIIIIIII = 0;
		// 	// WEngine->editor->add_gui_control("MDIIIIIIII", &MDIIIIIIII);
		//
		// 	if(MDIIIIIIII != 0) {
		// 		glMultiDrawArraysIndirect( GL_TRIANGLES, nullptr, 1, 0 );
		// 	} else {
		// 		glMultiDrawArraysIndirectCount(
		// 			GL_TRIANGLES, nullptr, 0, 10,0
		// 		);
		// 	}
		//
		// 	glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0);
		// 	glBindBuffer(GL_PARAMETER_BUFFER, 0);
		//
		// 	// glDrawArrays(GL_TRIANGLES, 0, elem_cnt);
		// }


	}

	void push(int elems) {
			// DrawArraysIndirectCommand cmd;
			// cmd.count = elems;
			// cmd.first = 0;
			//
			// this->mdi_param_buff->cpu_data
			//
	}

	void upload_verts() {
		this->mdi_param_buff->upload_sub_data(&this->draw_cnt_running, 4, 0);
		this->mdi_buff->upload_sub_data(this->mdi_buff->cpu_data, sizeof(DrawArraysIndirectCommand) * this->draw_cnt_running, 0);
		this->uniforms_buff->upload_sub_data(this->uniforms_buff->cpu_data, sizeof(MDIUniforms) * this->draw_cnt_running, 0);
	}

	void draw() {
		glMultiDrawArraysIndirectCount(
			// GL_TRIANGLES, nullptr, 0, this->max_draw_cnt,0
			GL_TRIANGLES, nullptr, 0, this->max_draw_cnt,0
		);
		// wlog_info("MDI draw");
		// std::cout << "MDI"
	}
	void reset() {
			draw_cnt_running = 0;
	}

	void flush_draw(bool flush = true) {
		upload_verts();
		draw();
		if(flush) {
			reset();
		}
	}


};
