//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Provides structures and classes necessary to simulate a portal.
//
// $NoKeywords: $
//=====================================================================================//

#ifndef PORTALSIMULATION_H
#define PORTALSIMULATION_H

#ifdef _WIN32
#pragma once
#endif

#include "mathlib/polyhedron.h"
#include "const.h"
#include "tier1/utlmap.h"
#include "tier1/utlvector.h"

#define PORTAL_SIMULATORS_EMBED_GUID //define this to embed a unique integer with each portal simulator for debugging purposes

struct StaticPropPolyhedronGroups_t //each static prop is made up of a group of polyhedrons, these help us pull those groups from an array
{
	int iStartIndex;
	int iNumPolyhedrons;
};

enum PortalSimulationEntityFlags_t
{
	PSEF_OWNS_ENTITY = (1 << 0), //this environment is responsible for the entity's physics objects
	PSEF_OWNS_PHYSICS = (1 << 1),
	PSEF_IS_IN_PORTAL_HOLE = (1 << 2), //updated per-phyframe
	PSEF_CLONES_ENTITY_FROM_MAIN = (1 << 3), //entity is close enough to the portal to affect objects intersecting the portal
	//PSEF_HAS_LINKED_CLONE = (1 << 1), //this environment has a clone of the entity which is transformed from its linked portal
};

enum PS_PhysicsObjectSourceType_t
{
	PSPOST_LOCAL_BRUSHES,
	PSPOST_REMOTE_BRUSHES,
	PSPOST_LOCAL_STATICPROPS,
	PSPOST_REMOTE_STATICPROPS,
	PSPOST_HOLYWALL_TUBE
};

struct PortalTransformAsAngledPosition_t //a matrix transformation from this portal to the linked portal, stored as vector and angle transforms
{
	Vector ptOriginTransform;
	QAngle qAngleTransform;
};

inline bool LessFunc_Integer( const int &a, const int &b ) { return a < b; };


class CPortalSimulatorEventCallbacks //sends out notifications of events to game specific code
{
public:
	virtual void PortalSimulator_TookOwnershipOfEntity( CBaseEntity *pEntity ) { };
	virtual void PortalSimulator_ReleasedOwnershipOfEntity( CBaseEntity *pEntity ) { };

	virtual void PortalSimulator_TookPhysicsOwnershipOfEntity( CBaseEntity *pEntity ) { };
	virtual void PortalSimulator_ReleasedPhysicsOwnershipOfEntity( CBaseEntity *pEntity ) { };
};

//====================================================================================
// To any coder trying to understand the following nested structures....
//
// You may be wondering... why? wtf?
//
// The answer. The previous incarnation of server side portal simulation suffered
// terribly from evolving variables with increasingly cryptic names with no clear
// definition of what part of the system the variable was involved with.
//
// It's my hope that a nested structure with clear boundaries will eliminate that 
// horrible, awful, nasty, frustrating confusion. (It was really really bad). This
// system has the added benefit of pseudo-forcing a naming structure.
//
// Lastly, if it all roots in one struct, we can const reference it out to allow 
// easy reads without writes
//
// It's broken out like this to solve a few problems....
// 1. It cleans up intellisense when you don't actually define a structure
//		within a structure.
// 2. Shorter typenames when you want to have a pointer/reference deep within
//		the nested structure.
// 3. Needed at least one level removed from CPortalSimulator so
//		pointers/references could be made while the primary instance of the
//		data was private/protected.
//
// It may be slightly difficult to understand in it's broken out structure, but
// intellisense brings all the data together in a very cohesive manner for
// working with.
//====================================================================================

