#ifndef _APPLICATIONCLASS_H_
#define _APPLICATIONCLASS_H_


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d_3d_class.h"
#include "camera_class.h"
#include "light_class.h"

#include "Fmod/core/inc/fmod.hpp"

#include "bitmap_class.h"
#include "sprite_class.h"
#include "timer_class.h"
#include "font_shader_class.h"
#include "font_class.h"
#include "text_class.h"
#include "fps_class.h"
#include "input_class.h"
#include "shader_manager_class.h"
#include "modellistclass.h"
#include "position_class.h"
#include "frustumclass.h"
#include "render_texture_class.h"
#include "display_plane_class.h"
#include "translate_shader_class.h"
#include "reflection_shader_class.h"
#include "physics.h"
#include "frustum.h"
#include "skybox.h"
#include "shadow_map.h"
#include "stats.h"

#include "ecs/components/identity_component.h"
#include "ecs/components/render_component.h"
#include "ecs/components/transform_component.h"
#include "ecs/components/physics_component.h"
#include "ecs/components/shader_component.h"
#include "ecs/systems/render_system.h"
#include "ecs/components/model_path_component.h"

#include <fstream>
#include <WICTextureLoader.h>
#include <comdef.h> // Pour _com_error
#include <chrono>
#include <thread>
#include <map>
#include <algorithm>
#include <DirectXMath.h>
#include <mutex>
#include <memory>
#include <vector>
#include <filesystem>

#include "ecs/entity_manager.h"


/////////////
// GLOBALS //
/////////////
constexpr bool full_screen = false;
constexpr float screen_depth = 1000.0f;
constexpr float screen_near = 0.3f;

static std::map<std::string, std::shared_ptr<model_class>> g_model_cache;

struct input
{
	bool key_left = false;
	bool key_right = false;
	bool key_up = false;
	bool key_down = false;
};

////////////////////////////////////////////////////////////////////////////////
// Class name: application_class
////////////////////////////////////////////////////////////////////////////////
class application_class
{
public:
	/**
	 * @brief Constructor for the application class.
	 * Initializes member variables and sets up the application.
	 */
	application_class();
	~application_class();
	virtual d_3d_class* get_direct_3d();
	void set_direct_3d(d_3d_class* direct_3d) { direct_3d_ = direct_3d; };

	/**
	 * Get the scene texture, which is used for rendering the scene.
	 * @return Pointer to the scene texture.
	 */
	render_texture_class* get_scene_texture() const { return scene_texture_; };
	/**
	 * Get the display plane used for rendering.
	 * @return Pointer to the display plane.
	 */
	render_texture_class* get_render_texture() const { return render_texture_; };
	/**
	 * Get the refraction texture used for rendering water effects.
	 * @return Pointer to the refraction texture.
	 */
	render_texture_class* get_refraction_texture() const { return refraction_texture_; };
	/**
	 * Get the reflection texture used for rendering reflections.
	 * @return Pointer to the reflection texture.
	 */
	render_texture_class* get_reflection_texture() const { return reflection_texture_; };

	/** 
	 * @brief Create a big cube with a specified number of little cube per sides.
	 * @param side_count The number of cubes per sides for the big cube.
	 * @return True if the cube was created successfully, false otherwise.
	 */
	bool create_big_cube(int side_count);
	/**
	 * initialize the application with the given parameters.
	 * 
	 * @param screen_width The width of the screen.
	 * @param screen_height The height of the screen.
	 * @param hwdn The handle to the window.
	 * @param is_vulkan Whether to use Vulkan for rendering.
	 * @return True if the initialization was successful, false otherwise.
	 */
	virtual bool initialize(int screen_width, int screen_height, HWND hwdn, bool is_vulkan);
	/**
	 * Shutdown the application and release resources.
	 */
	void shutdown();
	/**
	 * @brief Run the main loop of the application.
	 * This function will handle the main loop, including rendering and updating the application.
	 * @return True if the application should continue running, false if it should quit.
	 */
	virtual bool frame(input_class*);
	/** 
	 * @brief Thread function for handling physics updates.
	 * this function will run in a separate thread to handle physics updates at a fixed rate (50 fps by default).
	 */
	void physics_thread_function();
	/**
	 * Get the physics tick rate.
	 * @return The physics tick rate in frames per second as an integer.
	 */
	int get_physics_tick_rate() const { return physics_tick_rate_; };
	/**
	 * Set the physics tick rate.
	 * @param physics_tick_rate The new physics tick rate in frames per second.
	 */
	void set_physics_tick_rate(int physics_tick_rate) { physics_tick_rate_ = physics_tick_rate; };

