#include "application_class.h"

#include "depth_shader_class.h"
#include "system_class.h"

application_class::application_class() : should_quit_(false)
{
	direct_3d_ = nullptr;
	camera_ = nullptr;
	model_ = nullptr;
	bitmap_ = nullptr;
	sprite_ = nullptr;
	timer_ = nullptr;
	mouse_strings_ = nullptr;
	font_shader_ = nullptr;
	font_ = nullptr;
	fps_ = nullptr;
	fps_string_ = nullptr;
	shader_manager_ = nullptr;
	render_count_string_ = nullptr;
	model_list_ = nullptr;
	position_ = nullptr;
	display_plane_ = nullptr;
	bath_model_ = nullptr;
	water_model_ = nullptr;
	m_light_ = nullptr;
	refraction_texture_ = nullptr;
	reflection_texture_ = nullptr;
	scene_texture_ = nullptr;
	physics_ = nullptr;
	lights_.clear();
	sun_light_ = nullptr;
	swap_chain_ = nullptr;
	ground_model_ = nullptr;
	wall_model_ = nullptr;
	hwnd_ = nullptr;
	base_view_matrix_ = XMMatrixIdentity();
	render_texture_ = nullptr;
	screen_width_ = 0;
	screen_height_ = 0;
	num_lights_ = 0;
	water_height_ = 0.0f;
	water_translation_ = 0.0f;
	true_light_position_ = XMFLOAT3(0.0f, 0.0f, 0.0f);
	light_model_ = nullptr;
	render_count_ = 0;
	tab_was_pressed_ = false;
	sound_system_ = nullptr;
	camera_input_ = nullptr;
}

application_class::~application_class()
{
	should_quit_ = true;
	culling_active_ = false;

	// Joindre les threads pour s'assurer qu'ils se terminent correctement
	if (physics_thread_.joinable())
	{
		physics_thread_.join();
	}
    
	if (culling_thread_.joinable())
	{
		culling_thread_.join();
	}

}


