// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com)
// This file is part of the "Nazara Engine - Widgets module"
// For conditions of distribution and use, see copyright notice in Config.hpp

#include <Nazara/Widgets/SimpleWidgetStyles.hpp>
#include <Nazara/Graphics/Components/GraphicsComponent.hpp>
#include <Nazara/Utility/Components/NodeComponent.hpp>
#include <Nazara/Widgets/ButtonWidget.hpp>
#include <Nazara/Widgets/Canvas.hpp>
#include <Nazara/Widgets/CheckboxWidget.hpp>
#include <Nazara/Widgets/ImageButtonWidget.hpp>
#include <Nazara/Widgets/LabelWidget.hpp>
#include <Nazara/Widgets/ScrollAreaWidget.hpp>
#include <Nazara/Widgets/ScrollbarButtonWidget.hpp>
#include <Nazara/Widgets/ScrollbarWidget.hpp>
#include <Nazara/Widgets/Widgets.hpp>
#include <Nazara/Widgets/Debug.hpp>

namespace Nz
{
	SimpleButtonWidgetStyle::SimpleButtonWidgetStyle(ButtonWidget* buttonWidget, StyleConfig config) :
	ButtonWidgetStyle(buttonWidget, 2),
	m_hoveredMaterial(std::move(config.hoveredMaterial)),
	m_material(std::move(config.material)),
	m_pressedMaterial(std::move(config.pressedMaterial)),
	m_pressedHoveredMaterial(std::move(config.pressedHoveredMaterial)),
	m_isHovered(false),
	m_isPressed(false)
	{
		assert(m_material);

		auto& registry = GetRegistry();
		UInt32 renderMask = GetRenderMask();

		SlicedSprite::Corner corner;
		corner.size = Vector2f(config.cornerSize);
		corner.textureCoords = Vector2f(config.cornerTexCoords);

		m_sprite = std::make_shared<SlicedSprite>(m_material);
		m_sprite->SetCorners(corner, corner);

		m_textSprite = std::make_shared<TextSprite>(Widgets::Instance()->GetTransparentMaterial());

		m_spriteEntity = CreateGraphicsEntity();
		registry.get<GraphicsComponent>(m_spriteEntity).AttachRenderable(m_sprite, renderMask);

		m_textEntity = CreateGraphicsEntity();
		registry.get<GraphicsComponent>(m_textEntity).AttachRenderable(m_textSprite, renderMask);
	}

	void SimpleButtonWidgetStyle::Layout(const Vector2f& size)
	{
		m_sprite->SetSize(size);

		entt::registry& registry = GetRegistry();

		Boxf textBox = m_textSprite->GetAABB();
		registry.get<NodeComponent>(m_textEntity).SetPosition(size.x / 2.f - textBox.width / 2.f, size.y / 2.f - textBox.height / 2.f);
	}

	void SimpleButtonWidgetStyle::OnHoverBegin()
	{
		UpdateMaterial(true, m_isPressed);
		m_isHovered = true;
	}

	void SimpleButtonWidgetStyle::OnHoverEnd()
	{
		UpdateMaterial(false, m_isPressed);
		m_isHovered = false;
	}

	void SimpleButtonWidgetStyle::OnPress()
	{
		UpdateMaterial(m_isHovered, true);
		m_isPressed = true;
	}

	void SimpleButtonWidgetStyle::OnRelease()
	{
		UpdateMaterial(m_isHovered, false);
		m_isPressed = false;
	}

	void SimpleButtonWidgetStyle::UpdateRenderLayer(int baseRenderLayer)
	{
		m_sprite->UpdateRenderLayer(baseRenderLayer);
		m_textSprite->UpdateRenderLayer(baseRenderLayer + 1);
	}

	void SimpleButtonWidgetStyle::UpdateText(const AbstractTextDrawer& drawer)
	{
		m_textSprite->Update(drawer);
	}

