#pragma once
#include "Nuake/Core/Core.h"
#include "Nuake/Core/Maths.h"

#include "Nuake/Rendering/Shaders/Shader.h"
#include "Nuake/Rendering/Textures/TextureManager.h"
#include "Texture.h"
#include "Nuake/Resource/Serializable.h"
#include "Nuake/Resource/Resource.h"
#include "Nuake/FileSystem/FileSystem.h"

#include "Nuake/Rendering/Vulkan/VkResources.h"
#include "Nuake/Rendering/Vulkan/VulkanImage/VulkanImage.h"


namespace Nuake
{
	struct UBOStructure {
		int u_HasAlbedo;
		float padding;
		float padding2;
		float padding3;
		Vector3 m_AlbedoColor;
		int u_HasMetalness;
		float u_MetalnessValue;
		int u_HasRoughness;
		float u_RoughnessValue;
		int u_HasAO;
		float u_AOValue;
		int u_HasNormal;
		int u_HasDisplacement;
		int u_Unlit;
		float u_Emissive;
	};

	enum class SamplingType
	{
		Nearest = 0,
		Linear,
	};

	enum class CullingType
	{
		Back = 0,
		Front,
		None
	};

	class Material : ISerializable, public Resource
	{
	private:
		std::string m_Name;
		unsigned int UBO;

		void InitUniformBuffer()
		{
			data = {
				0, // Has albedo texture
				0, // Padding byte
				0, // Padding byte
				0, // Padding byte
				Vector3(1.f, 1.f, 1.f),	// Albedo color
				0,						// Has metalness 
				0.f,					// Metalness value
				0,						// u_HasRoughness
				1.f,					// u_RoughnessValue
				0,						// u_HasAO
				0.5f,					// u_AOValue
				0,						// u_HasNormal
				0,						// u_HasDisplacement
				0, // unlit
				1.0f // emissive
			};
		}
	public:
		UUID AlbedoImage = UUID(0);
		UUID AOImage = UUID(0);
		UUID MetalnessImage = UUID(0);
		UUID RoughnessImage = UUID(0);
		UUID NormalImage = UUID(0);
		
		SamplingType m_SamplingType = SamplingType::Nearest;
		CullingType m_CullingType = CullingType::Back;
		bool m_ReceiveShadows = true;
		bool m_CastShadows = true;
		bool m_AlphaScissor = false;

		Ref<Texture> m_Albedo; 
		Ref<Texture> m_AO;
		Ref<Texture> m_Metalness;
		Ref<Texture> m_Roughness;
		Ref<Texture> m_Normal;
		Ref<Texture> m_Displacement;

		UBOStructure data;

		static Ref<Texture> m_DefaultAlbedo;
		static Ref<Texture> m_DefaultAO;
		static Ref<Texture> m_DefaultMetalness;
		static Ref<Texture> m_DefaultRoughness;
		static Ref<Texture> m_DefaultNormal;
		static Ref<Texture> m_DefaultDisplacement;

		Material();
		Material(const std::string albedo);
		Material(Ref<Texture> texture) { m_Albedo = texture; }
		Material(const Vector3 albedoColor);
		~Material();

		void InitDefaultTextures();

		void Bind(Shader* shader);
		void SetupUniformBuffer();

		void SetName(const std::string name);
		std::string GetName();

		inline void SetUnlit(bool value) { data.u_Unlit = value; }
		inline bool GetUnlit() { return data.u_Unlit == 1; }

		bool HasAlbedo() { return (m_Albedo != nullptr || AlbedoImage != UUID(0)); }
		void SetAlbedo(const std::string path) 
		{ 
			if (FileSystem::FileExists(path))
			{
				GPUResources& resources = GPUResources::Get();
				Ref<VulkanImage> image = CreateRef<VulkanImage>(FileSystem::RelativeToAbsolute(path));
				if (resources.AddTexture(image))
				{
					AlbedoImage = image->GetID();
				}

				Ref<Texture> albedoTexture = TextureManager::Get()->GetTexture(FileSystem::RelativeToAbsolute(path));
				SetAlbedo(albedoTexture);
			}
		}
		void SetAlbedo(Ref<Texture> texture) { m_Albedo = texture; }

		bool HasAO() { return (m_AO != nullptr || AOImage != UUID(0)); }
		void SetAO(const std::string albedo);
		void SetAO(Ref<Texture> texture) { m_AO = texture; }

		bool HasMetalness() { return (m_Metalness != nullptr || MetalnessImage != UUID(0)); }
		void SetMetalness(const std::string albedo);
		void SetMetalness(Ref<Texture> texture) { m_Metalness = texture; }