	/**
	 * Get the screen width.
	 * @return The width of the screen in pixels as an integer.
	 */ 
	int get_screen_width() const;
	/**
	 * Set the screen width.
	 * @param screen_width The new width of the screen in pixels as an integer.
	 */
	void set_screen_width(int screen_width);
	/**
	 * Get the screen height.
	 * @return The height of the screen in pixels as an integer.
	 */
	int get_screen_height() const;
	/**
	 * Set the screen height.
	 * @param screen_height The new height of the screen in pixels as an integer.
	 */
	void set_screen_height(int screen_height);

	/**
	 * Get the speed value.
	 * An old value used for the demo spinning object.
	 * This value is not used in the current implementation.
	 * @return The speed value as a float.
	 */
	float get_speed() const { return speed_; };
	/**
	 * Set the speed value.
	 * An old value used for the demo spinning object.
	 * This value is not used in the current implementation.
	 * @param speed The new speed value as a float.
	 */
	void set_speed(const float speed) { this->speed_ = speed; };

	/**
	 * Add a basic cube to the scene.
	 */
	void add_cube();
	/**
	 * Delete an entity by its ID.
	 * @param entity_id The ID of the entity to delete.
	 */
	void delete_entity_by_id(int entity_id);
	/**
	 * Create a new entity with the specified model path.
	 * @param filepath 
	 */
	void add_kobject(std::wstring& filepath);
	/**
	 * Set the executable path of the engine.
	 * @param path 
	 */
	void set_path(WCHAR* path) { path_ = path; };
	/**
	 * Set the working folder of the engine.
	 */
	void set_w_folder(const std::filesystem::path& w_folder) { w_folder_ = w_folder; };
	/**
	 * Get the working folder of the engine.
	 * @return The working folder as a std::filesystem::path.
	 */
	std::filesystem::path get_w_folder() const { return w_folder_; };
	/**
	 * Get the number of entities with the ObjectType set as Terrain in the scene.
	 * @return The count of terrain entities as an integer.
	 */
	int get_terrain_entity_count();

	/**
	 * Get the object ID.
	 * @return The object ID as an integer.
	 */
	int get_object_id() const { return object_id_; };
	/**
	 * Set the object ID.
	 * @param object_id The new object ID as an integer.
	 */
	void set_object_id(int object_id) { object_id_ = object_id; };

	/**
	 * Generate a flat terrain for test purposes using plane entities.
	 */
	void generate_terrain();
	/**
	 * Delete the flat terrain/Big Cubes from the scene.
	 */
	void delete_terrain();

	/**
	 * Get the position for a specific light index.
	 * @param index 
	 * @return The position of the light as an XMVECTOR.
	 */
	XMVECTOR get_light_position(int index);
	/**
	 * Get the color for a specific light index.
	 * @param index 
	 * @return The color of the light as an XMVECTOR.
	 */
	XMVECTOR get_light_color(int index);
	/**
	 * Set the position for a specific light index.
	 * @param index The index of the light to set the position for.
	 * @param position The new position as an XMVECTOR.
	 */
	void set_light_position(int index, XMVECTOR position);
	/**
	 * Set the color for a specific light index.
	 * @param index The index of the light to set the color for.
	 * @param color The new color as an XMVECTOR.
	 */
	void set_light_color(int index, XMVECTOR color);
	/**
	 * Get all the lights in the scene.
	 * @return A vector of pointers to light_class objects representing the lights in the scene.
	 */
	std::vector<light_class*> get_lights() const { return lights_; };
	/**
	 * Get the sun light in the scene.
	 * @return The sun light as a pointer to a light_class object.
	 */
	light_class* get_sun_light() const { return sun_light_; };

