#ifndef _APPLICATIONCLASS_H_
#define _APPLICATIONCLASS_H_


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "object.h"
#include "lightclass.h"
#include <vector>
#include <filesystem>

#include "bitmapclass.h"
#include "spriteclass.h"
#include "timerclass.h"
#include "fontshaderclass.h"
#include "fontclass.h"
#include "textclass.h"
#include "fpsclass.h"
#include "inputclass.h"
#include "shadermanagerclass.h"
#include "modellistclass.h"
#include "positionclass.h"
#include "frustumclass.h"
#include "rendertextureclass.h"
#include "displayplaneclass.h"
#include "translateshaderclass.h"
#include "reflectionshaderclass.h"
#include "physics.h"
#include "frustum.h"
#include <fstream>

#include <WICTextureLoader.h>
#include <comdef.h> // Pour _com_error
#include <chrono>
#include <thread>
#include <map>
#include <algorithm>
#include <DirectXMath.h>


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = false;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.3f;

struct Input
{
	bool m_KeyLeft = false;
	bool m_KeyRight = false;
	bool m_KeyUp = false;
	bool m_KeyDown = false;
};

////////////////////////////////////////////////////////////////////////////////
// Class name: ApplicationClass
////////////////////////////////////////////////////////////////////////////////
class ApplicationClass
{
public:
	ApplicationClass();
	~ApplicationClass();
	D3DClass* GetDirect3D();
	RenderTextureClass* GetSceneTexture() const { return m_SceneTexture; };
	RenderTextureClass* GetRenderTexture() const { return m_RenderTexture; };
	RenderTextureClass* GetRefractionTexture() const { return m_RefractionTexture; };
	RenderTextureClass* GetReflectionTexture() const { return m_ReflectionTexture; };
	

	bool Initialize(int, int, HWND);
	void Shutdown();
	bool Frame(InputClass*);
	void PhysicsThreadFunction();
	int GetPhysicsTickRate() const { return m_PhysicsTickRate; };
	void SetPhysicsTickRate(int physicsTickRate) { m_PhysicsTickRate = physicsTickRate; };

	int GetScreenWidth() const;
	void SetScreenWidth(int screenWidth);
	int GetScreenHeight() const;
	void SetScreenHeight(int screenHeight);

	float GetSpeed() const { return m_speed; };
	void SetSpeed(float speed) { this->m_speed = speed; };

	void AddCube();
	void DeleteKobject(int index);
	size_t GetCubeCount() const { return m_cubes.size(); };
	size_t GetTerrainCubeCount() const { return m_terrainChunk.size(); };
	std::vector<Object*> GetCubes() const { return m_cubes; };
	std::vector<Object*> GetTerrainCubes() const { return m_terrainChunk; };
	std::vector<Object*> GetKobjects() const { return m_object; };
	void AddKobject(std::wstring& filepath);
	void SetPath(WCHAR* path) { m_path = path; };
	void SetWFolder(std::filesystem::path WFolder) { m_WFolder = WFolder; };

	void GenerateTerrain();
	void DeleteTerrain();

	XMVECTOR GetLightPosition(int index);
	XMVECTOR GetLightColor(int index);
	void SetLightPosition(int index, XMVECTOR color);
	void SetLightColor(int index, XMVECTOR color);
	void DeleteLight(int index);
	void AddLight();
	std::vector<LightClass*> GetLights() const { return m_Lights; };
	LightClass* GetSunLight() const { return m_SunLight; };

	bool GetShouldQuit() const { return m_ShouldQuit; };
	void SetShouldQuit(bool shouldQuit) { m_ShouldQuit = shouldQuit; };

	void SetCelShading(bool enable) { m_enableCelShading = enable; };

	void SetVsync(bool vsync);
	bool GetVsync() const { return VSYNC_ENABLED; };

	HWND GetHwnd() const;
	void SetHwnd(HWND hwnd);

	bool IsWindowed() const;
	void SetWindowed(bool windowed);

	void SetWindowSize(ImVec2 size) { windowSize = size; };
	ImVec2 GetWindowSize() const { return windowSize; };
	float GetAspectRatio() const { return (float)m_screenWidth / (float)m_screenHeight; };

	Physics* GetPhysics() const { return m_Physics; };

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

	Frustum GetFrustum() const { return m_FrustumCulling; };
	void SetFrustum(Frustum frustum) { m_FrustumCulling = frustum; };

	void ConstructFrustum();
	int GetRenderCount() const { return m_renderCount; };
	void SetRenderCount(int renderCount) { m_renderCount = renderCount; };
	float GetFrustumTolerance() const { return m_FrustumCullingTolerance; };
	void SetFrustumTolerance(float frustumTolerance) { m_FrustumCullingTolerance = frustumTolerance; };