	void SimpleButtonWidgetStyle::UpdateMaterial(bool hovered, bool pressed)
	{
		if (pressed && hovered && m_pressedHoveredMaterial)
			m_sprite->SetMaterial(m_pressedHoveredMaterial);
		else if (pressed && m_pressedMaterial)
			m_sprite->SetMaterial(m_pressedMaterial);
		else if (hovered && m_hoveredMaterial)
			m_sprite->SetMaterial(m_hoveredMaterial);
		else
			m_sprite->SetMaterial(m_material);
	}


	SimpleCheckboxWidgetStyle::SimpleCheckboxWidgetStyle(CheckboxWidget* buttonWidget, StyleConfig config) :
	CheckboxWidgetStyle(buttonWidget, 2),
	m_checkMaterial(std::move(config.checkMaterial)),
	m_hoveredMaterial(std::move(config.backgroundHoveredMaterial)),
	m_material(std::move(config.backgroundMaterial)),
	m_tristateMaterial(std::move(config.tristateMaterial)),
	m_isHovered(false)
	{
		assert(m_material);
		assert(m_checkMaterial);
		assert(m_tristateMaterial);

		auto& registry = GetRegistry();
		UInt32 renderMask = GetRenderMask();

		SlicedSprite::Corner corner;
		corner.size = Vector2f(config.backgroundCornerSize);
		corner.textureCoords = Vector2f(config.backgroundCornerTexCoords);

		m_backgroundSprite = std::make_shared<SlicedSprite>(m_material);
		m_backgroundSprite->SetCorners(corner, corner);

		m_checkSprite = std::make_shared<Sprite>(m_checkMaterial);

		m_backgroundEntity = CreateGraphicsEntity();
		registry.get<GraphicsComponent>(m_backgroundEntity).AttachRenderable(m_backgroundSprite, renderMask);

		m_checkEntity = CreateGraphicsEntity();
		{
			auto& gfxComponent = registry.get<GraphicsComponent>(m_checkEntity);
			gfxComponent.AttachRenderable(m_checkSprite, renderMask);
			gfxComponent.Hide();
		}
	}

	void SimpleCheckboxWidgetStyle::Layout(const Vector2f& size)
	{
		m_backgroundSprite->SetSize(size);

		Vector2f checkSize = size * 0.66f;
		m_checkSprite->SetSize(checkSize);

		GetRegistry().get<NodeComponent>(m_checkEntity).SetPosition(size.x / 2.f - checkSize.x / 2.f, size.y / 2.f - checkSize.y / 2.f);
	}

	void SimpleCheckboxWidgetStyle::OnHoverBegin()
	{
		m_isHovered = true;
		UpdateMaterial(m_isHovered);
	}

	void SimpleCheckboxWidgetStyle::OnHoverEnd()
	{
		m_isHovered = false;
		UpdateMaterial(m_isHovered);
	}

	void SimpleCheckboxWidgetStyle::OnNewState(CheckboxState newState)
	{
		switch (newState)
		{
			case CheckboxState::Unchecked:
			{
				GetRegistry().get<GraphicsComponent>(m_checkEntity).Hide();
				break;
			}

			case CheckboxState::Tristate:
			case CheckboxState::Checked:
			{
				m_checkSprite->SetMaterial((newState == CheckboxState::Checked) ? m_checkMaterial : m_tristateMaterial);
				GetRegistry().get<GraphicsComponent>(m_checkEntity).Show();
				break;
			}
		}
	}

	void SimpleCheckboxWidgetStyle::UpdateRenderLayer(int baseRenderLayer)
	{
		m_backgroundSprite->UpdateRenderLayer(baseRenderLayer);
		m_checkSprite->UpdateRenderLayer(baseRenderLayer + 1);
	}

	void SimpleCheckboxWidgetStyle::UpdateMaterial(bool hovered)
	{
		if (hovered && m_hoveredMaterial)
			m_backgroundSprite->SetMaterial(m_hoveredMaterial);
		else
			m_backgroundSprite->SetMaterial(m_material);
	}