		bool HasRoughness() { return (m_Roughness != nullptr || RoughnessImage != UUID(0)); }
		void SetRoughness(const std::string albedo);
		void SetRoughness(Ref<Texture> texture) { m_Roughness = texture; }

		bool HasNormal() { return (m_Normal != nullptr || NormalImage != UUID(0)); }
		void SetNormal(const std::string albedo);
		void SetNormal(Ref<Texture> texture) { m_Normal = texture; }

		bool HasDisplacement() { return m_Displacement != nullptr; }
		void SetDisplacement(const std::string displacement);
		void SetDisplacement(Ref<Texture> texture) { m_Displacement = texture; }

		json Serialize() override
		{
			BEGIN_SERIALIZE();
			
			j["Path"] = Path;
			j["UUID"] = static_cast<uint64_t>(ID);
			j["HasAlbedo"] = this->HasAlbedo();

			// new UUID
			if (AlbedoImage != 0)
			{
				j["AlbedoImage"] = static_cast<uint64_t>(AlbedoImage);
			}
			// TODO others.

			if (NormalImage != 0)
			{
				j["NormalImage"] = static_cast<uint64_t>(NormalImage);
			}

			if (RoughnessImage != 0)
			{
				j["RoughnessImage"] = static_cast<uint64_t>(RoughnessImage);
			}

			if (MetalnessImage != 0)
			{
				j["MetalnessImage"] = static_cast<uint64_t>(MetalnessImage);
			}

			if (AOImage != 0)
			{
				j["AOImage"] = static_cast<uint64_t>(AOImage);
			}

			if (HasAlbedo())
			{
				j["Albedo"] = this->m_Albedo->Serialize();
			}

			Vector3 AlbedoColor = data.m_AlbedoColor;
			SERIALIZE_VEC3(AlbedoColor);

			SERIALIZE_VAL_LBL("Emissive", data.u_Emissive);
			SERIALIZE_VAL_LBL("AOValue", data.u_AOValue);
			SERIALIZE_VAL_LBL("MetalnessValue", data.u_MetalnessValue);
			SERIALIZE_VAL_LBL("RoughnessValue", data.u_RoughnessValue);
			SERIALIZE_VAL_LBL("Unlit", data.u_Unlit);

			j["HasAO"] = this->HasAO();
			if (HasAO())
			{
				j["AO"] = m_AO->Serialize();
			}

			j["HasMetalness"] = this->HasMetalness();
			if (HasMetalness())
			{
				j["Metalness"] = m_Metalness->Serialize();
			}

			j["HasRoughness"] = this->HasRoughness();
			if (HasRoughness())
			{
				j["Roughness"] = m_Roughness->Serialize();
			}

			j["HasNormal"] = this->HasNormal();
			if (HasNormal())
			{
				j["Normal"] = m_Normal->Serialize();
			}

			j["HasDisplacement"] = this->HasDisplacement();
			
			END_SERIALIZE();
		}

