#include "pch.h"
#include "WEntity.h"

#include "gl/ShaderProgram.h"

#include "Physics.h"

#include "Engine.h"
#include "engine/model/Mesh.h"
#include "engine/model/Model.h"


PhysicsCreationInfo::PhysicsCreationInfo() {
	physics_enabled = true;
	// collision_shape = std::make_shared<JPH::SphereShape>(0.5f);
	// collision_shape = new JPH::SphereShape(0.5f);
	motion_type = JPH::EMotionType::Dynamic;
	object_layer = Layers::MOVING;
	// allowed_dofs = JPH::EAllowedDOFs::All;
	allowed_dofs = JPH::EAllowedDOFs::None;
	allowed_dofs |= JPH::EAllowedDOFs::TranslationX;
	allowed_dofs |= JPH::EAllowedDOFs::TranslationY;
	allowed_dofs |= JPH::EAllowedDOFs::TranslationZ;
	// allowed_dofs |= JPH::EAllowedDOFs::TranslationZ;
	position = JPH::RVec3::sZero();
}

PhysicsCreationInfo PhysicsCreationInfo::set_2d_dof() {
	// allowed_dofs = JPH::EAllowedDOFs::Plane2D;
	// allowed_dofs = JPH::EAllowedDOFs::Plane2D;
	locked_pos.z = true;
	locked_pos.x = false;
	locked_pos.y = false;
	return *this;
}

PhysicsCreationInfo PhysicsCreationInfo::set_physics_enabled(bool _physics_enabled) {
	this->physics_enabled = _physics_enabled;
	return *this;
}

PhysicsCreationInfo PhysicsCreationInfo::set_static() {
	this->motion_type = JPH::EMotionType::Static;
	this->object_layer = Layers::NON_MOVING;
	return *this;
}

PhysicsCreationInfo PhysicsCreationInfo::set_position(JPH::RVec3 pos) {
	this->position = pos;
	return *this;
}

PhysicsCreationInfo PhysicsCreationInfo::set_mass(float mass) {
	this->mass = mass;
	return *this;
}

PhysicsCreationInfo PhysicsCreationInfo::set_gravity_factor(float gravity_factor) {
	this->gravity_factor = gravity_factor;
	return *this;
}

PhysicsCreationInfo PhysicsCreationInfo::remove_col_shape() {
	this->collision_shape = nullptr;
	return *this;
}

PhysicsCreationInfo PhysicsCreationInfo::set_col_shape(JPH::Shape* col_shape) {
	this->collision_shape = col_shape;
	return *this;
}

WCollision::WCollision() {
	me = nullptr;
	other = nullptr;
}

WCollision::WCollision(WEntity* me, WEntity* other, JPH::CollisionEstimationResult collision_res, glm::vec3 pos):
	me(me), other(other), collision_res(collision_res), pos(pos) {}

glm::vec3 WEntity::get_pos() {
	if (this->physics_active) {
		this->position = glm::from_jph(WEngine->physics.body_interface->GetPosition(this->jph_body_id));
		return this->position;
	} else {
		return this->position;
	}
}

glm::quat WEntity::get_rot() {
	if (this->physics_active) {
		this->rotation = glm::from_jph(WEngine->physics.body_interface->GetRotation(this->jph_body_id));
		return this->rotation;
	} else {
		return this->rotation;
	}
}

void WEntity::set_rot(glm::quat rot) {
	this->rotation = rot;
	if (this->physics_active) {
		WEngine->physics.body_interface->SetRotation(this->jph_body_id, glm::to_jph(this->rotation),JPH::EActivation::Activate);
	}
}

// glm::quat WEntity::get_scale() {
// 	if (this->physics_active) {
// 		this->rotation = glm::from_jph(WEngine->physics.body_interface->GetRotation(this->jph_body_id));
// 		return this->rotation;
// 	} else {
// 		return this->rotation;
// 	}
// }

void WEntity::set_scale(glm::vec3 _scale) {
	this->scale = _scale;
	// this->jph_body.
	this->collision_shape->Release();
	this->collision_shape = JPH::ScaledShapeSettings(this->creation_collision_shape.value(), glm::to_jph(scale)).Create().Get();
	if(this->creation_collision_shape.has_value()) {
		WEngine->physics.body_interface->SetShape(
			this->jph_body_id,
			this->collision_shape,
				true,
				JPH::EActivation::Activate
			);
	}
	if (this->physics_active) {

		// if(model_node->collision_shape)
		// WEngine->physics.body_interface->SetRotation(this->jph_body_id, glm::to_jph(this->rotation),JPH::EActivation::Activate);
	}
}


glm::vec3 WEntity::get_vel() {
	JPH::Vec3 vel = WEngine->physics.body_interface->GetLinearVelocity(this->jph_body_id);
	return glm::from_jph(vel);
}