bool application_class::initialize(int screenWidth, int screenHeight, HWND hwnd, bool is_vulkan)
{

	LOG_INIT("Initializing application class");
	
	try 
	{
		char mouseString1[32], mouseString2[32], mouseString3[32];
		char modelFilename[128], renderString[32];
		char bitmapFilename[128];
		char spriteFilename[128];
		char fpsString[32];
		bool result;
		HRESULT Hresult;

		// create entity manager
		entity_manager_ = std::make_unique<ecs::EntityManager>();

		screen_width_ = screenWidth;
		screen_height_ = screenHeight;

		set_hwnd(hwnd);
		set_windowed(full_screen);
		set_screen_height(screenHeight);
		set_screen_width(screenWidth);

		// Create the Direct3D object.
		direct_3d_ = new d_3d_class;
		if (!direct_3d_)
		{
			LOG_ERROR("Could not create the Direct3D object");
			R_FALSE
		}

		result = direct_3d_->initialize(screen_width_, screen_height_, vsync_enabled_, hwnd, full_screen, screen_depth, screen_near);
		R_LOG_ERROR(result, "Could not initialize Direct3D")

		// Create the camera object.
		camera_ = new camera_class;
		if (!camera_)
		{
			LOG_ERROR("Could not create the camera object");
			R_FALSE
		}
		// Init the CameraInput structure
		camera_input_ = std::make_shared<CameraInput>();
		camera_->set_camera_inputs(camera_input_);

		sun_camera_ = new camera_class;
		if (!sun_camera_)
		{
			LOG_ERROR("Could not create the sun camera object");
			R_FALSE
		}

		sun_camera_->set_position(0.0f,0.0f,0.0f);
		sun_camera_->set_rotation(0.0f, 0.0f, 0.0f);
		sun_camera_->render();
		sun_camera_->get_view_matrix(base_view_matrix_);

		// Set the initial position of the camera.
		camera_->set_position(0.0f, 0.0f, -12.0f);
		camera_->set_rotation(0.0f, 0.0f, 0.0f);
		camera_->render();
		camera_->get_view_matrix(base_view_matrix_);

		active_camera_ = camera_;

		// Create and initialize the font shader object.
		font_shader_ = new font_shader_class;

		result = font_shader_->initialize(direct_3d_->get_device(), hwnd);
		R_LOG_ERROR(result , "Could not initialize the font shader object")

		// Create and initialize the font object.
		font_ = new font_class;

		result = font_->Initialize(direct_3d_->get_device(), direct_3d_->get_device_context(), 0);
		R_LOG_ERROR(result, "Could not initialize the font object")

		// Create and initialize the render to texture object.
		render_texture_ = new render_texture_class;

		result = render_texture_->Initialize(direct_3d_->get_device(), 256, 256, screen_depth, screen_near, 1);
		R_LOG_ERROR(result , "Could not initialize the render texture object")

		// Create and initialize the display plane object.
		display_plane_ = new display_plane_class;

		result = display_plane_->Initialize(direct_3d_->get_device(), 1.0f, 1.0f);
		R_LOG_ERROR(result, "Could not initialize the display plane object")
		
		// Set the sprite info file we will be using.
		strcpy_s(spriteFilename, "sprite_data_01.txt");

		// Create and initialize the sprite object.
		sprite_ = new sprite_class;

		result = sprite_->Initialize(direct_3d_->get_device(), direct_3d_->get_device_context(), screenWidth, screenHeight, spriteFilename, 50, 50);
		R_LOG_ERROR(result, "Could not initialize the sprite object")

		// Set the initial mouse strings.
		strcpy_s(mouseString1, "Mouse X: 0");
		strcpy_s(mouseString2, "Mouse Y: 0");
		strcpy_s(mouseString3, "Mouse Button: No");

		// Create and initialize the text objects for the mouse strings.
		mouse_strings_ = new text_class[3];

		for (int i = 0; i < 3; i++)
		{
			int y = 10 + (i * 25);
			result = mouse_strings_[i].Initialize(direct_3d_->get_device(), direct_3d_->get_device_context(), screenWidth, screenHeight, 32, font_, mouseString1, 10, y, 1.0f, 1.0f, 1.0f);
			R_LOG_ERROR(result , "Could not initialize the mouse string " + std::to_string(i))
		}

		// Set the file name of the bitmap file.
		strcpy_s(bitmapFilename, "assets/Texture/stone01.tga");

		// Create and initialize the bitmap object.
		bitmap_ = new bitmap_class;

		result = bitmap_->initialize(direct_3d_->get_device(), direct_3d_->get_device_context(), screenWidth, screenHeight, bitmapFilename, 50, 50);
		R_LOG_ERROR(result, "Could not initialize the bitmap object")

		// Set the file name of the model.
		strcpy_s(modelFilename, "assets/Model/TXT/cube.txt");

		// Charger les textures_
		std::vector<std::wstring> textureFilenames = {
			L"assets/Texture/stone01.png"
		};

		TextureContainer CubeTextures;

		for (const auto& textureFilename : textureFilenames)
		{
			ID3D11ShaderResourceView* texture = nullptr;
			Hresult = DirectX::CreateWICTextureFromFile(direct_3d_->get_device(), direct_3d_->get_device_context(), textureFilename.c_str(), nullptr, &texture);
			if (FAILED(Hresult))
			{
				LOG_ERROR("Failed to load texture: " + std::string(textureFilename.begin(), textureFilename.end()));
				R_FALSE
			}
			CubeTextures.diffuse.push_back(texture);
		}

		// Create and initialize the model object.
		model_ = new model_class;


		result = model_->Initialize(direct_3d_->get_device(), direct_3d_->get_device_context(), modelFilename, CubeTextures);
		R_LOG_ERROR(result, "Could not initialize the model object")

		// Create and initialize the light object.
		m_light_ = new light_class;

		m_light_->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f);
		m_light_->SetDirection(0.0f, 0.0f, -1.0f);
		m_light_->SetAmbientColor(0.15f, 0.15f, 0.15f, 1.0f);
		m_light_->SetSpecularColor(1.0f, 1.0f, 1.0f, 1.0f);
		m_light_->SetSpecularPower(16.0f);

		// Set the number of lights we will use.
		num_lights_ = 4;
		// Create and initialize the light objects array.
		lights_.resize(num_lights_);

		lights_[0] = new light_class;
		lights_[0]->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f);  // White
		lights_[0]->SetDirection(0.0f, 0.0f, -1.0f);
		lights_[0]->SetAmbientColor(0.15f, 0.15f, 0.15f, 1.0f);
		lights_[0]->SetSpecularColor(1.0f, 1.0f, 1.0f, 1.0f);
		lights_[0]->SetSpecularPower(16.0f);
		lights_[0]->SetPosition(10.0f, 7.0f, -5.0f);

		lights_[1] = new light_class;
		lights_[1]->SetDiffuseColor(1.0f, 0.0f, 0.0f, 1.0f);  // Red
		lights_[1]->SetDirection(0.0f, 0.0f, -1.0f);
		lights_[1]->SetAmbientColor(0.15f, 0.15f, 0.15f, 1.0f);
		lights_[1]->SetSpecularColor(1.0f, 0.0f, 0.0f, 1.0f);
		lights_[1]->SetSpecularPower(16.0f);
		lights_[1]->SetPosition(-10.0f, 7.0f, -5.0f);

		lights_[2] = new light_class;
		lights_[2]->SetDiffuseColor(0.0f, 1.0f, 0.0f, 1.0f);  // Green
		lights_[2]->SetDirection(0.0f, 0.0f, -1.0f);
		lights_[2]->SetAmbientColor(0.15f, 0.15f, 0.15f, 1.0f);
		lights_[2]->SetSpecularColor(0.0f, 1.0f, 0.0f, 1.0f);
		lights_[2]->SetSpecularPower(16.0f);
		lights_[2]->SetPosition(10.0f, 7.0f, 5.0f);

		lights_[3] = new light_class;
		lights_[3]->SetDiffuseColor(0.0f, 0.0f, 1.0f, 1.0f);  // Blue
		lights_[3]->SetDirection(0.0f, 0.0f, -1.0f);
		lights_[3]->SetAmbientColor(0.15f, 0.15f, 0.15f, 1.0f);
		lights_[3]->SetSpecularColor(0.0f, 0.0f, 1.0f, 1.0f);
		lights_[3]->SetSpecularPower(16.0f);
		lights_[3]->SetPosition(-10.0f, 7.0f, 5.0f);

		// ------------------------------------------------------------- //
		// ----------------------- || THE SUN || ----------------------- //
		// ------------------------------------------------------------- //

		sun_light_ = new light_class;
		sun_light_->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f);  // White
		sun_light_->SetDirection(0.0f, -1.0f, 0.0f);
		sun_light_->SetAmbientColor(0.15f, 0.15f, 0.15f, 1.0f);
		sun_light_->SetPosition(0.0f, 100.0f, 0.0f);
		sun_light_->SetIntensity(1.0f);

		sun_camera_->set_position(sun_light_->GetPosition().x, sun_light_->GetPosition().y, sun_light_->GetPosition().z);
		sun_camera_->set_rotation(0.0f, 0.0f, 0.0f);
		sun_camera_->render();

		// Create and initialize the normal map shader object.
		shader_manager_ = new shader_manager_class;

		result = shader_manager_->initialize(direct_3d_->get_device(), hwnd);
		R_LOG_ERROR(result, "Could not initialize the shader manager object")

		// Set the initial render count string.
		strcpy_s(renderString, "render Count: 0");

		// Create and initialize the text object for the render count string.
		render_count_string_ = new text_class;

		result = render_count_string_->Initialize(direct_3d_->get_device(), direct_3d_->get_device_context(), screenWidth, screenHeight, 32, font_, renderString, 10, 10, 1.0f, 1.0f, 1.0f);
		R_LOG_ERROR(result, "Could not initialize the render count string")

		// Create and initialize the model list object.
		model_list_ = new ModelListClass;
		model_list_->Initialize(25);

		// Charger les textures_ initiales pour bath_model_
		std::vector<std::wstring> bathTextures = {
			L"assets/Texture/marble01.png"
		};

		TextureContainer BathTextures;

		textures.clear();
		for (const auto& textureFilename : bathTextures)
		{
			ID3D11ShaderResourceView* texture = nullptr;
			Hresult = DirectX::CreateWICTextureFromFile(direct_3d_->get_device(), direct_3d_->get_device_context(), textureFilename.c_str(), nullptr, &texture);
			if (FAILED(Hresult))
			{
				LOG_ERROR("Failed to load texture: " + std::string(textureFilename.begin(), textureFilename.end()));
				R_FALSE
			}
			BathTextures.diffuse.push_back(texture);
		}

		// Set the file name of the bath model.
		strcpy_s(modelFilename, "assets/Model/TXT/bath.txt");

		// Create and initialize the bath model object.
		bath_model_ = new model_class;

		result = bath_model_->Initialize(direct_3d_->get_device(), direct_3d_->get_device_context(), modelFilename, BathTextures);
		R_LOG_ERROR(result, "Could not initialize the bath model object")

		// Set the file names of the water model.
		strcpy_s(modelFilename, "assets/Model/TXT/water.txt");
		// replace first element with the new filename
		std::vector<std::wstring> waterTextures = {
			L"assets/Texture/water01.png"
		};

		TextureContainer WaterTextures;

		textures.clear();
		for (const auto& textureFilename : waterTextures)
		{
			ID3D11ShaderResourceView* texture = nullptr;
			Hresult = DirectX::CreateWICTextureFromFile(direct_3d_->get_device(), direct_3d_->get_device_context(), textureFilename.c_str(), nullptr, &texture);
			if (FAILED(Hresult))
			{
				LOG_ERROR("Failed to load texture: " + std::string(textureFilename.begin(), textureFilename.end()));
				R_FALSE
			}
			WaterTextures.diffuse.push_back(texture);
		}

		// Create and initialize the water model object.
		water_model_ = new model_class;

		result = water_model_->Initialize(direct_3d_->get_device(), direct_3d_->get_device_context(), modelFilename, WaterTextures);
		R_LOG_ERROR(result, "Could not initialize the water model object")

		// Create and initialize the refraction render to texture object.
		refraction_texture_ = new render_texture_class;

		result = refraction_texture_->Initialize(direct_3d_->get_device(), screenWidth, screenHeight, screen_depth, screen_near, 1);
		R_LOG_ERROR(result, "Could not initialize the refraction render texture object")

		// Create and initialize the reflection render to texture object.
		reflection_texture_ = new render_texture_class;

		result = reflection_texture_->Initialize(direct_3d_->get_device(), screenWidth, screenHeight, screen_depth, screen_near, 1);
		R_LOG_ERROR(result, "Could not initialize the reflection render texture object")

		// Set the height of the water.
		water_height_ = -9.25f;

		// initialize the position of the water.
		water_translation_ = 100.0f;

		// Create and initialize the timer object.
		timer_ = new timer_class;

		result = timer_->Initialize();
		R_LOG_ERROR(result, "Could not initialize the timer object")

		// Create the position class object.
		position_ = new position_class;

		// Create and initialize the fps object.
		fps_ = new fps_class();

		fps_->Initialize();

		// Set the initial fps and fps string.
		previous_fps_ = -1;
		strcpy_s(fpsString, "Fps: 0");

		// Create and initialize the text object for the fps string.
		fps_string_ = new text_class;

		result = fps_string_->Initialize(direct_3d_->get_device(), direct_3d_->get_device_context(), screenWidth, screenHeight, 32, font_, fpsString, 10, 10, 0.0f, 1.0f, 0.0f);
		R_LOG_ERROR(result, "Could not initialize the fps string")

		shadow_map_ = new shadow_map();
		if (!shadow_map_->initialize(direct_3d_->get_device(), 2048, 2048))
		{
			LOG_ERROR("Could not initialize the shadow map object");
			R_FALSE
		}

		stats_ = new stats();
		if (!stats_->initialize(this))
		{
			LOG_ERROR("Could not initialize the stats object");
			R_FALSE
		}

		physics_ = new physics;

		physics_thread_ = std::thread(&application_class::physics_thread_function, this);

		culling_active_ = true;
		culling_thread_ = std::thread(&application_class::culling_thread_function, this);
		
		FMOD_RESULT FMOD_result = FMOD::System_Create(&sound_system_);
		std::string m_lastError;
		if (FMOD_result != FMOD_OK) {
			m_lastError = "Échec de la création du système FMOD: " + std::to_string(FMOD_result);
			LOG_ERROR(m_lastError);
			R_FALSE
		}
		
		FMOD_result = sound_system_->init(512, FMOD_INIT_NORMAL | FMOD_INIT_3D_RIGHTHANDED, nullptr);
		if (FMOD_result != FMOD_OK) {
			m_lastError = "Échec de l'initialisation du système FMOD: " + std::to_string(FMOD_result);
			LOG_ERROR(m_lastError);
			sound_system_->release();
			sound_system_ = nullptr;
			R_FALSE
		}
		
		entity_manager_->SetCamera(camera_);
		entity_manager_->SetSoundSystem(sound_system_);
		entity_manager_->SetDevice(direct_3d_->get_device());
		entity_manager_->SetContext(direct_3d_->get_device_context());

		create_skysphere();

		// create the render textures for the shadow map
		shadow_texture_ = new render_texture_class();
		result = shadow_texture_->Initialize(direct_3d_->get_device(), 2048, 2048, screen_depth, screen_near, 1);

		
		culling_sun_active_ = true;
		culling_sun_thread_ = std::thread(&application_class::sun_culling_thread_function, this);
		R_LOG_ERROR(result, "Could not initialize the shadow render texture object")

	}
	catch (const std::exception& e)
	{
		LOG_ERROR(std::string("Exception caught during initialization: ") + e.what() );
		R_FALSE
	}
	LOG_INIT("Application class initialized");



	R_TRUE
}