	/**
	 * Get the should_quit flag.
	 * This flag indicates whether the application should quit.
	 * @return The should_quit flag as a boolean.
	 */
	bool get_should_quit() const { return should_quit_; };
	/**
	 * Set the should_quit flag.
	 * This flag indicates whether the application should quit.
	 * @param should_quit The new value for the should_quit flag as a boolean.
	 */
	void set_should_quit(const bool should_quit) { should_quit_ = should_quit; };

	/**
	 * Set the cel shading mode.
	 * This function isn't used in the current implementation.
	 * It's an old function that was used for cel shading effects before the complete implementation of the cel shading shader.
	 * @param enable 
	 */
	void set_cel_shading(const bool enable) { enable_cel_shading_ = enable; };

	/**
	 * Set the V-sync state.
	 * @param vsync 
	 */
	void set_vsync(bool vsync) { 
		vsync_enabled_ = vsync; 
		if (direct_3d_) {
			direct_3d_->set_vsync(vsync); 
			Logger::Get().Log("Setting Vsync to " + std::to_string(vsync), __FILE__, __LINE__);
		}
	};

	/**
	 * Get the V-sync state.
	 * @return The V-sync state as a boolean.
	 */
	bool get_vsync() const { return vsync_enabled_; };

	/**
	 * Get the handle to the window.
	 * @return The handle to the window as an HWND.
	 */
	HWND get_hwnd() const { return hwnd_; };
	/**
	 * Set the handle to the window.
	 * @param hwnd The new handle to the window as an HWND.
	 */
	void set_hwnd(HWND hwnd) { hwnd_ = hwnd; };

	/**
	 * Check if the application is running in windowed mode.
	 * @return True if the application is in windowed mode, false otherwise.
	 */
	bool is_windowed() const { return windowed_; };
	/**
	 * Set the windowed mode state.
	 * @param windowed True to set the application in windowed mode, false for full screen.
	 */
	void set_windowed(bool windowed) { windowed_ = windowed; };

	/**
	 * Set the window size for an ImGui window.
	 * This isn't used in the current implementation.
	 * @param size 
	 */
	void set_window_size(const ImVec2 size) { window_size_ = size; };
	/**
	 * Get the window size for an ImGui window.
	 * This isn't used in the current implementation.
	 * @return The window size as an ImVec2.
	 */
	ImVec2 get_window_size() const { return window_size_; };
	/**
	 * Get the aspect ratio of the screen.
	 * @return The aspect ratio as a float.
	 */
	float get_aspect_ratio() const { return static_cast<float>(screen_width_) / static_cast<float>(screen_height_); };

	/**
	 * Get the physics engine instance.
	 * @return Pointer to the physics engine instance.
	 */
	physics* get_physics() const { return physics_; };

	// ----------------------------------- //
	// ------------- Culling ------------- //
	// ----------------------------------- //

	/**
	 * Get the frustum used for culling.
	 * @return The frustum used for culling as a frustum object.
	 */
	frustum get_frustum() const { return frustum_culling_; };
	/**
	 * Set the frustum used for culling.
	 * @param frustum The new frustum to set for culling.
	 */
	void set_frustum(const frustum& frustum) { frustum_culling_ = frustum; };

	/**
	 * Construct the frustum for culling.
	 * This function will calculate the frustum based on the current camera view and projection matrices.
	 */
	void construct_frustum();

	/**
	 * Get the number of objects rendered in the current frame.
	 * @return 
	 */
	int get_render_count() const { return render_count_; };
	/**
	 * Set the number of objects rendered in the current frame.
	 * @param render_count The new render count as an integer.
	 */
	void set_render_count(const int render_count) { render_count_ = render_count; };
	/**
	 * Get the frustum culling tolerance.
	 * This value is used to determine how much an object must be outside the frustum to be culled.
	 * @return The frustum culling tolerance as a float.
	 */
	float get_frustum_tolerance() const { return frustum_culling_tolerance_; };
	/**
	 * Set the frustum culling tolerance.
	 * This value is used to determine how much an object must be outside the frustum to be culled.
	 * @param frustum_tolerance The new frustum culling tolerance as a float.
	 */
	void set_frustum_tolerance(const float frustum_tolerance) { frustum_culling_tolerance_ = frustum_tolerance; };