struct PS_PlacementData_t //stuff useful for geometric operations
{
	Vector ptCenter;
	QAngle qAngles;
	Vector vForward;
	Vector vUp;
	Vector vRight;
	VPlane PortalPlane;
	VMatrix matThisToLinked;
	VMatrix matLinkedToThis;
	PortalTransformAsAngledPosition_t ptaap_ThisToLinked;
	PortalTransformAsAngledPosition_t ptaap_LinkedToThis;
	CPhysCollide *pHoleShapeCollideable; //used to test if a collideable is in the hole, should NOT be collided against in general
	PS_PlacementData_t( void )
	{
		memset( this, 0, sizeof( PS_PlacementData_t ) );
	}
};

struct PS_SD_Static_World_Brushes_t
{
	CUtlVector<CPolyhedron *> Polyhedrons; //the building blocks of more complex collision
	CPhysCollide *pCollideable;
#ifndef CLIENT_DLL
	IPhysicsObject *pPhysicsObject;
	PS_SD_Static_World_Brushes_t() : pCollideable(NULL), pPhysicsObject(NULL) {};
#else
	PS_SD_Static_World_Brushes_t() : pCollideable(NULL) {};
#endif
	
};


struct PS_SD_Static_World_StaticProps_ClippedProp_t
{
	StaticPropPolyhedronGroups_t	PolyhedronGroup;
	CPhysCollide *					pCollide;
#ifndef CLIENT_DLL
	IPhysicsObject *				pPhysicsObject;
#endif
	IHandleEntity *					pSourceProp;

	int								iTraceContents;
	short							iTraceSurfaceProps;
	static CBaseEntity *			pTraceEntity;
	static const char *				szTraceSurfaceName; //same for all static props, here just for easy reference
	static const int				iTraceSurfaceFlags; //same for all static props, here just for easy reference
};

struct PS_SD_Static_World_StaticProps_t
{
	CUtlVector<CPolyhedron *> Polyhedrons; //the building blocks of more complex collision
	CUtlVector<PS_SD_Static_World_StaticProps_ClippedProp_t> ClippedRepresentations;
	bool bCollisionExists; //the shortcut to know if collideables exist for each prop
	bool bPhysicsExists; //the shortcut to know if physics obects exist for each prop
	PS_SD_Static_World_StaticProps_t( void ) : bCollisionExists( false ), bPhysicsExists( false ) { };
};

struct PS_SD_Static_World_t //stuff in front of the portal
{
	PS_SD_Static_World_Brushes_t Brushes;
	PS_SD_Static_World_StaticProps_t StaticProps;
};

struct PS_SD_Static_Wall_Local_Tube_t //a minimal tube, an object must fit inside this to be eligible for portaling
{
	CUtlVector<CPolyhedron *> Polyhedrons; //the building blocks of more complex collision
	CPhysCollide *pCollideable;

#ifndef CLIENT_DLL
	IPhysicsObject *pPhysicsObject;
	PS_SD_Static_Wall_Local_Tube_t() : pCollideable(NULL), pPhysicsObject(NULL) {};
#else
	PS_SD_Static_Wall_Local_Tube_t() : pCollideable(NULL) {};
#endif
};

struct PS_SD_Static_Wall_Local_Brushes_t 
{
	CUtlVector<CPolyhedron *> Polyhedrons; //the building blocks of more complex collision
	CPhysCollide *pCollideable;

#ifndef CLIENT_DLL
	IPhysicsObject *pPhysicsObject;
	PS_SD_Static_Wall_Local_Brushes_t() : pCollideable(NULL), pPhysicsObject(NULL) {};
#else
	PS_SD_Static_Wall_Local_Brushes_t() : pCollideable(NULL) {};
#endif
};

struct PS_SD_Static_Wall_Local_t //things in the wall that are completely independant of having a linked portal
{
	PS_SD_Static_Wall_Local_Tube_t Tube;
	PS_SD_Static_Wall_Local_Brushes_t Brushes;
};

struct PS_SD_Static_Wall_RemoteTransformedToLocal_Brushes_t
{
	IPhysicsObject *pPhysicsObject;
	PS_SD_Static_Wall_RemoteTransformedToLocal_Brushes_t() : pPhysicsObject(NULL) {};
};