		bool Deserialize(const json& j) override
		{
			ID = static_cast<uint64_t>(j["UUID"]);

			if (j.contains("AlbedoImage"))
			{
				AlbedoImage = static_cast<uint64_t>(j["AlbedoImage"]);
			}

			if (j.contains("NormalImage"))
			{
				NormalImage = static_cast<uint64_t>(j["NormalImage"]);
			}

			if (j.contains("AOImage"))
			{
				AOImage = static_cast<uint64_t>(j["AOImage"]);
			}

			if (j.contains("RoughnessImage"))
			{
				RoughnessImage = static_cast<uint64_t>(j["RoughnessImage"]);
			}

			if (j.contains("MetalnessImage"))
			{
				MetalnessImage = static_cast<uint64_t>(j["MetalnessImage"]);
			}

			if (j.contains("Albedo"))
			{
				const auto& texturePath = j["Albedo"]["Path"];
				const std::string absolutePath = FileSystem::RelativeToAbsolute(texturePath);

				if (FileSystem::FileExists(texturePath))
				{
					GPUResources& resources = GPUResources::Get();
					Ref<VulkanImage> image = CreateRef<VulkanImage>(absolutePath);
					if (resources.AddTexture(image))
					{
						AlbedoImage = image->GetID();
					}

					Ref<Texture> albedoTexture = TextureManager::Get()->GetTexture(absolutePath);
					SetAlbedo(albedoTexture);
				}
				else
				{
					GPUResources& resources = GPUResources::Get();
					Ref<VulkanImage> missingTexture = TextureManager::Get()->GetTexture2("missing_texture");
					AlbedoImage = missingTexture->GetID();
				}
			}

			if (j.contains("AlbedoColor"))
			{
				DESERIALIZE_VEC3(j["AlbedoColor"], data.m_AlbedoColor);
			}

			if (j.contains("Emissive"))
			{
				data.u_Emissive = j["Emissive"];
			}

			if (j.contains("MetalnessValue"))
			{
				data.u_MetalnessValue = j["MetalnessValue"];
			}

			if (j.contains("RoughnessValue"))
			{
				data.u_RoughnessValue = j["RoughnessValue"];
			}

			if (j.contains("AOValue"))
			{
				data.u_AOValue = j["AOValue"];
			}

			if (j.contains("Unlit"))
			{
				data.u_Unlit = j["Unlit"];
			}

			if (j.contains("Normal"))
			{
				const auto& texturePath = j["Normal"]["Path"];
				const std::string absolutePath = FileSystem::RelativeToAbsolute(texturePath);

				if (FileSystem::FileExists(texturePath))
				{
					GPUResources& resources = GPUResources::Get();
					Ref<VulkanImage> image = CreateRef<VulkanImage>(absolutePath);
					if (resources.AddTexture(image))
					{
						NormalImage = image->GetID();
					}

					Ref<Texture> normalTexture = TextureManager::Get()->GetTexture(absolutePath);
					SetNormal(normalTexture);
				}
				else
				{
					GPUResources& resources = GPUResources::Get();
					Ref<VulkanImage> missingTexture = TextureManager::Get()->GetTexture2("missing_texture");
					NormalImage = missingTexture->GetID();
				}
			}
			

			if (j.contains("AO"))
			{
				const auto& texturePath = j["AO"]["Path"];
				const std::string absolutePath = FileSystem::RelativeToAbsolute(texturePath);

				if (FileSystem::FileExists(texturePath))
				{
					GPUResources& resources = GPUResources::Get();
					Ref<VulkanImage> image = CreateRef<VulkanImage>(absolutePath);
					if (resources.AddTexture(image))
					{
						AOImage = image->GetID();
					}

					Ref<Texture> normalTexture = TextureManager::Get()->GetTexture(absolutePath);
					SetAO(normalTexture);
				}
				else
				{
					GPUResources& resources = GPUResources::Get();
					Ref<VulkanImage> missingTexture = TextureManager::Get()->GetTexture2("missing_texture");
					AOImage = missingTexture->GetID();
				}
			}

			if (j.contains("Metalness"))
			{
				const auto& texturePath = j["Metalness"]["Path"];
				const std::string absolutePath = FileSystem::RelativeToAbsolute(texturePath);

				if (FileSystem::FileExists(texturePath))
				{
					GPUResources& resources = GPUResources::Get();
					Ref<VulkanImage> image = CreateRef<VulkanImage>(absolutePath);
					if (resources.AddTexture(image))
					{
						AOImage = image->GetID();
					}

					Ref<Texture> normalTexture = TextureManager::Get()->GetTexture(absolutePath);
					SetMetalness(normalTexture);
				}
				else
				{
					GPUResources& resources = GPUResources::Get();
					Ref<VulkanImage> missingTexture = TextureManager::Get()->GetTexture2("missing_texture");
					MetalnessImage = missingTexture->GetID();
				}
			}

			if (j.contains("Roughness"))
			{
				const auto& texturePath = j["Roughness"]["Path"];
				const std::string absolutePath = FileSystem::RelativeToAbsolute(texturePath);

				if (FileSystem::FileExists(texturePath))
				{
					GPUResources& resources = GPUResources::Get();
					Ref<VulkanImage> image = CreateRef<VulkanImage>(absolutePath);
					if (resources.AddTexture(image))
					{
						AOImage = image->GetID();
					}

					Ref<Texture> normalTexture = TextureManager::Get()->GetTexture(absolutePath);
					SetRoughness(normalTexture);
				}
				else
				{
					GPUResources& resources = GPUResources::Get();
					Ref<VulkanImage> missingTexture = TextureManager::Get()->GetTexture2("missing_texture");
					RoughnessImage = missingTexture->GetID();
				}
			}

			if (j.contains("Displacement"))
			{
				const std::string absolutePath = FileSystem::RelativeToAbsolute(j["Normal"]["Path"]);
				Ref<Texture> displacementTexture = TextureManager::Get()->GetTexture(absolutePath);
				SetDisplacement(displacementTexture);
			}

			return true;
		}
	};
}