void WEntity::set_vel(glm::vec3 vel) {
	WEngine->physics.body_interface->SetLinearVelocity(this->jph_body_id, glm::to_jph(vel));
}

void WEntity::set_pos(glm::vec3 pos) {
	this->position = pos;
	if (this->physics_active) {
		WEngine->physics.body_interface->SetPosition(this->jph_body_id, glm::to_jph(pos), JPH::EActivation::Activate);
	}
}

void WEntity::init_game_object(
	std::optional<PhysicsCreationInfo> physics_creation_info
) {
	// if(!physics_creation_info.has_value()) {
	// 	physics_creation_info = PhysicsCreationInfo();
	// }
	// physics_creation_info->collision_shape.Du
	
	memset(&this->props, 0, sizeof(ObjProps));
	
	game_obj_id = WEngine->gam_obj_id_cnt++;

	if (physics_creation_info.has_value() && physics_creation_info.value().physics_enabled) {
		this->physics_active = true;
		auto activation_mode = JPH::EActivation::Activate;
		if (physics_creation_info->object_layer == Layers::NON_MOVING) {
			// physics_creation_info
			activation_mode = JPH::EActivation::DontActivate;


		}


		if (!physics_creation_info->collision_shape) {
			physics_creation_info->collision_shape = new JPH::SphereShape(0.5f);
		}
		this->creation_collision_shape = physics_creation_info.value().collision_shape;
		auto pp = (new JPH::ScaledShapeSettings(
				physics_creation_info.value().collision_shape,
				glm::to_jph(this->scale)
			)
		)->Create().Get().GetPtr();
		this->collision_shape = pp;
		// this->collision_shape = physics_creation_info.value().collision_shape;
		// scaled_shape->TransformShape()
		JPH::BodyCreationSettings _bcs(
			// this->collision_shape,
			this->collision_shape,
			// physics_creation_info.position,
			// JPH::Quat::sIdentity(),

			// new JPH::BoxShape(JPH::Vec3(100.0f, 1.0f, 100.0f),0),
			// JPH::RVec3(0,-1,0),
			physics_creation_info.value().position,
			// JPH::Quat::sIdentity(),
			glm::to_jph(physics_creation_info.value().rotation),
			// JPH::Quat::sIdentity(),
			physics_creation_info.value().motion_type,
			physics_creation_info.value().object_layer
		);
		JPH::EAllowedDOFs allowed_dofs = JPH::EAllowedDOFs::None;
		if (!physics_creation_info.value().locked_pos.x) {
			allowed_dofs |= JPH::EAllowedDOFs::TranslationX;
		}
		if (!physics_creation_info.value().locked_pos.y) {
			allowed_dofs |= JPH::EAllowedDOFs::TranslationY;
		}
		if (!physics_creation_info.value().locked_pos.z) {
			allowed_dofs |= JPH::EAllowedDOFs::TranslationZ;
		}
		if (!physics_creation_info.value().locked_rot.x) {
			allowed_dofs |= JPH::EAllowedDOFs::RotationX;
		}
		if (!physics_creation_info.value().locked_rot.y) {
			allowed_dofs |= JPH::EAllowedDOFs::RotationY;
		}
		if (!physics_creation_info.value().locked_rot.z) {
			allowed_dofs |= JPH::EAllowedDOFs::RotationZ;
		}
		physics_creation_info.value().allowed_dofs = allowed_dofs;

		_bcs.mAllowedDOFs = allowed_dofs;
		// bcs.mAllowedDOFs |= ;
		_bcs.mMassPropertiesOverride = JPH::MassProperties();
		_bcs.mMassPropertiesOverride.mMass = physics_creation_info.value().mass;
		_bcs.mOverrideMassProperties = JPH::EOverrideMassProperties::CalculateInertia;
		_bcs.mGravityFactor = physics_creation_info.value().gravity_factor;
		_bcs.mMotionQuality = JPH::EMotionQuality::LinearCast;


		this->bcs = _bcs;
		// jph_body_id = WEngine->physics.body_interface->CreateAndAddBody(bcs, activation_mode);
		this->jph_body = WEngine->physics.body_interface->CreateBody(_bcs);
		// this->jph_body.

		this->jph_body_id = this->jph_body->GetID();
		WEngine->physics.body_interface->AddBody(this->jph_body_id, JPH::EActivation::Activate);
		// WEngine->physics.body_interface->AddBody(this->jph_body_id, JPH::EActivation::Activate);
		// WEngine->physics.body_interface.Body

		// jph_body_id = WEngine->physics.body_interface->CreateConstraint();

		this->set_pos(this->position);
		WEngine->body_to_game_object[jph_body_id] = this;
	}
	WEngine->entitties[game_obj_id] = this;

	// this->render_function = [this](float a, float b, float c, float d, float e, float f) {
	// 	if (this->model_node) {
	// 		this->shader_prog->use();
	// 		this->matrix = glm::mat4(
	// 			1, 0, 0, 0,
	// 			0, 1, 0, 0,
	// 			0, 0, 1, 0,
	// 			0, 0, 0, 1
	// 		);
	// 		this->matrix = glm::scale(this->matrix, this->scale);
	// 		this->matrix = glm::translate(this->matrix, this->get_pos());
	// 		for (auto& mesh : this->model_node->mesh_outlines) {
	// 			Buffer* buff = mesh->buff_vert_indices;
	// 			this->shader_prog->setUniform("mat", this->matrix);
	// 			this->shader_prog->setUniform("verts", buff);
	// 			this->shader_prog->setUniform("element_count", int(buff->elem_cnt / 4));
	// 			this->shader_prog->dispatch_compute(int(mesh->element_count), 1, 1);
	// 		}
	// 	}
	// };

	this->render_function = [](WEntity* game_object,float a, float b, float c, float d, float e, float f) {
		ShaderProgram* shader_p = game_object->shader_prog;
		if(!shader_p) {
			return;
		}
		if (f > 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) {
			// 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, .subdata_disabled = false, .name = "MDI buff"});
			static Buffer* indirect_param_buff = new Buffer(BuffDesc{.byte_len = 1000, .subdata_disabled = 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);
		}
	};

	this->on_collision = [](WCollision col) {};
}

void WEntity::recalc_matrices() {
	this->prev_matrix = this->matrix;
	this->matrix = glm::mat4(
		1, 0, 0, 0,
		0, 1, 0, 0,
		0, 0, 1, 0,
		0, 0, 0, 1
	);

	this->matrix = glm::translate(this->matrix, this->get_pos());
	this->matrix = glm::scale(this->matrix, this->scale);

	glm::quat rot = this->get_rot();
	this->matrix = glm::rotate(this->matrix, glm::angle(rot), glm::axis(rot));
	this->rot_matrix = glm::mat4(rot);
}

WEntity::WEntity(
	ModelNode* model_node,
	ShaderProgram* shader_prog,
	std::optional<PhysicsCreationInfo> physics_creation_info
): model_node(model_node), shader_prog(shader_prog) {
	JPH::Shape* col_shape = this->model_node->collision_shape.GetPtr();

	// if(model_node->name.find("Icos") == 0){
	// if (model_node->name.starts_with("_Icos")) {
	// 	std::cout << "icosa";
	// }
	// WEngine->physics.body_interface->AddBody(collision_shape.GetID(), JPH::EActivation::DontActivate);
	// WEngine->physics.body_interface->Add;
	if (col_shape == nullptr) {
		// physics_creation_info->remove_col_shape();
		if (!physics_creation_info.has_value()) {
			physics_creation_info = PhysicsCreationInfo();
		}
		physics_creation_info->set_physics_enabled(false);
		// std::cout << "mong" << std::endl;
		wlog_info("Create entity nocol: {}", this->game_obj_id);
	} else {
		wlog_info("Create entity col: {}", this->game_obj_id);
		bool there_is_input_creation_info = true;
		if (!physics_creation_info.has_value()) {
			physics_creation_info = PhysicsCreationInfo();
			there_is_input_creation_info = false;
		}
		if (model_node->is_dynamic_physics_object == false) {
			physics_creation_info->set_static();
		}
		physics_creation_info.value().locked_rot = model_node->rot_locked;
		if (!there_is_input_creation_info || !physics_creation_info.value().collision_shape) {
			physics_creation_info.value().collision_shape = col_shape;
		} else {
			if (!physics_creation_info->collision_shape) {
				physics_creation_info->collision_shape = new JPH::SphereShape(0.5f);
			}
			this->collision_shape = physics_creation_info.value().collision_shape;
		}
	}
	this->scale = model_node->scale;
	this->rotation = model_node->rotation;
	this->position = model_node->position;
	this->init_game_object(physics_creation_info);
}

ModelNode* model_node = nullptr;
ShaderProgram* shader_prog = nullptr;
std::function<void(float, float, float, float, float, float)> render_function;

void WEntity::draw(float arg_a, float arg_b, float arg_c, float arg_d, float arg_e, float arg_f) {
	render_function(this, arg_a, arg_b, arg_c, arg_d, arg_e, arg_f);
}

void WEntity::destroy() {
	WEngine->objects_to_destroy.push_back(this);
}

WEntity::WEntity(
	ShaderProgram* shader_prog,
	std::optional<PhysicsCreationInfo> physics_creation_info
): shader_prog(shader_prog) {
	this->init_game_object(physics_creation_info);
}

WEntity::WEntity(
	std::optional<PhysicsCreationInfo> physics_creation_info
): shader_prog(shader_prog) {
	this->init_game_object(physics_creation_info);
	this->render_function = [](WEntity* game_object,float arg_a, float arg_b, float arg_c, float arg_d, float arg_e, float arg_f) {};
}
