/* Copyright (C) 2025 Wildfire Games.
 * This file is part of 0 A.D.
 *
 * 0 A.D. is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * 0 A.D. is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "precompiled.h"

#include "ICmpRangeOverlayRenderer.h"

#include "graphics/Color.h"
#include "graphics/Overlay.h"
#include "maths/Vector2D.h"
#include "ps/CStrIntern.h"
#include "renderer/Renderer.h"
#include "renderer/Scene.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPlayer.h"
#include "simulation2/components/ICmpPlayerManager.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/helpers/Player.h"
#include "simulation2/helpers/Render.h"
#include "simulation2/system/Component.h"
#include "simulation2/system/Message.h"

#include <memory>
#include <string>
#include <vector>

class CFrustum;

class CCmpRangeOverlayRenderer final : public ICmpRangeOverlayRenderer
{
public:
	static void ClassInit(CComponentManager& componentManager)
	{
		componentManager.SubscribeToMessageType(MT_Deserialized);
		componentManager.SubscribeToMessageType(MT_OwnershipChanged);
		componentManager.SubscribeToMessageType(MT_PlayerColorChanged);
	}

	DEFAULT_COMPONENT_ALLOCATOR(RangeOverlayRenderer)

	CCmpRangeOverlayRenderer() : m_RangeOverlayData()
	{
		m_Color = CColor(0.f, 0.f, 0.f, 1.f);
	}

	~CCmpRangeOverlayRenderer() = default;

	static std::string GetSchema()
	{
		return "<a:component type='system'/><empty/>";
	}

	void Init(const CParamNode&) override
	{
		m_Enabled = m_LastEnabledState = false;
		UpdateMessageSubscriptions();
	}

	void Deinit() override { }

	void Serialize(ISerializer&) override
	{
	}

	void Deserialize(const CParamNode& paramNode, IDeserializer&) override
	{
		Init(paramNode);
	}

	void ResetRangeOverlays() override
	{
		m_RangeOverlayData.clear();
		UpdateMessageSubscriptions();
		m_Enabled = false;
	}

	void AddRangeOverlay(float radius, const std::string& texture, const std::string& textureMask, float thickness) override
	{
		if (!CRenderer::IsInitialised())
			return;

		SOverlayDescriptor rangeOverlayDescriptor;
		rangeOverlayDescriptor.m_Radius = radius;
		rangeOverlayDescriptor.m_LineTexture = CStrIntern(TEXTURE_BASE_PATH + texture);
		rangeOverlayDescriptor.m_LineTextureMask = CStrIntern(TEXTURE_BASE_PATH + textureMask);
		rangeOverlayDescriptor.m_LineThickness = thickness;

		m_RangeOverlayData.push_back({
			rangeOverlayDescriptor, std::unique_ptr<SOverlayTexturedLine>()
		});
		m_Enabled = true;
		UpdateMessageSubscriptions();
	}

	void HandleMessage(const CMessage& msg, bool /*global*/) override
	{
		switch (msg.GetType())
		{
		case MT_Interpolate:
		{
			const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);

			for (RangeOverlayData& rangeOverlay : m_RangeOverlayData)
				UpdateRangeOverlay(rangeOverlay, msgData.offset);

			UpdateMessageSubscriptions();

			break;
		}
		case MT_Deserialized:
		case MT_OwnershipChanged:
		case MT_PlayerColorChanged:
		{
			UpdateColor();
			break;
		}
		case MT_RenderSubmit:
		{
			const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
			RenderSubmit(msgData.collector, msgData.frustum, msgData.culling);
			break;
		}
		}
	}

private:
	struct RangeOverlayData {
		SOverlayDescriptor descriptor;
		std::unique_ptr<SOverlayTexturedLine> line;
	};

	void UpdateColor() override
	{
		CmpPtr<ICmpOwnership> cmpOwnership(GetEntityHandle());
		if (!cmpOwnership)
			return;

		player_id_t owner = cmpOwnership->GetOwner();
		if (owner == INVALID_PLAYER)
			return;

		CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSystemEntity());
		if (!cmpPlayerManager)
			return;

		CmpPtr<ICmpPlayer> cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(owner));
		if (!cmpPlayer)
			return;

		CColor color = cmpPlayer->GetDisplayedColor();
		m_Color = color;
	}

	void UpdateMessageSubscriptions()
	{
		if (m_Enabled == m_LastEnabledState)
			return;

		CComponentManager& cmpManager = GetSimContext().GetComponentManager();
		cmpManager.DynamicSubscriptionNonsync(MT_Interpolate, this, m_Enabled);
		cmpManager.DynamicSubscriptionNonsync(MT_RenderSubmit, this, m_Enabled);
		m_LastEnabledState = m_Enabled;
	}

	void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
	{
		if (!m_RangeOverlayData.size())
			return;

		for (const RangeOverlayData& rangeOverlay : m_RangeOverlayData)
		{
			if (!rangeOverlay.line)
				continue;
			if (culling && !rangeOverlay.line->IsVisibleInFrustum(frustum))
				continue;
			collector.Submit(rangeOverlay.line.get());
		}
	}

	void UpdateRangeOverlay(RangeOverlayData& rangeOverlay, const float frameOffset)
	{
		if (!CRenderer::IsInitialised())
			return;

		CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
		if (!cmpPosition || !cmpPosition->IsInWorld())
			return;

		float rotY;
		CVector2D origin;
		cmpPosition->GetInterpolatedPosition2D(frameOffset, origin.X, origin.Y, rotY);

		rangeOverlay.line = std::make_unique<SOverlayTexturedLine>();
		rangeOverlay.line->m_SimContext = &GetSimContext();
		rangeOverlay.line->m_Color = m_Color;
		rangeOverlay.line->CreateOverlayTexture(&rangeOverlay.descriptor);

		SimRender::ConstructTexturedLineCircle(*rangeOverlay.line.get(), origin, rangeOverlay.descriptor.m_Radius);
	}

	bool m_LastEnabledState;
	bool m_Enabled;

	const char* TEXTURE_BASE_PATH = "art/textures/selection/";
	std::vector<RangeOverlayData> m_RangeOverlayData;
	CColor m_Color;
};

REGISTER_COMPONENT_TYPE(RangeOverlayRenderer)
