/* 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/>.
 */

#ifndef INCLUDED_ICMPOBSTRUCTIONMANAGER
#define INCLUDED_ICMPOBSTRUCTIONMANAGER

#include "lib/debug.h"
#include "lib/types.h"
#include "maths/Fixed.h"
#include "maths/FixedVector2D.h"
#include "simulation2/helpers/Position.h"
#include "simulation2/system/Component.h"
#include "simulation2/system/Entity.h"
#include "simulation2/system/Interface.h"

#include <js/Value.h>
#include <vector>

class IObstructionTestFilter;
class PathfinderPassability;
struct GridUpdateInformation;
template<typename T> class Grid;

using NavcellData = u16;

/**
 * Obstruction manager: provides efficient spatial queries over objects in the world.
 *
 * The class deals with two types of shape:
 * "static" shapes, typically representing buildings, which are rectangles with a given
 * width and height and angle;
 * and "unit" shapes, representing units that can move around the world, which have a
 * radius and no rotation. (Units sometimes act as axis-aligned squares, sometimes
 * as approximately circles, due to the algorithm used by the short pathfinder.)
 *
 * Other classes (particularly ICmpObstruction) register shapes with this interface
 * and keep them updated.
 *
 * The @c Test functions provide exact collision tests.
 * The edge of a shape counts as 'inside' the shape, for the purpose of collisions.
 * The functions accept an IObstructionTestFilter argument, which can restrict the
 * set of shapes that are counted as collisions.
 *
 * Units can be marked as either moving or stationary, which simply determines whether
 * certain filters include or exclude them.
 *
 * The @c Rasterize function approximates the current set of shapes onto a 2D grid,
 * for use with tile-based pathfinding.
 */
class ICmpObstructionManager : public IComponent
{
public:
	/**
	 * Standard representation for all types of shapes, for use with geometry processing code.
	 */
	struct ObstructionSquare
	{
		entity_pos_t x, z; // position of center
		CFixedVector2D u, v; // 'horizontal' and 'vertical' orthogonal unit vectors, representing orientation
		entity_pos_t hw, hh; // half width, half height of square
	};

	/**
	 * External identifiers for shapes.
	 * (This is a struct rather than a raw u32 for type-safety.)
	 */
	struct tag_t
	{
		tag_t() : n(0) {}
		explicit tag_t(u32 n) : n(n) {}
		bool valid() const { return n != 0; }

		u32 n;
	};

	/**
	 * Boolean flags affecting the obstruction behaviour of a shape.
	 */
	enum EFlags
	{
		FLAG_BLOCK_MOVEMENT           = (1 << 0), // prevents units moving through this shape
		FLAG_BLOCK_FOUNDATION         = (1 << 1), // prevents foundations being placed on this shape
		FLAG_BLOCK_CONSTRUCTION       = (1 << 2), // prevents buildings being constructed on this shape
		FLAG_BLOCK_PATHFINDING        = (1 << 3), // prevents the tile pathfinder choosing paths through this shape
		FLAG_MOVING                   = (1 << 4), // reserved for unitMotion - see usage there.
		FLAG_DELETE_UPON_CONSTRUCTION = (1 << 5)  // this entity is deleted when construction of a building placed on top of this entity starts
	};

	/**
	 * Bitmask of EFlag values.
	 */
	typedef u8 flags_t;