void application_class::shutdown()
{
	LOG_SHUTDOWN("Shutting down application class");

	// Release the shader manager object.
	if (shader_manager_)
	{
		LOG_SHUTDOWN("Releasing the shader manager object");

		shader_manager_->shutdown();
		delete shader_manager_;
		shader_manager_ = 0;

		LOG_SHUTDOWN("Shader manager object released");
	}

	// Release the reflection render texture object.
	if (reflection_texture_)
	{
		reflection_texture_->Shutdown();
		delete reflection_texture_;
		reflection_texture_ = 0;
	}

	// Release the refraction render texture object.
	if (refraction_texture_)
	{
		refraction_texture_->Shutdown();
		delete refraction_texture_;
		refraction_texture_ = 0;
	}

	// Release the water model object.
	if (water_model_)
	{
		water_model_->Shutdown();
		delete water_model_;
		water_model_ = 0;
	}

	// Release the bath model object.
	if (bath_model_)
	{
		bath_model_->Shutdown();
		delete bath_model_;
		bath_model_ = 0;
	}
	// Release the physics object.
	if (physics_)
	{
		delete physics_;
		physics_ = 0;
	}

	// Release the display plane object.
	if (display_plane_)
	{
		LOG_SHUTDOWN("Releasing the display plane object");

		display_plane_->Shutdown();
		delete display_plane_;
		display_plane_ = 0;

		LOG_SHUTDOWN("Display plane object released");
	}

	// Release the position object.
	if (position_)
	{
		LOG_SHUTDOWN("Releasing the position object");

		delete position_;
		position_ = 0;

		LOG_SHUTDOWN("Position object released");
	}

	// Release the model list object.
	if (model_list_)
	{
		LOG_SHUTDOWN("Releasing the model list object");

		model_list_->Shutdown();
		delete model_list_;
		model_list_ = 0;

		LOG_SHUTDOWN("Model list object released");
	}

	// Release the text objects for the render count string.
	if (render_count_string_)
	{
		LOG_SHUTDOWN("Releasing the render count string object");

		render_count_string_->Shutdown();
		delete render_count_string_;
		render_count_string_ = 0;

		LOG_SHUTDOWN("render count string object released");
	}

	// Release the text objects for the mouse strings.
	if (mouse_strings_)
	{
		LOG_SHUTDOWN("Releasing the mouse strings");

		mouse_strings_[0].Shutdown();
		mouse_strings_[1].Shutdown();
		mouse_strings_[2].Shutdown();

		delete[] mouse_strings_;
		mouse_strings_ = 0;

		LOG_SHUTDOWN("Mouse strings released");
	}

	// Release the text object for the fps string.
	if (fps_string_)
	{
		LOG_SHUTDOWN("Releasing the fps string object");

		fps_string_->Shutdown();
		delete fps_string_;
		fps_string_ = 0;

		LOG_SHUTDOWN("Fps string object released");
	}

	// Release the fps object.
	if (fps_)
	{
		LOG_SHUTDOWN("Releasing the fps object");

		delete fps_;
		fps_ = 0;

		LOG_SHUTDOWN("Fps object released");
	}

	// Release the font object.
	if (font_)
	{
		LOG_SHUTDOWN("Releasing the font object");

		font_->Shutdown();
		delete font_;
		font_ = 0;

		LOG_SHUTDOWN("Font object released");
	}

	// Release the font shader object.
	if (font_shader_)
	{
		LOG_SHUTDOWN("Releasing the font shader object");

		font_shader_->shutdown();
		delete font_shader_;
		font_shader_ = 0;

		LOG_SHUTDOWN("Font shader object released");
	}

	// Release the timer object.
	if (timer_)
	{
		LOG_SHUTDOWN("Releasing the timer object");

		delete timer_;
		timer_ = 0;

		LOG_SHUTDOWN("Timer object released");
	}

	// Release the sprite object.
	if (sprite_)
	{
		LOG_SHUTDOWN("Releasing the sprite object");

		sprite_->Shutdown();
		delete sprite_;
		sprite_ = 0;

		LOG_SHUTDOWN("Sprite object released");
	}

	for (auto light : lights_)
	{
		LOG_SHUTDOWN("Releasing the light object");
		if (light)
		{
			delete light;
			light = 0;
		}
		LOG_SHUTDOWN("Light object released");
	}

	// Release the light object.
	if (m_light_)
	{
		LOG_SHUTDOWN("Releasing the light object");
		delete m_light_;
		m_light_ = 0;
		LOG_SHUTDOWN("Light object released");
	}

	// Release the model object.
	if (model_)
	{
		LOG_SHUTDOWN("Releasing the model object");
		model_->Shutdown();
		delete model_;
		model_ = 0;
		LOG_SHUTDOWN("Model object released");
	}

	if (scene_texture_)
	{
		scene_texture_->Shutdown();
		delete scene_texture_;
		scene_texture_ = nullptr;
	}

	if (sun_camera_)
	{
		LOG_SHUTDOWN("Releasing the sun camera object");
		delete sun_camera_;
		sun_camera_ = nullptr;
		LOG_SHUTDOWN("Sun camera object released");
	}

	if (shadow_map_) {
		LOG_SHUTDOWN("Releasing the shadow map object");
		shadow_map_->shutdown();
		delete shadow_map_;
		shadow_map_ = nullptr;
		LOG_SHUTDOWN("Shadow map object released");
	}

	if (stats_) {
		LOG_SHUTDOWN("Releasing the stats object");
		delete stats_;
		stats_ = nullptr;
		LOG_SHUTDOWN("Stats object released");
	}

	if (shadow_texture_) {
		shadow_texture_->Shutdown();
		delete shadow_texture_;
		shadow_texture_ = nullptr;
	}

	LOG_SHUTDOWN("Application class shut down");
}

bool application_class::frame(input_class* Input)
{
	stats_->reset_draw_call_count();

	int mouseX, mouseY, currentMouseX, currentMouseY;
	bool result, leftMouseDown;
	static float textureTranslation = 0.0f;

	float frameTime;

	static int lastMouseX = 0, lastMouseY = 0;

	static float rotation = 360.0f;
	static float x = 0.0f;
	static float y = 3.0f;
	static float z = 0.0f;

	// Update the system stats.
	timer_->Frame();

	// Get the current frame time.
	frameTime = timer_->GetTime();

	// Check if the user pressed escape and wants to exit the application.
	if (Input->IsEscapePressed())
	{
		LOG("User pressed escape, exiting application",Logger::LogLevel::Input);
		should_quit_ = true;
	}

	// Get the location of the mouse from the input object,
	Input->GetMouseLocation(mouseX, mouseY);
	leftMouseDown = Input->IsLeftMousePressed();

	currentMouseX = mouseX;

	int deltaX = currentMouseX - lastMouseX;  // Calculate the mouse movement.
	lastMouseX = currentMouseX;  // Update the last mouse position for the next frame

	currentMouseY = mouseY;

	int deltaY = currentMouseY - lastMouseY;  // Calculate the mouse movement.
	lastMouseY = currentMouseY;  // Update the last mouse position for the next frame
	
	// New input processing
	camera_->update_camera_inputs_states(Input);
	camera_->move(frameTime, deltaX, deltaY);

	XMFLOAT3 dir = sun_light_->GetDirection();
	float pitch = asinf(-dir.y) * TO_DEG; // en degrés
	float yaw   = atan2f(dir.x, dir.z) * TO_DEG; // en degrés
	float roll  = 0.0f;

	if (Input->is_key_pressed(DIK_TAB)) {
		if (!tab_was_pressed_) {
			// Alterner la caméra active
			if (active_camera_ == camera_)
				active_camera_ = sun_camera_;
			else
				active_camera_ = camera_;
			tab_was_pressed_ = true;
		}
	} else {
		tab_was_pressed_ = false;
	}

	// Update the sun camera position and rotation based on the light position.
	sun_camera_->set_position(sun_light_->GetPosition().x, sun_light_->GetPosition().y, sun_light_->GetPosition().z);
	sun_camera_->set_rotation(pitch, yaw, roll);

	active_camera_->render();
	entity_manager_->UpdateEntities(frameTime);

	result = render_shadow_map();
	R_LOG_ERROR(result, "Could not render the shadow map")

	// render the static graphics scene.
	result = render(rotation, x, y, z, textureTranslation);
	R_LOG_ERROR(result, "Could not render the scene")

	// Update the frames per second each frame.
	result = update_fps();
	R_LOG_ERROR(result, "Could not update the fps")

	// Update the rotation variable each frame.
	rotation -= 0.0174532925f * speed_;
	if (rotation < 0.0f)
	{
		rotation += 360.0f;
	}

	// Update the position of the water to simulate motion.
	water_translation_ += 0.001f;
	if (water_translation_ > 1.0f)
	{
		water_translation_ -= 1.0f;
	}

	// render the refraction of the scene to a texture.
	result = render_refraction_to_texture();
	R_LOG_ERROR(result, "Could not render the refraction texture")

	// render the reflection of the scene to a texture.
	result = render_reflection_to_texture();
	R_LOG_ERROR(result, "Could not render the reflection texture")

	inputs_.key_left = Input->IsLeftArrowPressed();
	inputs_.key_right = Input->IsRightArrowPressed();
	inputs_.key_up = Input->IsUpArrowPressed();
	inputs_.key_down = Input->IsDownArrowPressed();

	// render the scene to a render texture.
	result = render_scene_to_texture(rotation);
	R_LOG_ERROR(result, "Could not render the scene to the texture")

	// Update the mouse strings each frame.
	result = update_mouse_strings(mouseX, mouseY, leftMouseDown);
	R_LOG_ERROR(result, "Could not update the mouse strings")

	// Update the sprite object using the frame time.
	sprite_->Update(frameTime);

	// Increment the texture translation.
	textureTranslation += 0.01f;
	if (textureTranslation > 1.0f)
	{
		textureTranslation -= 1.0f;
	}

	R_TRUE
}

