// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT

#pragma once

JPH_NAMESPACE_BEGIN

class Body;
class TransformedShape;

/// Traits to use for CastRay
class CollisionCollectorTraitsCastRay
{
public:
	/// For rays the early out fraction is the fraction along the line to order hits.
	static constexpr float InitialEarlyOutFraction = 1.0f + FLT_EPSILON;	///< Furthest hit: Fraction is 1 + epsilon
	static constexpr float ShouldEarlyOutFraction = 0.0f;					///< Closest hit: Fraction is 0
};

/// Traits to use for CastShape
class CollisionCollectorTraitsCastShape
{
public:
	/// For rays the early out fraction is the fraction along the line to order hits.
	static constexpr float InitialEarlyOutFraction = 1.0f + FLT_EPSILON;	///< Furthest hit: Fraction is 1 + epsilon
	static constexpr float ShouldEarlyOutFraction = -FLT_MAX;				///< Deepest hit: Penetration is infinite
};

/// Traits to use for CollideShape
class CollisionCollectorTraitsCollideShape
{
public:
	/// For shape collisions we use -penetration depth to order hits.
	static constexpr float InitialEarlyOutFraction = FLT_MAX;				///< Most shallow hit: Separation is infinite
	static constexpr float ShouldEarlyOutFraction = -FLT_MAX;				///< Deepest hit: Penetration is infinite
};

/// Traits to use for CollidePoint
using CollisionCollectorTraitsCollidePoint = CollisionCollectorTraitsCollideShape;

/// Virtual interface that allows collecting multiple collision results
template <class ResultTypeArg, class TraitsType>
class CollisionCollector
{
public:
	/// Declare ResultType so that derived classes can use it
	using ResultType = ResultTypeArg;

	/// Default constructor
							CollisionCollector() = default;

	/// Constructor to initialize from another collector
	template <class ResultTypeArg2>
	explicit				CollisionCollector(const CollisionCollector<ResultTypeArg2, TraitsType> &inRHS) : mEarlyOutFraction(inRHS.GetEarlyOutFraction()), mContext(inRHS.GetContext()) { }
							CollisionCollector(const CollisionCollector<ResultTypeArg, TraitsType> &inRHS) = default;

	/// Destructor
	virtual					~CollisionCollector() = default;

	/// If you want to reuse this collector, call Reset()
	virtual void			Reset()											{ mEarlyOutFraction = TraitsType::InitialEarlyOutFraction; }

	/// When running a query through the NarrowPhaseQuery class, this will be called for every body that is potentially colliding.
	/// It allows collecting additional information needed by the collision collector implementation from the body under lock protection
	/// before AddHit is called (e.g. the user data pointer or the velocity of the body).
	virtual void			OnBody([[maybe_unused]] const Body &inBody)		{ /* Collects nothing by default */ }

	/// When running a query through the NarrowPhaseQuery class, this will be called after all AddHit calls have been made for a particular body.
	virtual void			OnBodyEnd()										{ /* Does nothing by default */ }

	/// Set by the collision detection functions to the current TransformedShape that we're colliding against before calling the AddHit function.
	/// Note: Only valid during AddHit! For performance reasons, the pointer is not reset after leaving AddHit so the context may point to freed memory.
	void					SetContext(const TransformedShape *inContext)	{ mContext = inContext; }
	const TransformedShape *GetContext() const								{ return mContext; }

	/// This function can be used to set some user data on the collision collector
	virtual void			SetUserData(uint64 inUserData)					{ /* Does nothing by default */ }

	/// This function will be called for every hit found, it's up to the application to decide how to store the hit
	virtual void			AddHit(const ResultType &inResult) = 0;

	/// Update the early out fraction (should be lower than before)
	inline void				UpdateEarlyOutFraction(float inFraction)		{ JPH_ASSERT(inFraction <= mEarlyOutFraction); mEarlyOutFraction = inFraction; }

	/// Reset the early out fraction to a specific value
	inline void				ResetEarlyOutFraction(float inFraction = TraitsType::InitialEarlyOutFraction) { mEarlyOutFraction = inFraction; }

	/// Force the collision detection algorithm to terminate as soon as possible. Call this from the AddHit function when a satisfying hit is found.
	inline void				ForceEarlyOut()									{ mEarlyOutFraction = TraitsType::ShouldEarlyOutFraction; }

	/// When true, the collector will no longer accept any additional hits and the collision detection routine should early out as soon as possible
	inline bool				ShouldEarlyOut() const							{ return mEarlyOutFraction <= TraitsType::ShouldEarlyOutFraction; }

	/// Get the current early out value
	inline float			GetEarlyOutFraction() const						{ return mEarlyOutFraction; }

	/// Get the current early out value but make sure it's bigger than zero, this is used for shape casting as negative values are used for penetration
	inline float			GetPositiveEarlyOutFraction() const				{ return max(FLT_MIN, mEarlyOutFraction); }

private:
	/// The early out fraction determines the fraction below which the collector is still accepting a hit (can be used to reduce the amount of work)
	float					mEarlyOutFraction = TraitsType::InitialEarlyOutFraction;

	/// Set by the collision detection functions to the current TransformedShape of the body that we're colliding against before calling the AddHit function
	const TransformedShape *mContext = nullptr;
};

JPH_NAMESPACE_END