	/**
	 * Get the flag indicating whether the application can perform fixed updates.
	 * @return True if fixed updates are allowed, false otherwise.
	 */
	bool get_can_fixed_update() const { return can_fixed_update_; };
	/**
	 * Set the flag indicating whether the application can perform fixed updates.
	 * @param can_fixed_update True to allow fixed updates, false to disallow them.
	 */
	void set_can_fixed_update(bool can_fixed_update) { can_fixed_update_ = can_fixed_update; };

	/**
	 * Get the Direct3D back buffer texture.
	 * @return Pointer to the Direct3D back buffer texture.
	 */
	ID3D11ShaderResourceView* get_back_buffer_srv() const {return back_buffer_srv_;};

	/**
	 * Get the Stats manager instance.
	 * @return Pointer to the Stats manager instance.
	 */
	stats* get_stats() const { return stats_; };
	/**
	 * Get the FPS manager instance.
	 * @return Pointer to the FPS manager instance.
	 */
	fps_class* get_fps() const { return fps_; };

	/**
	 * Get the entity manager instance.
	 * @return Pointer to the entity manager instance.
	 */
	ecs::EntityManager* get_entity_manager() const { return entity_manager_.get(); };
	/**
	 * Update the stats after an update in the scene.
	 */
	void update_stats_after_modification();
	/**
	 * Get the global model cache.
	 * The model cache is a static map that stores shared pointers to model_class objects.
	 * This cache is used to avoid loading the same model multiple times.
	 * @return A reference to the global model cache as a map of strings to shared pointers of model_class.
	 */
	std::map<std::string, std::shared_ptr<model_class>>& get_model_cache() { return g_model_cache; }
	
private:
	/**
	 * Do the rendering of the scene by calling the appropriate rendering functions.
	 * @param rotation The rotation angle in radians use for the demo spinning object.
	 * @param x The x position of the demo spinning object.
	 * @param y The y position of the demo spinning object.
	 * @param z The z position of the demo spinning object.
	 * @param textureTranslation This isn't used in the current implementation.
	 * @return True if the rendering was successful, false otherwise.
	 */
	bool render(float, float, float, float, float);
	/**
	 * Update the physics engine with the delta time.
	 * @param delta_time 
	 * @return True if the physics update was successful, false otherwise.
	 */
	bool render_physics(float delta_time);
	/**
	 * Update the mouse strings displayed on the screen.
	 * @param mouseX The x position of the mouse cursor.
	 * @param mouseY The y position of the mouse cursor.
	 * @param mouseDown Whether the left mouse button is pressed or not.
	 * @return True if the mouse strings were updated successfully, false otherwise.
	 */
	bool update_mouse_strings(int, int, bool);
	/**
	 * Update the frames per second (FPS) counter.
	 * @return True if the FPS was updated successfully, false otherwise.
	 */
	bool update_fps();
	/**
	 * Update the render count string displayed on the screen.
	 * @param render_count The number of objects rendered in the current frame.
	 * @return True if the render count string was updated successfully, false otherwise.
	 */
	bool update_render_count_string(int);
	/**
	 * Render the scene to a texture.
	 * This function will render the scene to a texture for further processing, such as reflections or refractions.
	 * @param rotation The rotation angle in radians used for the demo spinning object.
	 * @return True if the scene was rendered to the texture successfully, false otherwise.
	 */
	bool render_scene_to_texture(float);
	/**
	 * Render the refraction of the scene to a texture.
	 * This function will render the refraction effects, such as water or glass, to a texture.
	 * @return True if the refraction was rendered to the texture successfully, false otherwise.
	 */
	bool render_refraction_to_texture();
	/**
	 * Render the reflection of the scene to a texture.
	 * This function will render the reflection effects, such as water or mirrors, to a texture.
	 * @return True if the reflection was rendered to the texture successfully, false otherwise.
	 */
	bool render_reflection_to_texture();
	/**
	 * Render All the Objects in the scene.
	 * The Skysphere is rendered first, then the objects in the scene.
	 * The Skysphere isn't a entity, but a static object that is always rendered in the background.
	 * The other objects are render using the entity manager.
	 * @param diffuse 
	 * @param position 
	 * @param ambient 
	 * @param view 
	 * @param projection 
	 * @return True if the rendering was successful, false otherwise.
	 */
	bool render_pass(XMFLOAT4* diffuse, XMFLOAT4* position, XMFLOAT4* ambient, XMMATRIX view, XMMATRIX projection);