bool application_class::render_refraction_to_texture()
{
	XMMATRIX worldMatrix, viewMatrix, projectionMatrix;
	XMFLOAT4 diffuseColor[4], lightPosition[4], ambientColor[4];
	XMFLOAT4 clipPlane;
	int i;
	bool result;

	// Setup a clipping plane based on the height of the water to clip everything above it.
	clipPlane = XMFLOAT4(0.0f, -1.0f, 0.0f, water_height_ + 0.1f);

	// Set the render target to be the refraction render to texture and clear it.
	refraction_texture_->SetRenderTarget(direct_3d_->get_device_context());
	refraction_texture_->ClearRenderTarget(direct_3d_->get_device_context(), 0.0f, 0.0f, 0.0f, 1.0f);

	// Generate the view matrix based on the camera's position.
	camera_->render();
	camera_->get_view_matrix(viewMatrix);
	projectionMatrix = direct_3d_->get_projection_matrix();

	// Get the light properties.
	for (i = 0; i < num_lights_; i++)
	{
		// Create the diffuse color array from the four light colors.
		diffuseColor[i] = lights_[i]->GetDiffuseColor();

		// Create the light position array from the four light positions.
		lightPosition[i] = lights_[i]->GetPosition();

		// Create the light position array from the four light positions.
		ambientColor[i] = lights_[i]->GetAmbientColor();
	}

	// Translate to where the bath model will be rendered.
	worldMatrix = XMMatrixTranslation(0.0f, -10.0f, 0.0f);

	// render the bath model using the refraction shader.
	bath_model_->Render(direct_3d_->get_device_context());

	result = shader_manager_->render_refraction_shader(direct_3d_->get_device_context(), bath_model_->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
		bath_model_->GetTexture(TextureType::Diffuse,0), lights_[0]->GetDirection(), ambientColor, diffuseColor, lightPosition, clipPlane);
	R_LOG_ERROR(result, "Could not render the bath model using the refraction shader")

	// Reset the render target back to the original back buffer and not the render to texture anymore.  And reset the viewport back to the original.
	direct_3d_->set_back_buffer_render_target();
	direct_3d_->reset_viewport();

	R_TRUE
}

bool application_class::render_reflection_to_texture()
{
	XMMATRIX reflectionViewMatrix;

	// Set the render target to be the reflection render to texture and clear it.
	reflection_texture_->SetRenderTarget(direct_3d_->get_device_context());
	reflection_texture_->ClearRenderTarget(direct_3d_->get_device_context(), 0.0f, 0.0f, 0.0f, 1.0f);

	// Use the camera to render the reflection and create a reflection view matrix.
	camera_->render_reflection(water_height_);

	// Get the camera reflection view matrix instead of the normal view matrix.
	camera_->get_reflection_view_matrix(reflectionViewMatrix);

	// Reset the render target back to the original back buffer and not the render to texture anymore.  And reset the viewport back to the original.
	direct_3d_->set_back_buffer_render_target();
	direct_3d_->reset_viewport();

	R_TRUE
}

bool application_class::render_shadow_map()
{
	XMMATRIX light_viewMatrix, light_projectionMatrix;
	bool result;

	shadow_texture_->SetRenderTarget(direct_3d_->get_device_context());
	shadow_texture_->ClearRenderTarget(direct_3d_->get_device_context(), 1.0f,1.0f, 1.0f, 1.0f);
	
	sun_camera_->render(); // Generate the view matrix based on the camera's position.
	sun_camera_->get_view_matrix(light_viewMatrix);
	shadow_texture_->GetProjectionMatrix(light_projectionMatrix);

	for (auto& entity : entity_manager_->GetAllEntities()) {

		// ignore skysphere entity
		if (entity == sky_entity_)
			continue;
		
		auto render_cpnt = entity->GetComponent<ecs::RenderComponent>();
		
		if (!render_cpnt) continue;

		auto transform_cpnt = entity->GetComponent<ecs::TransformComponent>();
		if (!transform_cpnt) continue;
		
		if (render_cpnt->IsShadowCaster())
		{

			// Prepare the world matrix.
			XMMATRIX tra, rot, scl;
			tra = transform_cpnt->GetTranslateMatrix();
			rot = transform_cpnt->GetRotateMatrix();
			scl = transform_cpnt->GetScaleMatrix();

			XMMATRIX worldMat = XMMatrixMultiply(
				XMMatrixMultiply(scl, rot),
				tra
				);
			
			
			// Render the geometry of the model
			render_cpnt->RenderOnlyGeometryFromSun(direct_3d_->get_device_context());
			// render using the depth shader to create the shadow map
			result = shader_manager_->render_depth_shader(
				direct_3d_->get_device_context(),
				render_cpnt->GetIndexCount(),
				worldMat,
				light_viewMatrix,
				light_projectionMatrix
				);

			R_LOG_ERROR(result, "Could not render the model using the depth shader")
		}
	}

	direct_3d_->set_back_buffer_render_target();
	direct_3d_->reset_viewport();

	R_TRUE;
	
}


bool application_class::render_scene_to_texture(float rotation)
{
	XMMATRIX worldMatrix, viewMatrix, projectionMatrix;
	bool result;

	// Set the render target to be the render texture and clear it.
	render_texture_->SetRenderTarget(direct_3d_->get_device_context());
	render_texture_->ClearRenderTarget(direct_3d_->get_device_context(), 0.0f, 0.5f, 1.0f, 1.0f);

	// Set the position of the camera for viewing the cube.
	camera_->render();

	camera_->get_view_matrix(viewMatrix);
	render_texture_->GetProjectionMatrix(projectionMatrix);

	// Rotate the world matrix by the rotation value so that the cube will spin.
	worldMatrix = XMMatrixRotationY(rotation);

	// render the model using the texture shader.
	model_->Render(direct_3d_->get_device_context());

	result = shader_manager_->render_texture_shader(direct_3d_->get_device_context(), model_->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
		model_->GetTexture(TextureType::Diffuse,0));
	R_LOG_ERROR(result, "Could not render the model using the texture shader")

	// Reset the render target back to the original back buffer and not the render to texture anymore.  And reset the viewport back to the original.
	direct_3d_->set_back_buffer_render_target();
	direct_3d_->reset_viewport();

	R_TRUE
}