struct PS_SD_Static_Wall_RemoteTransformedToLocal_StaticProps_t
{
	CUtlVector<IPhysicsObject *> PhysicsObjects;
};

struct PS_SD_Static_Wall_RemoteTransformedToLocal_t //things taken from the linked portal's "World" collision and transformed into local space
{
	PS_SD_Static_Wall_RemoteTransformedToLocal_Brushes_t Brushes;
	PS_SD_Static_Wall_RemoteTransformedToLocal_StaticProps_t StaticProps;
};

struct PS_SD_Static_Wall_t //stuff behind the portal
{
	PS_SD_Static_Wall_Local_t Local;
#ifndef CLIENT_DLL
	PS_SD_Static_Wall_RemoteTransformedToLocal_t RemoteTransformedToLocal;
#endif
};

struct PS_SD_Static_SurfaceProperties_t //surface properties to pretend every collideable here is using
{
	int contents;
	csurface_t surface;
	CBaseEntity *pEntity;
};

struct PS_SD_Static_t //stuff that doesn't move around
{
	PS_SD_Static_World_t World;
	PS_SD_Static_Wall_t Wall;
	PS_SD_Static_SurfaceProperties_t SurfaceProperties;
};

class CPhysicsShadowClone;

struct PS_SD_Dynamic_PhysicsShadowClones_t
{
	CUtlVector<CBaseEntity *> ShouldCloneFromMain; //a list of entities that should be cloned from main if physics simulation is enabled
													//in single-environment mode, this helps us track who should collide with who
	
	CUtlVector<CPhysicsShadowClone *> FromLinkedPortal;
};

struct PS_SD_Dynamic_t //stuff that moves around
{
	unsigned int EntFlags[MAX_EDICTS]; //flags maintained for every entity in the world based on its index

	PS_SD_Dynamic_PhysicsShadowClones_t ShadowClones;

	CUtlVector<CBaseEntity *> OwnedEntities;

	PS_SD_Dynamic_t()
	{
		memset( EntFlags, 0, sizeof( EntFlags ) );
	}
};

class CPSCollisionEntity;

struct PS_SimulationData_t //compartmentalized data for coherent management
{
	PS_SD_Static_t Static;

#ifndef CLIENT_DLL
	PS_SD_Dynamic_t Dynamic;

	IPhysicsEnvironment *pPhysicsEnvironment;
	CPSCollisionEntity *pCollisionEntity; //the entity we'll be tying physics objects to for collision

	PS_SimulationData_t() : pPhysicsEnvironment(NULL), pCollisionEntity(NULL) {};
#endif
};

struct PS_InternalData_t
{
	PS_PlacementData_t Placement;
	PS_SimulationData_t Simulation;
};


class CPortalSimulator
{
public:
	CPortalSimulator( void );
	~CPortalSimulator( void );

	void				MoveTo( const Vector &ptCenter, const QAngle &angles );
	void				ClearEverything( void );

	void				AttachTo( CPortalSimulator *pLinkedPortalSimulator );
	void				DetachFromLinked( void ); //detach portals to sever the connection, saves work when planning on moving both portals
	CPortalSimulator	*GetLinkedPortalSimulator( void ) const;

	void				SetPortalSimulatorCallbacks( CPortalSimulatorEventCallbacks *pCallbacks );
	
	bool				IsReadyToSimulate( void ) const; //is active and linked to another portal
	
	void				SetCollisionGenerationEnabled( bool bEnabled ); //enable/disable collision generation for the hole in the wall, needed for proper vphysics simulation
	bool				IsCollisionGenerationEnabled( void ) const;

	void				SetVPhysicsSimulationEnabled( bool bEnabled ); //enable/disable vphysics simulation. Will automatically update the linked portal to be the same
	bool				IsSimulatingVPhysics( void ) const; //this portal is setup to handle any physically simulated object, false means the portal is handling player movement only
	