	SimpleImageButtonWidgetStyle::SimpleImageButtonWidgetStyle(ImageButtonWidget* imageButtonWidget, StyleConfig config) :
	ImageButtonWidgetStyle(imageButtonWidget, 1),
	m_isHovered(false),
	m_isPressed(false)
	{
		auto& registry = GetRegistry();
		UInt32 renderMask = GetRenderMask();

		SlicedSprite::Corner hoveredCorner;
		hoveredCorner.size = Vector2f(config.hoveredCornerSize, config.hoveredCornerSize);
		hoveredCorner.textureCoords = Vector2f(config.hoveredCornerTexCoords, config.hoveredCornerTexCoords);

		m_hoveredSprite = std::make_shared<SlicedSprite>(config.hoveredMaterial);
		m_hoveredSprite->SetCorners(hoveredCorner, hoveredCorner);

		float imageCornerSize = imageButtonWidget->GetCornerSize();
		float imageCornerTexCoords = imageButtonWidget->GetCornerTexCoords();

		SlicedSprite::Corner corner;
		corner.size = Vector2f(imageCornerSize, imageCornerSize);
		corner.textureCoords = Vector2f(imageCornerTexCoords, imageCornerTexCoords);

		m_sprite = std::make_shared<SlicedSprite>(imageButtonWidget->GetMaterial());
		m_sprite->SetCorners(corner, corner);
		m_sprite->SetTextureCoords(imageButtonWidget->GetTextureCoords());

		m_entity = CreateGraphicsEntity();

		GraphicsComponent& gfxComponent = registry.get<GraphicsComponent>(m_entity);
		gfxComponent.AttachRenderable(m_sprite, renderMask);
	}

	void SimpleImageButtonWidgetStyle::Layout(const Vector2f& size)
	{
		m_hoveredSprite->SetSize(size);
		m_sprite->SetSize(size);
	}

	void SimpleImageButtonWidgetStyle::OnHoverBegin()
	{
		Update(true, m_isPressed);
		m_isHovered = true;
	}

	void SimpleImageButtonWidgetStyle::OnHoverEnd()
	{
		Update(false, m_isPressed);
		m_isHovered = false;
	}

	void SimpleImageButtonWidgetStyle::OnPress()
	{
		Update(m_isHovered, true);
		m_isPressed = true;
	}

	void SimpleImageButtonWidgetStyle::OnRelease()
	{
		Update(m_isHovered, false);
		m_isPressed = false;
	}

	void SimpleImageButtonWidgetStyle::OnUpdate()
	{
		ImageButtonWidget* owner = GetOwnerWidget<ImageButtonWidget>();

		// If a hovering material was added while we're being hovered, we need to detach the hovering sprite
		if (owner->GetHoveredMaterial())
		{
			GraphicsComponent& gfxComponent = GetRegistry().get<GraphicsComponent>(m_entity);
			gfxComponent.DetachRenderable(m_hoveredSprite);
		}

		m_sprite->SetTextureCoords(owner->GetTextureCoords());

		Update(m_isHovered, m_isPressed);
	}

	void SimpleImageButtonWidgetStyle::UpdateRenderLayer(int baseRenderLayer)
	{
		m_sprite->UpdateRenderLayer(baseRenderLayer);
		m_hoveredSprite->UpdateRenderLayer(baseRenderLayer + 1);
	}