bool application_class::render(float rotation, float x, float y, float z, float textureTranslation)
{
	XMMATRIX worldMatrix, viewMatrix, orthoMatrix, projectionMatrix, rotateMatrix, translateMatrix, scaleMatrix, srMatrix, reflectionMatrix;
	XMFLOAT4 diffuseColor[4], lightPosition[4], ambientColor[4];
	int i;
	bool result;

	// Generate the view matrix based on the camera's position.
	active_camera_->render();

	// Get the world, view, and projection matrices from the camera and d3d objects.
	active_camera_->get_view_matrix(viewMatrix);
	projectionMatrix = direct_3d_->get_projection_matrix();
	orthoMatrix = direct_3d_->get_ortho_matrix();

	// Get the light properties.
	for (i = 0; i < num_lights_; i++)
	{
		// Create the diffuse color array from the four light colors.
		diffuseColor[i] = lights_[i]->GetDiffuseColor();

		// Create the light position array from the four light positions.
		lightPosition[i] = lights_[i]->GetPosition();

		// Create the light position array from the four light positions.
		ambientColor[i] = lights_[i]->GetPosition();
	}

	scaleMatrix = XMMatrixScaling(0.5f, 0.5f, 0.5f);  // Build the scaling matrix.
	rotateMatrix = XMMatrixRotationY(rotation);  // Build the rotation matrix.
	translateMatrix = XMMatrixTranslation(x, y, z);  // Build the translation matrix.

	// Multiply the scale, rotation, and translation matrices together to create the final world transformation matrix.
	srMatrix = XMMatrixMultiply(scaleMatrix, rotateMatrix);
	worldMatrix = XMMatrixMultiply(srMatrix, translateMatrix);

	// Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
	model_->Render(direct_3d_->get_device_context());

	// render the model using the light shader.
	result = shader_manager_->renderlight_shader(direct_3d_->get_device_context(), model_->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, model_->GetTexture(TextureType::Diffuse,0),
		diffuseColor, lightPosition, ambientColor);
	R_LOG_ERROR(result, "Could not render the model using the light shader")

	update_skybox_position(); // Update the position of the skybox to match the camera position.

	direct_3d_->turn_z_buffer_on(); // Enable the Z buffer after rendering the skybox.

	// -------------------------------------------------------- //
	// ------------ render the object in the queue ------------ //
	// -------------------------------------------------------- //
	
	result = render_pass(diffuseColor, lightPosition, ambientColor, viewMatrix, projectionMatrix);
	R_LOG_ERROR(result, "Could not perform the render pass")

	// Update the render count text.
	result = update_render_count_string(get_render_count());
	R_LOG_ERROR(result, "Could not update the render count string")


	// Translate to where the bath model will be rendered.
	worldMatrix = XMMatrixTranslation(0.0f, -10.0f, 0.0f);

	// Put the bath model vertex and index buffers on the graphics pipeline to prepare them for drawing.
	bath_model_->Render(direct_3d_->get_device_context());

	// render the bath model using the light shader.
	result = shader_manager_->renderlight_shader(direct_3d_->get_device_context(), bath_model_->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
		bath_model_->GetTexture(TextureType::Diffuse,0), diffuseColor, lightPosition, ambientColor);
	R_LOG_ERROR(result, "Could not render the bath model using the light shader")

	// Get the camera reflection view matrix.
	camera_->get_reflection_view_matrix(reflectionMatrix);

	// Translate to where the water model will be rendered.
	worldMatrix = XMMatrixTranslation(0.0f, water_height_, 0.0f);

	// Put the water model vertex and index buffers on the graphics pipeline to prepare them for drawing.
	water_model_->Render(direct_3d_->get_device_context());

	// render the water model using the water shader.
	result = shader_manager_->render_water_shader(direct_3d_->get_device_context(), water_model_->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, reflectionMatrix,
		reflection_texture_->GetShaderResourceView(), refraction_texture_->GetShaderResourceView(), water_model_->GetTexture(TextureType::Diffuse,0),
		water_translation_, 0.01f);
	R_LOG_ERROR(result, "Could not render the water model using the water shader")

	// Disable the Z buffer and enable alpha blending for 2D rendering.
	direct_3d_->turn_z_buffer_off();
	direct_3d_->enable_alpha_blending();

	// Reset the world matrix.
	worldMatrix = direct_3d_->get_world_matrix();

	// render the render count text string using the font shader.
	render_count_string_->Render(direct_3d_->get_device_context());

	result = font_shader_->render(direct_3d_->get_device_context(), render_count_string_->GetIndexCount(), worldMatrix, base_view_matrix_, orthoMatrix,
		font_->GetTexture(), render_count_string_->GetPixelColor());
	R_LOG_ERROR(result, "Could not render the render count text string using the font shader")

	// render the fps text string using the font shader.
	fps_string_->Render(direct_3d_->get_device_context());

	result = font_shader_->render(direct_3d_->get_device_context(), fps_string_->GetIndexCount(), worldMatrix, base_view_matrix_, orthoMatrix,
		font_->GetTexture(), fps_string_->GetPixelColor());
	R_LOG_ERROR(result, "Could not render the fps text string using the font shader")

	// render the mouse text strings using the font shader.
	for (i = 0; i < 3; i++)
	{
		mouse_strings_[i].Render(direct_3d_->get_device_context());

		result = font_shader_->render(direct_3d_->get_device_context(), mouse_strings_[i].GetIndexCount(), worldMatrix, base_view_matrix_, orthoMatrix,
			font_->GetTexture(), mouse_strings_[i].GetPixelColor());
		R_LOG_ERROR(result, "Could not render the mouse text string using the font shader")
	}

	// Put the sprite vertex and index buffers on the graphics pipeline to prepare them for drawing.
	result = sprite_->Render(direct_3d_->get_device_context());
	R_LOG_ERROR(result, "Could not render the sprite")

	// render the sprite with the texture shader.
	result = shader_manager_->render_texture_shader(direct_3d_->get_device_context(), model_->GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix,
		sprite_->GetTexture());
	R_LOG_ERROR(result, "Could not render the sprite using the texture shader")

	// Turn off alpha blending after rendering the 2D text.
	direct_3d_->disable_alpha_blending();

	// turn on the Z buffer
	direct_3d_->turn_z_buffer_on();

	// Get the light properties.
	for (i = 0; i < num_lights_; i++)
	{
		// Create the diffuse color array from the four light colors.
		diffuseColor[i] = lights_[i]->GetDiffuseColor();

		// Create the light position array from the four light positions.
		lightPosition[i] = lights_[i]->GetPosition();
	}
	// Réinitialiser la cible de rendu au back buffer.
	direct_3d_->set_back_buffer_render_target();

	R_TRUE
}

d_3d_class* application_class::get_direct_3d()
{
	return direct_3d_;
}

int application_class::get_screen_width() const
{
	return GetSystemMetrics(SM_CXSCREEN);
}

int application_class::get_screen_height() const
{
	return GetSystemMetrics(SM_CYSCREEN);
}

void application_class::generate_terrain()
{
    LOG_INFO("Génération du terrain avec ECS");

	// delete previous terrain if it exists
	delete_terrain();
	
    // Dimensions du terrain
    float scaleX = 10.0f;
    float scaleY = 1.0f;
    float scaleZ = 10.0f;
    int gridSizeX = 20;
    int gridSizeZ = 20;

    // Vérifier si le modèle existe déjà dans le cache
    std::string modelName = "assets/Model/OBJ/plane.obj";
    std::shared_ptr<model_class> sharedModel;

    auto it = g_model_cache.find(modelName);
    if (it != g_model_cache.end()) {
        // Utiliser le modèle existant du cache
        LOG_INFO("Using cached model for terrain: " + modelName);
        sharedModel = it->second;
    }
    else {
        // Créer un conteneur de textures partagé
        TextureContainer textureContainer;
        textureContainer.diffusePaths.push_back(L"assets/Texture/Bricks2K.png");
        textureContainer.normalPaths.push_back(L"assets/Texture/BricksNRM2K.png");
        textureContainer.specularPaths.push_back(L"assets/Texture/BricksGLOSS2K.png");

        // Créer et initialiser le modèle si non trouvé
        char modelFilename[128];
        strcpy_s(modelFilename, modelName.c_str());

        auto newModel = std::make_shared<model_class>();
        newModel->PreloadTextures(direct_3d_->get_device(), direct_3d_->get_device_context(), textureContainer);

        if (!newModel->Initialize(direct_3d_->get_device(), direct_3d_->get_device_context(), modelFilename, textureContainer)) {
            LOG_ERROR("Impossible d'initialiser le modèle du terrain");
            return;
        }

        // Ajouter le modèle au cache
        g_model_cache[modelName] = newModel;
        sharedModel = newModel;
        LOG_INFO("Added terrain model to cache: " + modelName);
    }

    // Vérifier si l'entity manager est disponible
    if (entity_manager_) {
        // Générer les tuiles de terrain avec le système ECS
        for (int i = 0; i < gridSizeX; i++) {
            for (int j = 0; j < gridSizeZ; j++) {
                // Créer une entité
                auto entity = CREATE_ENTITY

                // Ajouter un composant d'identité
                auto identity = entity->AddComponent<ecs::IdentityComponent>(object_id_++);
                identity->SetName("TerrainTile_" + std::to_string(i) + "_" + std::to_string(j));
                identity->SetType(ecs::ObjectType::Terrain);

                // Ajouter un composant de transformation
                auto transform = entity->AddComponent<ecs::TransformComponent>();
                transform->SetPosition(XMVectorSet(i * scaleX, -12.0f, j * scaleZ, 0.0f));
                transform->SetScale(XMVectorSet(scaleX, scaleY, scaleZ, 0.0f));
                transform->UpdateWorldMatrix();

                // Ajouter un composant de rendu
                auto render = entity->AddComponent<ecs::RenderComponent>();
                render->InitializeWithModel(sharedModel);

                // Ajouter un composant de shader
                auto shader = entity->AddComponent<ecs::ShaderComponent>();
                shader->SetActiveShader(SHD_SUN);
            }
        }
    }

    update_stats_after_modification();

    int totalTiles = gridSizeX * gridSizeZ;
    LOG_INFO("Terrain généré avec " + std::to_string(totalTiles) + " tuiles");
}