	bool				EntityIsInPortalHole( CBaseEntity *pEntity ) const; //true if the entity is within the portal cutout bounds and crossing the plane. Not just *near* the portal
	bool				EntityHitBoxExtentIsInPortalHole( CBaseAnimating *pBaseAnimating ) const; //true if the entity is within the portal cutout bounds and crossing the plane. Not just *near* the portal
	void				RemoveEntityFromPortalHole( CBaseEntity *pEntity ); //if the entity is in the portal hole, this forcibly moves it out by any means possible

	bool				RayIsInPortalHole( const Ray_t &ray ) const; //traces a ray against the same detector for EntityIsInPortalHole(), bias is towards false positives

#ifndef CLIENT_DLL
	int				GetMoveableOwnedEntities( CBaseEntity **pEntsOut, int iEntOutLimit ); //gets owned entities that aren't either world or static props. Excludes fake portal ents such as physics clones

	static CPortalSimulator *GetSimulatorThatOwnsEntity( const CBaseEntity *pEntity ); //fairly cheap to call
	static CPortalSimulator *GetSimulatorThatCreatedPhysicsObject( const IPhysicsObject *pObject, PS_PhysicsObjectSourceType_t *pOut_SourceType = NULL );
	static void			Pre_UTIL_Remove( CBaseEntity *pEntity );
	static void			Post_UTIL_Remove( CBaseEntity *pEntity );

	//these three really should be made internal and the public interface changed to a "watch this entity" setup
	void				TakeOwnershipOfEntity( CBaseEntity *pEntity ); //general ownership, not necessarily physics ownership
	void				ReleaseOwnershipOfEntity( CBaseEntity *pEntity, bool bMovingToLinkedSimulator = false ); //if bMovingToLinkedSimulator is true, the code skips some steps that are going to be repeated when the entity is added to the other simulator
	void				ReleaseAllEntityOwnership( void ); //go back to not owning any entities

	//void				TeleportEntityToLinkedPortal( CBaseEntity *pEntity );
	void				StartCloningEntity( CBaseEntity *pEntity );
	void				StopCloningEntity( CBaseEntity *pEntity );

	bool				OwnsEntity( const CBaseEntity *pEntity ) const;
	bool				OwnsPhysicsForEntity( const CBaseEntity *pEntity ) const;

	bool				CreatedPhysicsObject( const IPhysicsObject *pObject, PS_PhysicsObjectSourceType_t *pOut_SourceType = NULL ) const; //true if the physics object was generated by this portal simulator

	static void			PrePhysFrame( void );
	static void			PostPhysFrame( void );

#endif //#ifndef CLIENT_DLL

#ifdef PORTAL_SIMULATORS_EMBED_GUID
	int					GetPortalSimulatorGUID( void ) const { return m_iPortalSimulatorGUID; };
#endif

protected:
	bool				m_bLocalDataIsReady; //this side of the portal is properly setup, no guarantees as to linkage to another portal
	bool				m_bSimulateVPhysics;
	bool				m_bGenerateCollision;
	bool				m_bSharedCollisionConfiguration; //when portals are in certain configurations, they need to cross-clip and share some collision data and things get nasty. For the love of all that is holy, pray that this is false.
	CPortalSimulator	*m_pLinkedPortal;
	bool				m_bInCrossLinkedFunction; //A flag to mark that we're already in a linked function and that the linked portal shouldn't call our side
	CPortalSimulatorEventCallbacks *m_pCallbacks; 
#ifdef PORTAL_SIMULATORS_EMBED_GUID
	int					m_iPortalSimulatorGUID;
#endif

	struct
	{
		bool			bPolyhedronsGenerated;
		bool			bLocalCollisionGenerated;
		bool			bLinkedCollisionGenerated;
		bool			bLocalPhysicsGenerated;
		bool			bLinkedPhysicsGenerated;
	} m_CreationChecklist;