	void SimpleImageButtonWidgetStyle::Update(bool hovered, bool pressed)
	{
		ImageButtonWidget* owner = GetOwnerWidget<ImageButtonWidget>();

		if (pressed)
		{
			if (const auto& pressedMaterial = owner->GetPressedMaterial())
			{
				m_sprite->SetColor(owner->GetColor());
				m_sprite->SetMaterial(pressedMaterial);
			}
			else
			{
				m_sprite->SetColor(owner->GetColor() * Nz::Color::FromRGB8(120, 120, 120));
				m_sprite->SetMaterial(owner->GetMaterial());
			}
		}
		else
		{
			m_sprite->SetColor(owner->GetColor());
			m_sprite->SetMaterial(owner->GetMaterial());
		}

		if (hovered)
		{
			if (const auto& hoveredMaterial = owner->GetHoveredMaterial())
			{
				if (!pressed)
					m_sprite->SetMaterial(hoveredMaterial);
			}
			else
			{
				if (!pressed)
					m_sprite->SetMaterial(owner->GetMaterial());

				if (!m_isHovered)
				{
					GraphicsComponent& gfxComponent = GetRegistry().get<GraphicsComponent>(m_entity);
					gfxComponent.AttachRenderable(m_hoveredSprite, GetRenderMask());
				}
			}
		}
		else
		{
			if (!pressed)
				m_sprite->SetMaterial(owner->GetMaterial());

			if (m_isHovered)
			{
				GraphicsComponent& gfxComponent = GetRegistry().get<GraphicsComponent>(m_entity);
				gfxComponent.DetachRenderable(m_hoveredSprite);
			}
		}
	}


	SimpleLabelWidgetStyle::SimpleLabelWidgetStyle(LabelWidget* labelWidget, std::shared_ptr<MaterialInstance> material, std::shared_ptr<MaterialInstance> hoveredMaterial) :
	LabelWidgetStyle(labelWidget, 1),
	m_hoveredMaterial(std::move(hoveredMaterial)),
	m_material(std::move(material))
	{
		assert(m_material);

		auto& registry = GetRegistry();
		UInt32 renderMask = GetRenderMask();

		m_textSprite = std::make_shared<TextSprite>(m_material);

		m_entity = CreateGraphicsEntity();
		registry.get<GraphicsComponent>(m_entity).AttachRenderable(m_textSprite, renderMask);
	}

	void SimpleLabelWidgetStyle::Layout(const Vector2f& size)
	{
		entt::registry& registry = GetRegistry();

		Boxf textBox = m_textSprite->GetAABB();
		registry.get<NodeComponent>(m_entity).SetPosition(size.x / 2.f - textBox.width / 2.f, size.y / 2.f - textBox.height / 2.f);
	}

	void SimpleLabelWidgetStyle::OnHoverBegin()
	{
		UpdateMaterial(true);
	}

	void SimpleLabelWidgetStyle::OnHoverEnd()
	{
		UpdateMaterial(false);
	}

	void SimpleLabelWidgetStyle::UpdateMaterial(bool hovered)
	{
		if (hovered && m_hoveredMaterial)
			m_textSprite->SetMaterial(m_hoveredMaterial);
		else
			m_textSprite->SetMaterial(m_material);
	}

	void SimpleLabelWidgetStyle::UpdateRenderLayer(int baseRenderLayer)
	{
		m_textSprite->UpdateRenderLayer(baseRenderLayer);
	}

	void SimpleLabelWidgetStyle::UpdateText(const AbstractTextDrawer& drawer)
	{
		m_textSprite->Update(drawer);
	}


	SimpleScrollAreaWidgetStyle::SimpleScrollAreaWidgetStyle(ScrollAreaWidget* scrollAreaWidget) :
	ScrollAreaWidgetStyle(scrollAreaWidget, 0)
	{
	}

	void SimpleScrollAreaWidgetStyle::Layout(const Vector2f& /*size*/)
	{
	}

	void SimpleScrollAreaWidgetStyle::UpdateRenderLayer(int /*baseRenderLayer*/)
	{
	}