	/**
	 * Set the bounds of the world.
	 * Any point outside the bounds is considered obstructed.
	 * @param x0,z0,x1,z1 Coordinates of the corners of the world
	 */
	virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1) = 0;

	/**
	 * Register a static shape.
	 *
	 * @param ent entity ID associated with this shape (or INVALID_ENTITY if none)
	 * @param x,z coordinates of center, in world space
	 * @param a angle of rotation (clockwise from +Z direction)
	 * @param w width (size along X axis)
	 * @param h height (size along Z axis)
	 * @param flags a set of EFlags values
	 * @param group primary control group of the shape. Must be a valid control group ID.
	 * @param group2 Optional; secondary control group of the shape. Defaults to INVALID_ENTITY.
	 * @return a valid tag for manipulating the shape
	 * @see StaticShape
	 */
	virtual tag_t AddStaticShape(entity_id_t ent, entity_pos_t x, entity_pos_t z, entity_angle_t a,
		entity_pos_t w, entity_pos_t h, flags_t flags, entity_id_t group, entity_id_t group2 = INVALID_ENTITY) = 0;

	/**
	 * Register a unit shape.
	 *
	 * @param ent entity ID associated with this shape (or INVALID_ENTITY if none)
	 * @param x,z coordinates of center, in world space
	 * @param clearance pathfinding clearance of the unit (works as a radius)
	 * @param flags a set of EFlags values
	 * @param group control group (typically the owner entity, or a formation controller entity
	 *	- units ignore collisions with others in the same group)
	 * @return a valid tag for manipulating the shape
	 * @see UnitShape
	 */
	virtual tag_t AddUnitShape(entity_id_t ent, entity_pos_t x, entity_pos_t z, entity_pos_t clearance,
		flags_t flags, entity_id_t group) = 0;

	/**
	 * Adjust the position and angle of an existing shape.
	 * @param tag tag of shape (must be valid)
	 * @param x X coordinate of center, in world space
	 * @param z Z coordinate of center, in world space
	 * @param a angle of rotation (clockwise from +Z direction); ignored for unit shapes
	 */
	virtual void MoveShape(tag_t tag, entity_pos_t x, entity_pos_t z, entity_angle_t a) = 0;

	/**
	 * Set whether a unit shape is moving or stationary.
	 * @param tag tag of shape (must be valid and a unit shape)
	 * @param moving whether the unit is currently moving through the world or is stationary
	 */
	virtual void SetUnitMovingFlag(tag_t tag, bool moving) = 0;

	/**
	 * Set the control group of a unit shape.
	 * @param tag tag of shape (must be valid and a unit shape)
	 * @param group control group entity ID
	 */
	virtual void SetUnitControlGroup(tag_t tag, entity_id_t group) = 0;

	/**
	 * Sets the control group of a static shape.
	 * @param tag Tag of the shape to set the control group for. Must be a valid and static shape tag.
	 * @param group Control group entity ID.
	 */
	virtual void SetStaticControlGroup(tag_t tag, entity_id_t group, entity_id_t group2) = 0;

	/**
	 * Remove an existing shape. The tag will be made invalid and must not be used after this.
	 * @param tag tag of shape (must be valid)
	 */
	virtual void RemoveShape(tag_t tag) = 0;

	/**
	 * Returns the distance from the obstruction to the point (px, pz), or -1 if the entity is out of the world.
	 */
	virtual fixed DistanceToPoint(entity_id_t ent, entity_pos_t px, entity_pos_t pz) const = 0;

	/**
	 * Calculate the largest straight line distance between the entity and the point.
	 */
	virtual fixed MaxDistanceToPoint(entity_id_t ent, entity_pos_t px, entity_pos_t pz) const = 0;

	/**
	 * Calculate the shortest distance between the entity and the target.
	 */
	virtual fixed DistanceToTarget(entity_id_t ent, entity_id_t target) const = 0;

	/**
	 * Calculate the largest straight line distance between the entity and the target.
	 */
	virtual fixed MaxDistanceToTarget(entity_id_t ent, entity_id_t target) const = 0;

	/**
	 * Calculate the shortest straight line distance between the source and the target
	 */
	virtual fixed DistanceBetweenShapes(const ObstructionSquare& source, const ObstructionSquare& target) const = 0;

	/**
	 * Calculate the largest straight line distance between the source and the target
	 */
	virtual fixed MaxDistanceBetweenShapes(const ObstructionSquare& source, const ObstructionSquare& target) const = 0;

	/**
	 * Check if the given entity is in range of the other point given those parameters.
	 * @param maxRange - Can be a nonnegative decimal, ALWAYS_IN_RANGE or NEVER_IN_RANGE.
	 */
	virtual bool IsInPointRange(entity_id_t ent, entity_pos_t px, entity_pos_t pz, entity_pos_t minRange, entity_pos_t maxRange, bool opposite) const = 0;

	/**
	 * Check if the given entity is in range of the target given those parameters.
	 * @param maxRange - Can be a nonnegative decimal, ALWAYS_IN_RANGE or NEVER_IN_RANGE.
	 */
	virtual bool IsInTargetRange(entity_id_t ent, entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, bool opposite) const = 0;

	/**
	 * Check if the given entity is in parabolic range of the target given those parameters.
	 * @param maxRange - Can be a nonnegative decimal, ALWAYS_IN_RANGE or NEVER_IN_RANGE.
	 */
	virtual bool IsInTargetParabolicRange(entity_id_t ent, entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t yOrigin, bool opposite) const = 0;

	/**
	 * Check if the given point is in range of the other point given those parameters.
	 * @param maxRange - Can be a nonnegative decimal, ALWAYS_IN_RANGE or NEVER_IN_RANGE.
	 */
	virtual bool IsPointInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t px, entity_pos_t pz, entity_pos_t minRange, entity_pos_t maxRange) const = 0;

	/**
	 * Check if the given shape is in range of the target shape given those parameters.
	 * @param maxRange - Can be a nonnegative decimal, ALWAYS_IN_RANGE or NEVER_IN_RANGE.
	 */
	virtual bool AreShapesInRange(const ObstructionSquare& source, const ObstructionSquare& target, entity_pos_t minRange, entity_pos_t maxRange, bool opposite) const = 0;

	/**
	 * Collision test a flat-ended thick line against the current set of shapes.
	 * The line caps extend by @p r beyond the end points.
	 * Only intersections going from outside to inside a shape are counted.
	 * @param filter filter to restrict the shapes that are counted
	 * @param x0 X coordinate of line's first point
	 * @param z0 Z coordinate of line's first point
	 * @param x1 X coordinate of line's second point
	 * @param z1 Z coordinate of line's second point
	 * @param r radius (half width) of line
	 * @param relaxClearanceForUnits whether unit-unit collisions should be more permissive.
	 * @return true if there is a collision
	 */
	virtual bool TestLine(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, bool relaxClearanceForUnits) const = 0;

	virtual bool TestUnitLine(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, bool relaxClearanceForUnits) const = 0;

	/**
	 * Collision test a static square shape against the current set of shapes.
	 * @param filter filter to restrict the shapes that are being tested against
	 * @param x X coordinate of center
	 * @param z Z coordinate of center
	 * @param a angle of rotation (clockwise from +Z direction)
	 * @param w width (size along X axis)
	 * @param h height (size along Z axis)
	 * @param out if non-NULL, all colliding shapes' entities will be added to this list
	 * @return true if there is a collision
	 */
	virtual bool TestStaticShape(const IObstructionTestFilter& filter,
		entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h,
		std::vector<entity_id_t>* out) const = 0;

	/**
	 * Collision test a unit shape against the current set of registered shapes, and optionally writes a list of the colliding
	 * shapes' entities to an output list.
	 *
	 * @param filter filter to restrict the shapes that are being tested against
	 * @param x X coordinate of shape's center
	 * @param z Z coordinate of shape's center
	 * @param clearance clearance of the shape's unit
	 * @param out if non-NULL, all colliding shapes' entities will be added to this list
	 *
	 * @return true if there is a collision
	 */
	virtual bool TestUnitShape(const IObstructionTestFilter& filter,
		entity_pos_t x, entity_pos_t z, entity_pos_t clearance,
		std::vector<entity_id_t>* out) const = 0;

	/**
	 * Convert the current set of shapes onto a navcell grid, for all passability classes contained in @p passClasses.
	 * If @p fullUpdate is false, the function will only go through dirty shapes.
	 * Shapes are expanded by the @p passClasses clearances, by ORing their masks onto the @p grid.
	 */
	virtual void Rasterize(Grid<NavcellData>& grid, const std::vector<PathfinderPassability>& passClasses, bool fullUpdate) = 0;

	/**
	 * Gets dirtiness information and resets it afterwards. Then it's the role of CCmpPathfinder
	 * to pass the information to other components if needed. (AIs, etc.)
	 * The return value is false if an update is unnecessary.
	 */
	virtual void UpdateInformations(GridUpdateInformation& informations) = 0;

	/**
	 * Find all the obstructions that are inside (or partially inside) the given range.
	 * @param filter filter to restrict the shapes that are counted
	 * @param x0 X coordinate of left edge of range
	 * @param z0 Z coordinate of bottom edge of range
	 * @param x1 X coordinate of right edge of range
	 * @param z1 Z coordinate of top edge of range
	 * @param squares output list of obstructions
	 */
	virtual void GetObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector<ObstructionSquare>& squares) const = 0;
	virtual void GetStaticObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector<ObstructionSquare>& squares) const = 0;
	virtual void GetUnitObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector<ObstructionSquare>& squares) const = 0;
	virtual void GetStaticObstructionsOnObstruction(const ObstructionSquare& square, std::vector<entity_id_t>& out, const IObstructionTestFilter& filter) const = 0;

	/**
	 * Returns the entity IDs of all unit shapes that intersect the given
	 * obstruction square, filtering out using the given filter.
	 * @param square the Obstruction squre we want to compare with.
	 * @param out output list of obstructions
	 * @param filter filter for the obstructing units
	 * @param strict whether to be strict in the check or more permissive (ie rasterize more or less). Default false.
	 */
	virtual void GetUnitsOnObstruction(const ObstructionSquare& square, std::vector<entity_id_t>& out, const IObstructionTestFilter& filter, bool strict = false) const = 0;

	/**
	 * Get the obstruction square representing the given shape.
	 * @param tag tag of shape (must be valid)
	 */
	virtual ObstructionSquare GetObstruction(tag_t tag) const = 0;

	virtual ObstructionSquare GetUnitShapeObstruction(entity_pos_t x, entity_pos_t z, entity_pos_t clearance) const = 0;

	virtual ObstructionSquare GetStaticShapeObstruction(entity_pos_t x, entity_pos_t z, entity_angle_t a, entity_pos_t w, entity_pos_t h) const = 0;

	/**
	 * Set the passability to be restricted to a circular map.
	 */
	virtual void SetPassabilityCircular(bool enabled) = 0;

	virtual bool GetPassabilityCircular() const = 0;

	/**
	 * Toggle the rendering of debug info.
	 */
	virtual void SetDebugOverlay(bool enabled) = 0;

	DECLARE_INTERFACE_TYPE(ObstructionManager)
};