	friend class CPSCollisionEntity;

#ifndef CLIENT_DLL //physics handled purely by server side
	void				TakePhysicsOwnership( CBaseEntity *pEntity );
	void				ReleasePhysicsOwnership( CBaseEntity *pEntity, bool bContinuePhysicsCloning = true, bool bMovingToLinkedSimulator = false );

	void				CreateAllPhysics( void );
	void				CreateMinimumPhysics( void ); //stuff needed by any part of physics simulations
	void				CreateLocalPhysics( void );
	void				CreateLinkedPhysics( void );

	void				ClearAllPhysics( void );
	void				ClearMinimumPhysics( void );
	void				ClearLocalPhysics( void );
	void				ClearLinkedPhysics( void );

	void				ClearLinkedEntities( void ); //gets rid of transformed shadow clones
#endif

	void				CreateAllCollision( void );
	void				CreateLocalCollision( void );
	void				CreateLinkedCollision( void );

	void				ClearAllCollision( void );
	void				ClearLinkedCollision( void );
	void				ClearLocalCollision( void );

	void				CreatePolyhedrons( void ); //carves up the world around the portal's position into sets of polyhedrons
	void				ClearPolyhedrons( void );

	void				UpdateLinkMatrix( void );

	void				MarkAsOwned( CBaseEntity *pEntity );
	void				MarkAsReleased( CBaseEntity *pEntity );

	PS_InternalData_t m_InternalData;

public:
	const PS_InternalData_t &m_DataAccess;

	friend class CPS_AutoGameSys_EntityListener;
};

extern CUtlVector<CPortalSimulator *> const &g_PortalSimulators;


#ifndef CLIENT_DLL
class CPSCollisionEntity : public CBaseEntity
{
	DECLARE_CLASS( CPSCollisionEntity, CBaseEntity );
private:
	CPortalSimulator *m_pOwningSimulator;

public:
	CPSCollisionEntity( void );
	virtual ~CPSCollisionEntity( void );

	virtual void	Spawn( void );
	virtual void	Activate( void );
	virtual int		ObjectCaps( void );
	virtual IPhysicsObject *VPhysicsGetObject( void );
	virtual int		VPhysicsGetObjectList( IPhysicsObject **pList, int listMax );
	virtual void	UpdateOnRemove( void );
	virtual	bool	ShouldCollide( int collisionGroup, int contentsMask ) const;
	virtual void	VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) {}
	virtual void	VPhysicsFriction( IPhysicsObject *pObject, float energy, int surfaceProps, int surfacePropsHit ) {}

	static bool		IsPortalSimulatorCollisionEntity( const CBaseEntity *pEntity );
	friend class CPortalSimulator;
};
#endif
















#ifndef CLIENT_DLL
inline bool CPortalSimulator::OwnsEntity( const CBaseEntity *pEntity ) const
{
	return ((m_InternalData.Simulation.Dynamic.EntFlags[pEntity->entindex()] & PSEF_OWNS_ENTITY) != 0);
}

inline bool CPortalSimulator::OwnsPhysicsForEntity( const CBaseEntity *pEntity ) const
{
	return ((m_InternalData.Simulation.Dynamic.EntFlags[pEntity->entindex()] & PSEF_OWNS_PHYSICS) != 0);
}
#endif

inline bool CPortalSimulator::IsReadyToSimulate( void ) const
{
	return m_bLocalDataIsReady && m_pLinkedPortal && m_pLinkedPortal->m_bLocalDataIsReady;
}

inline bool CPortalSimulator::IsSimulatingVPhysics( void ) const
{
	return m_bSimulateVPhysics;
}

inline bool CPortalSimulator::IsCollisionGenerationEnabled( void ) const
{
	return m_bGenerateCollision;
}

inline CPortalSimulator	*CPortalSimulator::GetLinkedPortalSimulator( void ) const
{
	return m_pLinkedPortal;
}




#endif //#ifndef PORTALSIMULATION_H