	SimpleScrollbarWidgetStyle::SimpleScrollbarWidgetStyle(ScrollbarWidget* scrollBarWidget, StyleConfig config) :
	ScrollbarWidgetStyle(scrollBarWidget, 1),
	m_config(std::move(config))
	{
		auto& registry = GetRegistry();
		UInt32 renderMask = GetRenderMask();

		m_backgroundScrollbarSprite = std::make_shared<Sprite>((scrollBarWidget->GetOrientation() == ScrollbarOrientation::Horizontal) ? m_config.backgroundHorizontalMaterial : m_config.backgroundVerticalMaterial);

		m_backgroundScrollbarSpriteEntity = CreateGraphicsEntity();
		registry.get<GraphicsComponent>(m_backgroundScrollbarSpriteEntity).AttachRenderable(m_backgroundScrollbarSprite, renderMask);

		m_scrollbarSpriteEntity = CreateGraphicsEntity();
		registry.get<GraphicsComponent>(m_scrollbarSpriteEntity).AttachRenderable(m_scrollbarSprite, renderMask);
	}

	void SimpleScrollbarWidgetStyle::Layout(const Vector2f& size)
	{
		m_backgroundScrollbarSprite->SetSize(size);
	}

	void SimpleScrollbarWidgetStyle::UpdateRenderLayer(int baseRenderLayer)
	{
		m_backgroundScrollbarSprite->UpdateRenderLayer(baseRenderLayer);
	}


	SimpleScrollbarButtonWidgetStyle::SimpleScrollbarButtonWidgetStyle(ScrollbarButtonWidget* scrollbarButtonWidget, StyleConfig config) :
	ScrollbarButtonWidgetStyle(scrollbarButtonWidget, 1),
	m_hoveredMaterial(std::move(config.hoveredMaterial)),
	m_material(std::move(config.material)),
	m_pressedMaterial(std::move(config.grabbedMaterial)),
	m_pressedHoveredMaterial(std::move(config.grabbedHoveredMaterial)),
	m_isHovered(false),
	m_isPressed(false)
	{
		assert(m_material);

		auto& registry = GetRegistry();
		UInt32 renderMask = GetRenderMask();

		SlicedSprite::Corner corner;
		corner.size = Vector2f(config.cornerSize);
		corner.textureCoords = Vector2f(config.cornerTexCoords);

		m_sprite = std::make_shared<SlicedSprite>(m_material);
		m_sprite->SetCorners(corner, corner);

		m_entity = CreateGraphicsEntity();
		registry.get<GraphicsComponent>(m_entity).AttachRenderable(m_sprite, renderMask);
	}

	void SimpleScrollbarButtonWidgetStyle::Layout(const Vector2f& size)
	{
		m_sprite->SetSize(size);
	}

	void SimpleScrollbarButtonWidgetStyle::OnHoverBegin()
	{
		Update(true, m_isPressed);
		m_isHovered = true;
	}

	void SimpleScrollbarButtonWidgetStyle::OnHoverEnd()
	{
		Update(false, m_isPressed);
		m_isHovered = false;
	}

	void SimpleScrollbarButtonWidgetStyle::OnGrab()
	{
		Update(m_isHovered, true);
		m_isPressed = true;
	}

	void SimpleScrollbarButtonWidgetStyle::OnRelease()
	{
		Update(m_isHovered, false);
		m_isPressed = false;
	}

	void SimpleScrollbarButtonWidgetStyle::UpdateRenderLayer(int baseRenderLayer)
	{
		m_sprite->UpdateRenderLayer(baseRenderLayer);
	}

	void SimpleScrollbarButtonWidgetStyle::Update(bool hovered, bool pressed)
	{
		if (pressed && hovered && m_pressedHoveredMaterial)
			m_sprite->SetMaterial(m_pressedHoveredMaterial);
		else if (pressed && m_pressedMaterial)
			m_sprite->SetMaterial(m_pressedMaterial);
		else if (hovered && m_hoveredMaterial)
			m_sprite->SetMaterial(m_hoveredMaterial);
		else
			m_sprite->SetMaterial(m_material);
	}
}