/**
 * Interface for ICmpObstructionManager @c Test functions to filter out unwanted shapes.
 */
class IObstructionTestFilter
{
public:
	typedef ICmpObstructionManager::tag_t tag_t;
	typedef ICmpObstructionManager::flags_t flags_t;

	virtual ~IObstructionTestFilter() {}

	/**
	 * Return true if the shape with the specified parameters should be tested for collisions.
	 * This is called for all shapes that would collide, and also for some that wouldn't.
	 *
	 * @param tag tag of shape being tested
	 * @param flags set of EFlags for the shape
	 * @param group the control group of the shape (typically the shape's unit, or the unit's formation controller, or 0)
	 * @param group2 an optional secondary control group of the shape, or INVALID_ENTITY if none specified. Currently
	 *               exists only for static shapes.
	 */
	virtual bool TestShape(tag_t tag, flags_t flags, entity_id_t group, entity_id_t group2) const = 0;
};

/**
 * Obstruction test filter that will test against all shapes.
 */
class NullObstructionFilter : public IObstructionTestFilter
{
public:
	virtual bool TestShape(tag_t, flags_t /*flags*/, entity_id_t /*group*/, entity_id_t /*group2*/) const
	{
		return true;
	}
};

/**
 * Obstruction test filter that will test only against stationary (i.e. non-moving) shapes.
 */