	bool GetCanFixedUpdate() const { return CanFixedUpdate; };
	void SetCanFixedUpdate(bool canFixedUpdate) { CanFixedUpdate = canFixedUpdate; };

	ID3D11ShaderResourceView* GetBackBufferSRV() const {return m_BackBufferSRV;};

	// Save and load scene
	void SaveScene();
	void LoadScene();

	void SetScenePath(std::string path) { m_scenePath = path; };
	std::wstring GetScenePath();

	std::string ConvertWStringToString(const std::wstring& wstr);
	
private:
	bool Render(float, float, float, float, float);
	bool RenderPhysics(bool keyLeft, bool keyRight, bool keyUp, bool keyDown, float deltaTime);
	bool UpdateMouseStrings(int, int, bool);
	bool UpdateFps();
	bool UpdateRenderCountString(int);
	bool RenderSceneToTexture(float);
	bool RenderRefractionToTexture();
	bool RenderReflectionToTexture();
	bool RenderPass(const std::vector<std::reference_wrapper<std::vector<Object*>>>& RenderQueues, XMFLOAT4* diffuse, XMFLOAT4* position, XMFLOAT4* ambient, XMMATRIX view, XMMATRIX projection);

	void ConstructSkybox(); // Construct the skybox
	void UpdateSkyboxPosition(); // Update the skybox position
	bool RenderSkybox(XMMATRIX view, XMMATRIX projection); // Render the skybox
	
public :
	std::vector<ID3D11ShaderResourceView*> textures;
	std::vector<ID3D11ShaderResourceView*> m_SkyboxTextures;

private :

	// ------------------------------------- //
	// ------------- DIRECT3D -------------- //
	// ------------------------------------- //

	D3DClass* m_Direct3D;
	IDXGISwapChain* m_swapChain;
	ModelClass* m_Model,* m_GroundModel, * m_WallModel, * m_BathModel, * m_WaterModel;
	ModelListClass* m_ModelList;
	bool VSYNC_ENABLED = true;

	HWND m_hwnd;
	bool m_windowed;

	std::string m_scenePath;

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

	XMMATRIX m_baseViewMatrix;
	RenderTextureClass* m_RenderTexture, * m_RefractionTexture, * m_ReflectionTexture;
	RenderTextureClass* m_SceneTexture;
	DisplayPlaneClass* m_DisplayPlane;
	int m_screenWidth, m_screenHeight;
	CameraClass* m_Camera;
	PositionClass* m_Position;
	std::vector<XMMATRIX> m_SkyboxInitialTranslations;

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

	Object* m_SelectedObject;
	std::vector<Object*> m_cubes;
	std::vector<Object*> m_terrainChunk;
	float m_speed = 0.1f; // speed for the demo spinning object
	std::vector<Object*> m_object;
	std::vector<Object*> m_ImportedObject;
	int m_ObjectId = 0;
	std::vector<std::reference_wrapper<std::vector<Object*>>> m_RenderQueues;
	std::vector<Object*> m_Skybox;

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

	LightClass* m_Light;
	std::vector<LightClass*> m_Lights;
	int m_numLights;
	LightClass* m_SunLight;

	XMFLOAT3 TrueLightPosition;
	ModelClass* m_LightModel;

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

	ShaderManagerClass* m_ShaderManager;
	FontShaderClass* m_FontShader;
	BitmapClass* m_Bitmap;
	SpriteClass* m_Sprite;
	
	bool m_enableCelShading;

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

	float m_waterHeight, m_waterTranslation;
	wchar_t* m_path;
	std::filesystem::path m_WFolder;

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

	TimerClass* m_Timer;
	TextClass* m_MouseStrings;
	TextClass* m_RenderCountString;
	FontClass* m_Font;
	FpsClass* m_Fps;
	TextClass* m_FpsString;
	int m_previousFps;

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

	bool m_ShouldQuit;
	Physics* m_Physics;
	float m_gravity;
	XMVECTOR m_previousPosition;
	ImVec2 windowSize;
	int m_PhysicsTickRate = 50;
	bool CanFixedUpdate = false;
	std::thread m_PhysicsThread;

	ID3D11Texture2D* m_BackBufferTexture;
	ID3D11ShaderResourceView* m_BackBufferSRV;

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

	Frustum m_FrustumCulling;
	int m_renderCount;
	float m_FrustumCullingTolerance = 5.f;

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

	Input m_Inputs;
};

#endif