	/**
	 * Update the position of the skysphere based on the camera position.
	 * The skyphere is a sphere rendered first without depth to be the background of the scene.
	 * This function will update the position of the skysphere to match the camera position.
	 */
	void update_skybox_position();
	/**
	 * The thread function for culling objects in the scene.
	 */
	void culling_thread_function();
	
public :
	std::vector<ID3D11ShaderResourceView*> textures;

private :

	// Thread de culling
	std::thread culling_thread_;
	std::atomic<bool> culling_active_;
	std::mutex objects_mutex_;
	
	std::mutex terrain_mutex_;
	std::vector<std::tuple<float, float, float, std::string, int>> terrain_generation_data_;
	bool terrain_generation_ready_;
	int next_terrain_object_id_;
	
	// ------------------------------------- //
	// ------------- DIRECT3D -------------- //
	// ------------------------------------- //

	d_3d_class* direct_3d_;
	IDXGISwapChain* swap_chain_;
	model_class* model_,* ground_model_, * wall_model_, * bath_model_, * water_model_;
	ModelListClass* model_list_;
	bool vsync_enabled_ = true;

	HWND hwnd_;
	bool windowed_;

	// ------------------------------------- //
	// ------------- RENDERING ------------- //
	// ------------------------------------- //

	XMMATRIX base_view_matrix_;
	render_texture_class* render_texture_, * refraction_texture_, * reflection_texture_;
	render_texture_class* scene_texture_;
	display_plane_class* display_plane_;
	int screen_width_, screen_height_;
	camera_class* camera_;
	camera_class* sun_camera_;
	camera_class* active_camera_;
	position_class* position_;

	shadow_map* shadow_map_;
	ID3D11ShaderResourceView* shadow_map_srv_;

	// ------------------------------------ //
	// ------------- OBJECTS -------------- //
	// ------------------------------------ //

	std::unique_ptr<ecs::EntityManager> entity_manager_;
	
	object* selected_object_;
	
	float speed_ = 0.1f; // speed for the demo spinning object
	std::vector<object*> imported_object_;
	int object_id_ = 0;
	std::vector<object*> skybox_;

	// ----------------------------------- //
	// ------------- LIGHTS -------------- //
	// ----------------------------------- //

	light_class* m_light_;
	std::vector<light_class*> lights_;
	int num_lights_;
	light_class* sun_light_;
	XMFLOAT3 true_light_position_;
	model_class* light_model_;

	// ----------------------------------- //
	// ------------- SHADERS ------------- //
	// ----------------------------------- //

	shader_manager_class* shader_manager_;
	font_shader_class* font_shader_;
	bitmap_class* bitmap_;
	sprite_class* sprite_;
	bool enable_cel_shading_;

	// ----------------------------------- //
	// ------------ VARIABLES ------------ //
	// ----------------------------------- //

	float water_height_, water_translation_;
	wchar_t* path_;
	std::filesystem::path w_folder_;

	// ------------------------------------------------- //
	// ------------- FPS AND INFO ON SCREEN ------------ //
	// ------------------------------------------------- //

	timer_class* timer_;
	text_class* mouse_strings_;
	text_class* render_count_string_;
	font_class* font_;
	fps_class* fps_;
	text_class* fps_string_;
	int previous_fps_;

	// ------------------------------------------------- //
	// ------------------- OTHER ----------------------- //
	// ------------------------------------------------- //

	bool should_quit_;
	physics* physics_;
	float gravity_;
	XMVECTOR previous_position_;
	ImVec2 window_size_;
	int physics_tick_rate_ = 50;
	bool can_fixed_update_ = false;
	std::thread physics_thread_;

	ID3D11Texture2D* back_buffer_texture_;
	ID3D11ShaderResourceView* back_buffer_srv_;

	stats* stats_;

	// ------------------------------------------------- //
	// ------------------- Culling --------------------- //
	// ------------------------------------------------- //

	frustum frustum_culling_;
	int render_count_;
	float frustum_culling_tolerance_ = 5.f;

	// ------------------------------------------------- //
	// -------------------- Input ---------------------- //
	// ------------------------------------------------- //

	input inputs_;
	bool tab_was_pressed_;
};

#endif