class StationaryOnlyObstructionFilter : public IObstructionTestFilter
{
public:
	virtual bool TestShape(tag_t, flags_t flags, entity_id_t /*group*/, entity_id_t /*group2*/) const
	{
		return !(flags & ICmpObstructionManager::FLAG_MOVING);
	}
};

/**
 * Obstruction test filter that reject shapes in a given control group,
 * and rejects shapes that don't block unit movement, and optionally rejects moving shapes.
 */
class ControlGroupMovementObstructionFilter : public IObstructionTestFilter
{
	bool m_AvoidMoving;
	entity_id_t m_Group;

public:
	ControlGroupMovementObstructionFilter(bool avoidMoving, entity_id_t group) :
		m_AvoidMoving(avoidMoving), m_Group(group)
	{}

	virtual bool TestShape(tag_t, flags_t flags, entity_id_t group, entity_id_t group2) const
	{
		if (group == m_Group || (group2 != INVALID_ENTITY && group2 == m_Group))
			return false;

		if (!(flags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT))
			return false;

		if ((flags & ICmpObstructionManager::FLAG_MOVING) && !m_AvoidMoving)
			return false;

		return true;
	}
};

/**
 * Obstruction test filter that will test only against shapes that:
 *     - are part of neither one of the specified control groups
 *     - AND, depending on the value of the 'exclude' argument:
 *       - have at least one of the specified flags set.
 *       - OR have none of the specified flags set.
 *
 * The first (primary) control group to reject shapes from must be specified and valid. The secondary
 * control group to reject entities from may be set to INVALID_ENTITY to not use it.
 *
 * This filter is useful to e.g. allow foundations within the same control group to be placed and
 * constructed arbitrarily close together (e.g. for wall pieces that need to link up tightly).
 */