void application_class::add_kobject(std::wstring& filepath)
{
	std::lock_guard<std::mutex> lock(objects_mutex_);
	LOG_INFO("Adding object");

	char modelFilename[128];
	TextureContainer KobjectsTextures;
	filesystem::path p(filepath);
	string filename = p.stem().string();

	size_t convertedChars = 0;
	(void)wcstombs_s(&convertedChars, modelFilename, sizeof(modelFilename), filepath.c_str(), _TRUNCATE);

	filesystem::current_path(w_folder_);

	// Liste des fichiers de texture
	std::vector<std::wstring> kobjTexture = {
		L"assets/Texture/Bricks2K.png",
		L"assets/Texture/BricksNRM2K.png",
		L"assets/Texture/BricksGLOSS2K.png"
	};

	// Configurer les chemins des textures dans le conteneur
	KobjectsTextures.diffusePaths.push_back(kobjTexture[0]);
	if (kobjTexture.size() > 1) KobjectsTextures.normalPaths.push_back(kobjTexture[1]);
	if (kobjTexture.size() > 2) KobjectsTextures.specularPaths.push_back(kobjTexture[2]);

	// Vérifier si le modèle existe déjà dans le cache
	std::string modelKey = std::string(modelFilename);
	std::shared_ptr<model_class> sharedModel;

	auto it = g_model_cache.find(modelKey);
	if (it != g_model_cache.end()) {
		// Utiliser le modèle existant du cache
		LOG_INFO("Using cached model for " + modelKey);
		sharedModel = it->second;
	}
	else {
		// Créer un nouveau modèle
		sharedModel = std::make_shared<model_class>();

		// Précharger les textures
		sharedModel->PreloadTextures(direct_3d_->get_device(), direct_3d_->get_device_context(), KobjectsTextures);

		// Initialiser le modèle
		if (!sharedModel->Initialize(direct_3d_->get_device(), direct_3d_->get_device_context(), modelFilename, KobjectsTextures)) {
			LOG_ERROR("Failed to initialize model for object: " + modelKey);
			return;
		}

		// Ajouter le modèle au cache
		g_model_cache[modelKey] = sharedModel;
		LOG_INFO("Added model to cache: " + modelKey);
	}

	// Créer une nouvelle entité
	auto entity = CREATE_ENTITY

	// Ajouter un composant d'identité
	auto identity = entity->AddComponent<ecs::IdentityComponent>(object_id_++);
	identity->SetName(filename);
	identity->SetType(ecs::ObjectType::Unknown);

	// Ajouter un composant de transformation
	auto transform = entity->AddComponent<ecs::TransformComponent>();
	transform->SetPosition(XMVectorSet(0.0f, 50.0f, 0.0f, 0.0f));
	transform->SetScale(XMVectorSet(1.0f, 1.0f, 1.0f, 0.0f));
	transform->UpdateWorldMatrix();

	// Ajouter un composant de rendu avec le modèle partagé
	auto render = entity->AddComponent<ecs::RenderComponent>();
	render->InitializeWithModel(sharedModel);

	// Ajouter un composant de shader
	auto shader = entity->AddComponent<ecs::ShaderComponent>();
	shader->SetActiveShader(SHD_LIGHT);

	// Stocker le chemin du modèle
	auto modelPath = entity->AddComponent<ecs::ModelPathComponent>();
	modelPath->SetPath(filepath);

	LOG_INFO("ECS entity created with ID: " + std::to_string(identity->GetId()));

	update_stats_after_modification();
}

void application_class::add_cube()
{
	std::lock_guard<std::mutex> lock(objects_mutex_);
	LOG_INFO("Adding cube");

	std::string model_name = "assets/Model/TXT/cube.txt";
	std::shared_ptr<model_class> sharedModel;

	auto it = g_model_cache.find(model_name);
	if (it != g_model_cache.end())
	{
		LOG_INFO("Using cached model: " + model_name);
		sharedModel = it->second;
	}
	else
	{
		TextureContainer cube_textures;
		cube_textures.diffusePaths.push_back(L"assets/Texture/Bricks2K.png");
		cube_textures.normalPaths.push_back(L"assets/Texture/BricksNRM2K.png");
		cube_textures.specularPaths.push_back(L"assets/Texture/BricksGLOSS2K.png");

		char model_filename[128];
		strcpy_s(model_filename, model_name.c_str());

		auto newModel = std::make_shared<model_class>();
		newModel->PreloadTextures(direct_3d_->get_device(), direct_3d_->get_device_context(), cube_textures);
		if (!newModel->Initialize(direct_3d_->get_device(), direct_3d_->get_device_context(), model_filename, cube_textures)) {
			LOG_ERROR("Failed to initialize cube model");
			return;
		}

		g_model_cache[model_name] = newModel;
		sharedModel = newModel;
		LOG_INFO("Added cube model to cache: " + model_name);


		if (entity_manager_)
		{
			auto entity = CREATE_ENTITY

			auto identity = entity->AddComponent<ecs::IdentityComponent>(object_id_++);
			identity->SetName("Cube");
			identity->SetType(ecs::ObjectType::Cube);

			auto transform = entity->AddComponent<ecs::TransformComponent>();
			transform->SetPosition(XMVectorSet(0.0f, 10.0f, 0.0f, 0.0f));
			transform->SetScale(XMVectorSet(1.0f, 1.0f, 1.0f, 0.0f));
			transform->UpdateWorldMatrix();

			auto render = entity->AddComponent<ecs::RenderComponent>();
			render->InitializeWithModel(sharedModel);
			
			auto shader = entity->AddComponent<ecs::ShaderComponent>();
			shader->SetActiveShader(SHD_TEX);
		}
	}

	update_stats_after_modification();
	LOG_INFO("Cube added successfully");

}

void application_class::delete_entity_by_id(int entity_id)
{
	std::lock_guard<std::mutex> lock(objects_mutex_);
	LOG_INFO("Deleting entity with ID: " + std::to_string(entity_id));

	if (entity_manager_) {
		// Rechercher l'entité avec l'ID spécifié via le composant IdentityComponent
		auto entities_with_identity = entity_manager_->GetEntitiesWithComponent<ecs::IdentityComponent>();
		for (auto& entity : entities_with_identity) {
			auto identity = entity->GetComponent<ecs::IdentityComponent>();
			if (identity && identity->GetId() == entity_id) {
				// Supprimer l'entité
				entity_manager_->DestroyEntity(entity->GetID());
				LOG_INFO("Entity with ID " + std::to_string(entity_id) + " successfully deleted");
				break;
			}
		}
	}

	update_stats_after_modification();
}

void application_class::delete_terrain()
{
	std::lock_guard<std::mutex> lock(objects_mutex_);
	LOG_INFO("Deleting terrain");

	// Get all entities with the Terrain type
	auto entities_with_terrain = entity_manager_->GetEntitiesWithComponent<ecs::IdentityComponent>();
	for (auto& entity : entities_with_terrain) {
		auto identity = entity->GetComponent<ecs::IdentityComponent>();
		if (identity && identity->GetType() == ecs::ObjectType::Terrain) {
			// Destroy the entity
			entity_manager_->DestroyEntity(entity->GetID());
			LOG_INFO("Terrain entity with ID " + std::to_string(identity->GetId()) + " successfully deleted");
		}
	}
	
	update_stats_after_modification();
}

bool application_class::update_mouse_strings(int mouseX, int mouseY, bool mouseDown)
{
	char tempString[16], finalString[32];
	bool result;


	// Convert the mouse X integer to string format.
	sprintf_s(tempString, "%d", mouseX);

	// Setup the mouse X string.
	strcpy_s(finalString, "Mouse X: ");
	strcat_s(finalString, tempString);

	// Update the sentence vertex buffer with the new string information.
	result = mouse_strings_[0].UpdateText(direct_3d_->get_device_context(), font_, finalString, 10, 50, 1.0f, 1.0f, 1.0f);
	R_LOG_ERROR(result, "Could not update the mouse X string")

	// Convert the mouse Y integer to string format.
	sprintf_s(tempString, "%d", mouseY);

	// Setup the mouse Y string.
	strcpy_s(finalString, "Mouse Y: ");
	strcat_s(finalString, tempString);

	// Update the sentence vertex buffer with the new string information.
	result = mouse_strings_[1].UpdateText(direct_3d_->get_device_context(), font_, finalString, 10, 75, 1.0f, 1.0f, 1.0f);
	R_LOG_ERROR(result, "Could not update the mouse Y string")

	// Setup the mouse button string.
	if (mouseDown)
	{
		strcpy_s(finalString, "Mouse Button: Yes");
	}
	else
	{
		strcpy_s(finalString, "Mouse Button: No");
	}

	// Update the sentence vertex buffer with the new string information.
	result = mouse_strings_[2].UpdateText(direct_3d_->get_device_context(), font_, finalString, 10, 100, 1.0f, 1.0f, 1.0f);
	R_LOG_ERROR(result, "Could not update the mouse button string")

	R_TRUE
}