class SkipControlGroupsRequireFlagObstructionFilter : public IObstructionTestFilter
{
	bool m_Exclude;
	entity_id_t m_Group;
	entity_id_t m_Group2;
	flags_t m_Mask;

public:
	SkipControlGroupsRequireFlagObstructionFilter(bool exclude, entity_id_t group1, entity_id_t group2, flags_t mask) :
		m_Exclude(exclude), m_Group(group1), m_Group2(group2), m_Mask(mask)
	{
		Init();
	}

	SkipControlGroupsRequireFlagObstructionFilter(entity_id_t group1, entity_id_t group2, flags_t mask) :
		m_Exclude(false), m_Group(group1), m_Group2(group2), m_Mask(mask)
	{
		Init();
	}

	virtual bool TestShape(tag_t, flags_t flags, entity_id_t group, entity_id_t group2) const
	{
		// Don't test shapes that share one or more of our control groups.
		if (group == m_Group || group == m_Group2 || (group2 != INVALID_ENTITY &&
				(group2 == m_Group || group2 == m_Group2)))
			return false;

		// If m_Exclude is true, don't test against shapes that have any of the
		// obstruction flags specified in m_Mask.
		if (m_Exclude)
			return (flags & m_Mask) == 0;

		// Otherwise, only include shapes that match at least one flag in m_Mask.
		return (flags & m_Mask) != 0;
	}
private:
	void Init()
	{
		// the primary control group to filter out must be valid
		ENSURE(m_Group != INVALID_ENTITY);

		// for simplicity, if m_Group2 is INVALID_ENTITY (i.e. not used), then set it equal to m_Group
		// so that we have fewer special cases to consider in TestShape().
		if (m_Group2 == INVALID_ENTITY)
			m_Group2 = m_Group;
	}
};