bool application_class::update_fps()
{
	int fps;
	char tempString[16], finalString[16];
	float red, green, blue;
	bool result;


	// Update the fps each frame.
	fps_->Frame();

	stats_->update_display_stats(); // Update the stats display with the latest information from fps class.

	// Get the current fps.
	fps = fps_->GetFps();

	// Check if the fps from the previous frame was the same, if so don't need to update the text string.
	if (previous_fps_ == fps)
	{
		R_TRUE
	}

	// Store the fps for checking next frame.
	previous_fps_ = fps;

	// Truncate the fps to below 100,000.
	if (fps > 99999)
	{
		fps = 99999;
	}

	// Convert the fps integer to string format.
	sprintf_s(tempString, "%d", fps);

	// Setup the fps string.
	strcpy_s(finalString, "Fps: ");
	strcat_s(finalString, tempString);

	// If fps is 60 or above set the fps color to green.
	if (fps >= 60)
	{
		red = 0.0f;
		green = 1.0f;
		blue = 0.0f;
	}

	// If fps is below 60 set the fps color to yellow.
	if (fps < 60)
	{
		red = 1.0f;
		green = 1.0f;
		blue = 0.0f;
	}

	// If fps is below 30 set the fps color to red.
	if (fps < 30)
	{
		red = 1.0f;
		green = 0.0f;
		blue = 0.0f;
	}

	// Update the sentence vertex buffer with the new string information.
	result = fps_string_->UpdateText(direct_3d_->get_device_context(), font_, finalString, 10, 10, red, green, blue);
	R_LOG_ERROR(result, "Could not update the fps string")

	R_TRUE
}

bool application_class::update_render_count_string(int renderCount)
{
	char tempString[16], finalString[32];
	bool result;


	// Convert the render count integer to string format.
	sprintf_s(tempString, "%d", renderCount);

	// Setup the render count string.
	strcpy_s(finalString, "render Count: ");
	strcat_s(finalString, tempString);

	// Update the sentence vertex buffer with the new string information.
	result = render_count_string_->UpdateText(direct_3d_->get_device_context(), font_, finalString, 10, 30, 1.0f, 1.0f, 1.0f);
	R_LOG_ERROR(result, "Could not update the render count string")

	R_TRUE
}

XMVECTOR application_class::get_light_color(int index)
{
	//convert to XMVECTOR
	XMVECTOR lightColor = XMVectorSet(lights_[index]->GetDiffuseColor().x, lights_[index]->GetDiffuseColor().y, lights_[index]->GetDiffuseColor().z, 1.0f);

	return lightColor;
}

XMVECTOR application_class::get_light_position(int index)
{
	//convert to XMVECTOR
	XMVECTOR lightPosition = XMVectorSet(lights_[index]->GetPosition().x, lights_[index]->GetPosition().y, lights_[index]->GetPosition().z, 1.0f);

	return lightPosition;
}

void application_class::set_light_color(int index, XMVECTOR color)
{
	//convert to XMFLOAT4
	XMFLOAT4 lightColor;
	XMStoreFloat4(&lightColor, color);

	//set the color
	lights_[index]->SetDiffuseColor(lightColor.x, lightColor.y, lightColor.z, 1.0f);
}

void application_class::set_light_position(int index, XMVECTOR position)
{
	//convert to XMFLOAT4
	XMFLOAT4 lightPosition;
	XMStoreFloat4(&lightPosition, position);

	//set the position
	lights_[index]->SetPosition(lightPosition.x, lightPosition.y, lightPosition.z);
}

void application_class::set_screen_height(int height)
{
	// log the new screen height
	LOG_INFO("Setting screen height to " + std::to_string(height));
	screen_height_ = height;
}

void application_class::set_screen_width(int width)
{
	// log the new screen width
	LOG_INFO("Setting screen width to " + std::to_string(width));
	screen_width_ = width;
}

void application_class::sun_culling_thread_function()
{
	LOG_INFO("Sun culling thread started");

	XMMATRIX viewMatrix = XMMATRIX();
	XMMATRIX projectionMatrix = XMMATRIX();
	shadow_texture_->GetProjectionMatrix(projectionMatrix);

	while (culling_sun_active_)
	{

		construct_frustum(
			sun_culling_,
			screen_depth,
			projectionMatrix,
			sun_camera_->get_view_matrix(viewMatrix)
			);

		auto all_entity = entity_manager_->GetAllEntities();

		for (auto& entity : all_entity)
		{
			auto render = entity->GetComponent<ecs::RenderComponent>();

			// skip if no render component
			if (!render) continue;
			// set visible to false if not a shadow caster
			if (!render->IsShadowCaster()) render->set_is_sun_visible(false);

			auto transform = entity->GetComponent<ecs::TransformComponent>();
			if (!transform) continue;
			XMVECTOR position = transform->GetPosition();
			float x = XMVectorGetX(position);
			float y = XMVectorGetY(position);
			float z = XMVectorGetZ(position);
			XMVECTOR scale = transform->GetScale();
			float radius = max(max(XMVectorGetX(scale), XMVectorGetY(scale)), XMVectorGetZ(scale));
			bool visible = sun_culling_.CheckCube(x, y, z, radius, get_frustum_tolerance());
			render->set_is_sun_visible(visible);
		}

		// Pause pour éviter de surcharger le CPU ~ 30 Hz
		std::this_thread::sleep_for(std::chrono::milliseconds(33));
	}
}


void application_class::culling_thread_function()
{
    LOG_INFO("Thread de culling démarré");

    while (culling_active_)
    {
        // Construction du frustum une fois par cycle de culling
        construct_frustum(
        	frustum_culling_,
        	screen_depth,
        	direct_3d_->get_projection_matrix(),
        	active_camera_->get_view_matrix(base_view_matrix_)
        	);

        // Traitement des files d'objets normaux (sans la skybox)
        auto all_entity = entity_manager_->GetAllEntities();

    	for (auto& entity : all_entity)
		{

    		if (entity->GetID() == sky_id_)
    		{
    			// skipped the skybox entity to avoid unnecessary calculations since it's always visible
    			continue;
    		}
    		
    		std::lock_guard<std::mutex> lock(objects_mutex_);
    		auto renderComponent = entity->GetComponent<ecs::RenderComponent>();
    		if (renderComponent && renderComponent->GetModel())
			{
				// Extraction des données de position via le composant TransformComponent
    			auto transformComponent = entity->GetComponent<ecs::TransformComponent>();
    			if (transformComponent)
    			{
    				XMVECTOR transformPosition = transformComponent->GetPosition();
    				float x = XMVectorGetX(transformPosition);
    				float y = XMVectorGetY(transformPosition);
    				float z = XMVectorGetZ(transformPosition);

    				// Calcul du rayon approximatif
    				XMVECTOR scale = transformComponent->GetScale();
    				float radius = max(max(XMVectorGetX(scale), XMVectorGetY(scale)), XMVectorGetZ(scale));

    				// verification du frustum
    				bool visible = frustum_culling_.CheckCube(x, y, z, radius, get_frustum_tolerance());
    				renderComponent->SetVisible(visible);
    			}
    								
			}
		}

        // Pause pour éviter de surcharger le CPU
        std::this_thread::sleep_for(std::chrono::milliseconds(16)); // ~60 Hz
    }

    LOG_INFO("Thread de culling terminé");
}



bool application_class::render_pass(XMFLOAT4* diffuse, XMFLOAT4* position, XMFLOAT4* ambient, XMMATRIX view, XMMATRIX projection)
{
	std::lock_guard<std::mutex> lock(objects_mutex_);

	int renderCount = 0;

	// Rendu des entités du système ECS s'il est disponible
	if (entity_manager_) {
		// Créer le système de rendu pour les entités
		ecs::RenderSystem renderSystem(direct_3d_->get_device_context(), shader_manager_);

		// Données pour le système de rendu
		XMFLOAT4 sunlightDiffuse = sun_light_->GetDiffuseColor();
		XMFLOAT4 sunlightAmbient = sun_light_->GetAmbientColor();
		XMFLOAT3 sunlightDirection = sun_light_->GetDirection();
		float sunlightIntensity = sun_light_->GetIntensity();

		// Effectuer le rendu de toutes les entités
		int entitiesRendered = renderSystem.RenderAllEntities(
			entity_manager_.get(),
			view,
			projection,
			diffuse,
			position,
			ambient,
			camera_->get_position(),
			sunlightDiffuse,
			sunlightAmbient,
			sunlightDirection,
			sunlightIntensity
		);

		renderCount += entitiesRendered;
	}

	set_render_count(renderCount);

	R_TRUE
}