/**
 * Obstruction test filter that will test only against shapes that:
 *     - are part of both of the specified control groups
 *     - AND have at least one of the specified flags set.
 *
 * The first (primary) control group to include shapes from must be specified and valid.
 *
 * This filter is useful for preventing entities with identical control groups
 * from colliding e.g. building a new wall segment on top of an existing wall)
 *
 * @todo This filter needs test cases.
 */
class SkipTagRequireControlGroupsAndFlagObstructionFilter : public IObstructionTestFilter
{
	tag_t m_Tag;
	entity_id_t m_Group;
	entity_id_t m_Group2;
	flags_t m_Mask;

public:
	SkipTagRequireControlGroupsAndFlagObstructionFilter(tag_t tag, entity_id_t group1, entity_id_t group2, flags_t mask) :
		m_Tag(tag), m_Group(group1), m_Group2(group2), m_Mask(mask)
	{
		ENSURE(m_Group != INVALID_ENTITY);
	}

	virtual bool TestShape(tag_t tag, flags_t flags, entity_id_t group, entity_id_t group2) const
	{
		// To be included in testing, a shape must not have the specified tag, and must
		// match at least one of the flags in m_Mask, as well as both control groups.
		return (tag.n != m_Tag.n && (flags & m_Mask) != 0 && ((group == m_Group
			&& group2 == m_Group2) || (group2 == m_Group && group == m_Group2)));
	}
};

/**
 * Obstruction test filter that will test only against shapes that do not have the specified tag set.
 */
class SkipTagObstructionFilter : public IObstructionTestFilter
{
	tag_t m_Tag;
public:
	SkipTagObstructionFilter(tag_t tag) : m_Tag(tag)
	{
	}

	virtual bool TestShape(tag_t tag, flags_t, entity_id_t /*group*/, entity_id_t /*group2*/) const
	{
		return tag.n != m_Tag.n;
	}
};

/**
 * Similar to ControlGroupMovementObstructionFilter, but also ignoring a specific tag. See D3482 for why this exists.
 */
class SkipTagAndControlGroupObstructionFilter : public IObstructionTestFilter
{
	entity_id_t m_Group;
	tag_t m_Tag;
	bool m_AvoidMoving;

public:
	SkipTagAndControlGroupObstructionFilter(tag_t tag, bool avoidMoving,  entity_id_t group) :
	m_Tag(tag), m_Group(group), m_AvoidMoving(avoidMoving)
	{}

	virtual bool TestShape(tag_t tag, flags_t flags, entity_id_t group, entity_id_t group2) const
	{
		if (tag.n == m_Tag.n)
			return false;

		if (group == m_Group || (group2 != INVALID_ENTITY && group2 == m_Group))
			return false;

		if ((flags & ICmpObstructionManager::FLAG_MOVING) && !m_AvoidMoving)
			return false;

		if (!(flags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT))
			return false;

		return true;
	}
};


/**
 * Obstruction test filter that will test only against shapes that:
 *    - do not have the specified tag
 *    - AND have at least one of the specified flags set.
 */
class SkipTagRequireFlagsObstructionFilter : public IObstructionTestFilter
{
	tag_t m_Tag;
	flags_t m_Mask;
public:
	SkipTagRequireFlagsObstructionFilter(tag_t tag, flags_t mask) : m_Tag(tag), m_Mask(mask)
	{
	}

	virtual bool TestShape(tag_t tag, flags_t flags, entity_id_t /*group*/, entity_id_t /*group2*/) const
	{
		return (tag.n != m_Tag.n && (flags & m_Mask) != 0);
	}
};

#endif // INCLUDED_ICMPOBSTRUCTIONMANAGER