void application_class::construct_frustum(frustum& frustum_culling, float screen_depth, XMMATRIX projectionMatrix, XMMATRIX viewMatrix )
{
	frustum_culling.ConstructFrustum(screen_depth, projectionMatrix, viewMatrix);
}

// Update the position of the skybox based on the camera position
void application_class::update_skybox_position()
{

	if (sky_id_ == -1) return;

	if (!sky_entity_) return;

	auto transformComponent = sky_entity_->GetComponent<ecs::TransformComponent>();
	if (!transformComponent) return;

	// convert the camera position to XMVECTOR manually
	XMVECTOR cameraPosition = XMVectorSet(camera_->get_position().x, camera_->get_position().y, camera_->get_position().z, 0.0f);
	transformComponent->SetPosition(cameraPosition);
	transformComponent->UpdateWorldMatrix();
}

bool application_class::render_physics(float delta_time) {

	// update the physical entity if they have the physics component
	auto entities_with_physics = entity_manager_->GetEntitiesWithComponent<ecs::PhysicsComponent>();
	for (auto& entity : entities_with_physics) {
		auto physicsComponent = entity->GetComponent<ecs::PhysicsComponent>();
		if (physicsComponent) {
			// Update the physics component with the input keys and delta time
			physicsComponent->Update(delta_time);
		}
	}

	R_TRUE
}

void application_class::physics_thread_function()
{
	auto fixedUpdateInterval = std::chrono::milliseconds(1000 / physics_tick_rate_);
	auto lastTime = std::chrono::steady_clock::now();

	while (!should_quit_)
	{
		auto now = std::chrono::steady_clock::now();
		if (now - lastTime >= fixedUpdateInterval)
		{
			lastTime = now;

			float deltaTime = 1.0f / static_cast<float>(physics_tick_rate_);
			bool result = render_physics(deltaTime);
			if (!result)
			{
				LOG_ERROR("Failed to update physics");
			}
		}
		// Attendre un peu pour éviter de surcharger le CPU
		std::this_thread::sleep_for(std::chrono::milliseconds(1));
	}
}

bool application_class::create_big_cube(int side_count)
{
    std::string modelName = "assets/Model/TXT/cube.txt";
    std::shared_ptr<model_class> sharedModel;

    // Vérifier si le modèle existe déjà
    auto it = g_model_cache.find(modelName);
    if (it != g_model_cache.end()) {
        sharedModel = it->second;
    } else {
        // copy le string en char*
        char model_file[128];
        size_t convertedChars = 0;
        (void)wcstombs_s(&convertedChars, model_file, sizeof(model_file), std::wstring(modelName.begin(), modelName.end()).c_str(), _TRUNCATE);

        // Créer et initialiser le modèle si non trouvé
        auto newModel = std::make_shared<model_class>();
        TextureContainer textures;
        textures.diffusePaths.push_back(L"assets/Texture/Bricks2K.png");
        textures.normalPaths.push_back(L"assets/Texture/BricksNRM2K.png");
        textures.specularPaths.push_back(L"assets/Texture/BricksGLOSS2K.png");
        newModel->PreloadTextures(direct_3d_->get_device(), direct_3d_->get_device_context(), textures);
        if (!newModel->Initialize(direct_3d_->get_device(), direct_3d_->get_device_context(),model_file, textures)) {
            LOG_ERROR("Impossible d'initialiser le modèle du gros cube");
            R_FALSE
        }
        g_model_cache[modelName] = newModel;
        sharedModel = newModel;
    }

	for (int x = 0; x < side_count; x++) {
		for (int y = 0; y < side_count; y++) {
			for (int z = 0; z < side_count; z++) {
				// Créer une entité
				auto entity = CREATE_ENTITY

				// Ajouter un composant d'identité
				auto identity = entity->AddComponent<ecs::IdentityComponent>(object_id_++);
				identity->SetName("CubePart_" + std::to_string(x) + "_" + std::to_string(y) + "_" + std::to_string(z));
				identity->SetType(ecs::ObjectType::Terrain);

				// Ajouter un composant de transformation
				auto transform = entity->AddComponent<ecs::TransformComponent>();
				transform->SetPosition(XMVectorSet(static_cast<float>(x), static_cast<float>(y), static_cast<float>(z), 0.0f));
				transform->SetScale(XMVectorSet(1.0f, 1.0f, 1.0f, 0.0f));
				transform->UpdateWorldMatrix();

				// Ajouter un composant de rendu
				auto render = entity->AddComponent<ecs::RenderComponent>();
				render->InitializeWithModel(sharedModel);

				// Ajouter un composant de shader
				auto shader = entity->AddComponent<ecs::ShaderComponent>();
				shader->SetActiveShader(SHD_LIGHT);
			}
		}
	}

    update_stats_after_modification();

    R_TRUE
}

void application_class::update_stats_after_modification()
{
	if (stats_)
	{
		stats_-> update_geometric_stats();
	}
}

int application_class::get_terrain_entity_count()
{
	std::lock_guard<std::mutex> lock(objects_mutex_);
	int terrainCount = 0;

	// Get all entities with the Terrain type
	auto entities_with_terrain = entity_manager_->GetEntitiesWithComponent<ecs::IdentityComponent>();
	for (auto& entity : entities_with_terrain) {
		auto identity = entity->GetComponent<ecs::IdentityComponent>();
		if (identity && identity->GetType() == ecs::ObjectType::Terrain) {
			terrainCount++;
		}
	}

	return terrainCount;
}

bool application_class::create_skysphere()
{

	char modelFilename[128];
	TextureContainer textures;

	size_t convertedChars = 0;
	(void)wcstombs_s(&convertedChars, modelFilename, sizeof(modelFilename), L"assets/Model/OBJ/skysphere.obj", _TRUNCATE);
	

	std::vector<std::wstring> sky_textures = {
		L"assets/Skybox/skybox.png"
	};

	textures.diffusePaths.push_back(sky_textures[0]);
	if (sky_textures.size() > 1) textures.normalPaths.push_back(sky_textures[1]);
	if (sky_textures.size() > 2) textures.specularPaths.push_back(sky_textures[2]);

	std::string modelKey = std::string(modelFilename);
	std::shared_ptr<model_class> sharedModel;

	auto it = g_model_cache.find(modelKey);
	if (it != g_model_cache.end())
	{
		LOG_INFO("Using cached model for sky sphere: " + modelKey);
		sharedModel = it->second;
	}
	else
	{
		sharedModel = std::make_shared<model_class>();
		sharedModel->PreloadTextures(direct_3d_->get_device(), direct_3d_->get_device_context(), textures);
		if (!sharedModel->Initialize(direct_3d_->get_device(), direct_3d_->get_device_context(), modelFilename, textures))
		{
			LOG_ERROR("Failed to initialize model for sky sphere");
			R_FALSE
		}
		g_model_cache[modelKey] = sharedModel;
		LOG_INFO("Added sky sphere model to cache: " + modelKey);
	}
	
	
	sky_entity_ = CREATE_ENTITY
	if (!sky_entity_)
	{
		LOG_ERROR("Failed to create sky entity");
		R_FALSE
	}
	
	sky_id_ = sky_entity_->GetID();
	entity_manager_->SetSkyID(sky_id_);
	
	auto identity = sky_entity_->AddComponent<ecs::IdentityComponent>(object_id_++);
	identity->SetName("SkySphere");
	identity->SetType(ecs::ObjectType::Unknown);
	
	auto transform = sky_entity_->AddComponent<ecs::TransformComponent>();
	transform->SetPosition(XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f));
	transform->SetScale(XMVectorSet(2.0f, 2.0f, 2.0f, 0.0f));
	transform->UpdateWorldMatrix();
	
	auto render = sky_entity_->AddComponent<ecs::RenderComponent>();
	render->InitializeWithModel(sharedModel);

	auto modelpath = sky_entity_->AddComponent<ecs::ModelPathComponent>();
	modelpath->SetPath(L"assets/Model/OBJ/skysphere.obj");

	auto shader = sky_entity_->AddComponent<ecs::ShaderComponent>();
	shader->SetActiveShader(SHD_SKYBOX);
	
	R_TRUE
}

bool application_class::update_screen_depth(float new_screen_depth)
{
	CHECK_2_CONDITION_INDEPENDENT_RETURN(direct_3d_,new_screen_depth <= screen_near);
	screen_depth = new_screen_depth;
	direct_3d_->set_projection_params(screen_width_, screen_height_, screen_depth, screen_near);
	R_TRUE
}

bool application_class::update_screen_near(float new_screen_near)
{
	CHECK_2_CONDITION_INDEPENDENT_RETURN(direct_3d_,new_screen_near <= 0.0f || new_screen_near >= screen_depth);
	screen_near = new_screen_near;
	direct_3d_->set_projection_params(screen_width_, screen_height_, screen_depth, screen_near);
	R_TRUE
}