/* Copyright (C) 2009 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/>.
 */

// Entity.h
//
// Entity class.
//
// Usage: Do not attempt to instantiate this class directly. (See EntityManager.h)
//		  Most of the members are trivially obvious; some highlights are:
//
//			HEntity me: is a reference to this entity. See EntityHandles.h
//
//			Destroying entities: An entity is destroyed when all references to it expire.
//								 It is somewhat unfunny if this happens while a method from this
//                               class is still executing. If you need to kill an entity,
//								 use CEntity::Kill(). If Kill() releases the final handle,
//								 the entity is placed in the reaper queue and is deleted immediately
//								 prior to its next update cycle.
//
//			CUnit* m_actor: is the visible representation of this entity.
//
//			SnapToGround(): Called every frame, this will ensure the entity never takes flight.
//			UpdateActorTransforms(): Must be called every time the position of this entity changes.
//			Also remember to update the collision object if you alter the position directly.
//

#ifndef INCLUDED_ENTITY
#define INCLUDED_ENTITY

#include <boost/unordered_map.hpp>

#include <deque>
#include "scripting/ScriptableComplex.h"
#include "ps/Vector2D.h"
#include "maths/Vector3D.h"
#include "EntityOrders.h"
#include "EntityHandles.h"
#include "ScriptObject.h"
#include "EntitySupport.h"
#include "scripting/DOMEvent.h"
#include "scripting/ScriptCustomTypes.h"
#include "ps/scripting/JSCollection.h"

class CAura;
class CEntityTemplate;
class CBoundingObject;
class CPlayer;
class CProductionQueue;
class CSkeletonAnim;
class CUnit;
class CTerritory;
class CEntityFormation;
class CStance;

// 
enum EntityFlags
{
	// If this unit has been removed from the gameworld but has still
	// has references.
	ENTF_DESTROYED                  = 0x0001,

	// State transition in the FSM (animations should be reset)
	ENTF_TRANSITION                 = 0x0002,

	ENTF_HAS_RALLY_POINT            = 0x0004,

	ENTF_HEALTH_DECAY               = 0x0008,

	// if set, we destroy them; otherwise, the script does.
	ENTF_DESTROY_NOTIFIERS          = 0x0010,

	// is it actually running
	ENTF_IS_RUNNING                 = 0x0020,
	// if run was issued, it will remain true until it is stopped
	ENTF_SHOULD_RUN                 = 0x0040,
	// used in SetRun, corrects 1 frame stamina imbalance
	ENTF_TRIGGER_RUN                = 0x0080
};

// TODO MT: Put this is /some/ sort of order...

class CEntity : public CJSComplex<CEntity>, public IEventTarget
{
	NONCOPYABLE(CEntity);

	friend class CEntityManager;
	friend class CUnit;

	typedef boost::unordered_map<CStrW, CAura*> AuraTable;
	typedef boost::unordered_map<int, SEntityAction> ActionTable;
	typedef std::set<CAura*> AuraSet;

public:
	// Intrinsic properties
	CEntityTemplate* m_base;

	// The class types this entity has
	CClassSet m_classes;

	// Production queue
	CProductionQueue* m_productionQueue;

	// Unit AI stance
	CStance* m_stance;

	// Movement properties
	float m_speed;
	float m_turningRadius;
	float m_runSpeed;
	float m_runRegenRate;
	float m_runDecayRate;

	float m_maxActorPitch;
	float m_minActorPitch;

	float m_healthRegenRate;
	float m_healthRegenStart;
	float m_healthDecayRate;

	float m_runMaxRange;
	float m_runMinRange;
	
	ActionTable m_actions;

	bool m_selected;
	ssize_t m_grouped;	// group index or -1 if not grouped
	ssize_t m_formation;	// Index of which formation we're in or -1
	ssize_t m_formationSlot;	// The slot of the above formation

	int ent_flags;
	bool entf_get(int desired_flag) const { return (ent_flags & desired_flag) != 0; }
	void entf_set(int desired_flag) { ent_flags |= desired_flag; }
	void entf_clear(int desired_flag) { ent_flags &= ~desired_flag; }
	void entf_set_to(int desired_flag, bool value)
	{
		ent_flags &= ~desired_flag;
		const int mask = value? ~0u : 0;
		ent_flags |= desired_flag & mask;
	}

	// If this unit is still active in the gameworld - i.e. not a corpse.
	bool m_extant;

	// If this is false, the unit will not be drawn and cannot be interacted with using the mouse.
	bool m_visible;

	int m_frameCheck;	//counts the frame

	double m_lastCombatTime;
	double m_lastRunTime;

	// Building to convert to if this is a foundation, or "" otherwise
	CStrW m_building;

	//SP properties
	float m_staminaCurr;
	float m_staminaMax;

	// HP properties
	float m_healthCurr;
	float m_healthMax;

	// Rank properties
	CStr m_rankName;

	// Stance name
	CStr m_stanceName;

	// LOS distance/range
	ssize_t m_los;

	// If the object is a territory centre, this points to its territory
	CTerritory* m_associatedTerritory;

	// Territorial limit
	CStrW m_buildingLimitCategory;

	// Auras
	AuraTable m_auras;
	AuraSet m_aurasInfluencingMe;

	//-- Interpolated property
	CVector3D m_position;
	CVector3D m_position_previous;
	CVector3D m_graphics_position;

	CBoundingObject* m_bounds;
	float m_targetorientation;
	CVector2D m_ahead;

	//-- Interpolated property
	CVector3D m_orientation;
	CVector3D m_orientation_smoothed; // used for slow graphical-only rotation - tends towards m_orientation
	CVector3D m_orientation_previous; // previous smoothed value
	CVector3D m_graphics_orientation;
	
	CVector2D m_orientation_unclamped;

	CVector2D m_rallyPoint;	// valid iff ENT_HAS_RALLY_POINT

	// If the actor's current transform data is valid (i.e. the entity hasn't
	// moved since it was last calculated, and the terrain hasn't been changed).
	bool m_actor_transform_valid;

	// Our current collision patch in CEntityManager
	std::vector<CEntity*>* m_collisionPatch;

	//-- Scripts

	// Get script execution contexts - always run in the context of the entity that fired it.
	JSObject* GetScriptExecContext( IEventTarget* target ) { return( ((CEntity*)target)->GetScript() ); }

	CScriptObject m_EventHandlers[EVENT_LAST];

	CUnit* m_actor;
	std::set<CStr8> m_actorSelections;

	int m_lastState;	// used in animation FSM

	// Position in the current state's cycle
	static const int NOT_IN_CYCLE = -1;
	int m_fsm_cyclepos; // -cycle_length....cycle_length

	CEntityOrders m_orderQueue;
	std::deque<CEntityListener> m_listeners;

	std::vector<CEntity*> m_notifiers;
	int m_currentNotification;	//Current order in the form of a notification code
	int m_currentRequest;	//Notification we our notifiers are sending

	std::vector<bool> m_sectorValues;
	//Slight optimization for aura rendering
	std::vector< std::vector<CVector2D> > m_unsnappedPoints;
	
	//Kai: add id to identify the entity in the polygon soup
	//	   needed for update/remove entities in the triangulation.
	int m_dcdtId;
	void removeObstacle();

private:
	CEntity( CEntityTemplate* base, CVector3D position, float orientation, const std::set<CStr8>& actorSelections, const CStrW* building = 0 );

	// (returns EGotoSituation, but we don't want to expose that)
	int ProcessGotoHelper( CEntityOrder* current, int timestep_millis, HEntity& collide, float& timeLeft );

	bool ProcessContactAction( CEntityOrder* current, int timestep_millis, bool repath_if_needed );
	bool ProcessContactActionNoPathing( CEntityOrder* current, int timestep_millis );

	bool ProcessProduce( CEntityOrder* order );

	bool ProcessGotoNoPathing( CEntityOrder* current, int timestep_millis );
	bool ProcessGotoNoPathingContact( CEntityOrder* current, int timestep_millis );
	bool ProcessGoto( CEntityOrder* current, int timestep_millis );
	bool ProcessGotoWaypoint( CEntityOrder* current, int timestep_millis, bool contact );

	bool ProcessPatrol( CEntityOrder* current, int timestep_millis );

	float ChooseMovementSpeed( float distance );
	
	bool ShouldRun( float distance );		// Given our distance to a target, can we be running?

	void UpdateOrders( int timestep_millis );

public:
	~CEntity();

	// Handle-to-self.
	HEntity me;

	// Updates gameplay information for the specified timestep
	void Update( int timestep_millis );
	// Updates graphical information for a point between the last and current
	// simulation frame; should be 0 <= relativeoffset <= 1 (else it'll be
	// clamped)
	void Interpolate( float relativeoffset );

	// Forces update of actor information during next call to 'interpolate'.
	// (Necessary when terrain might move underneath the actor.)
	void InvalidateActor();

	// Updates auras
	void UpdateAuras( int timestep_millis );

	// Exit auras we're currently in (useful for example when we change state)
	void ExitAuras();

	// Removes entity from the gameworld and deallocates it, but not necessarily immediately.
	// The keepActor parameter specifies whether to remove the unit's actor immediately (for
	// units we want removed immediately, e.g. in Alkas) or to keep it and play the death
	// animation (for units that die of "natural causes").
	void Kill(bool keepActor = false);

	// Process initialization
	bool Initialize();
	void initAuraData();

	// Process tick.
// 	void Tick(); // (see comment in CEntityManager::UpdateAll)

	// Calculate distances along the terrain

	float Distance2D( float x, float z )
	{
		float dx = x - m_position.X;
		float dz = z - m_position.Z;
		return sqrt( dx*dx + dz*dz );
	}

	float Distance2D( CEntity* other )
	{
		return Distance2D( other->m_position.X, other->m_position.Z );
	}

	float Distance2D( CVector2D p )
	{
		return Distance2D( p.x, p.y );
	}

private:
	// The player that owns this entity.
	// (Player is set publicly via CUnit::SetPlayerID)
	CPlayer* m_player;
	void SetPlayer(CPlayer* player);
public:
	// Retrieve the player associated with this entity.
	CPlayer* GetPlayer() { return m_player; }

	// Update collision patch (move ourselves to a new one if necessary)
	void UpdateCollisionPatch();

	float GetAnchorLevel( float x, float z );

	void SnapToGround();
	void UpdateActorTransforms();
	void UpdateXZOrientation();

	// Getter and setter for the class sets
	jsval GetClassSet();
	void SetClassSet( jsval value );
	void RebuildClassSet();

	// Things like selection circles and debug info - possibly move to gui if/when it becomes responsible for (and capable of) it.
	void Render();
	void RenderSelectionOutline( float alpha = 1.0f );
	void RenderAuras();
	void RenderBars();
	void RenderBarBorders();
	void RenderHealthBar();
	void RenderStaminaBar();
	void RenderRank();
	void RenderRallyPoint();
	
	// Utility functions for rendering:

	// Draw rectangle around the given centre, aligned with the given axes
	void DrawRect(CVector3D& centre, CVector3D& up, CVector3D& right, float x1, float y1, float x2, float y2);
	void DrawBar(CVector3D& centre, CVector3D& up, CVector3D& right, 
		float x1, float y1, float x2, float y2,
		SColour col1, SColour col2, float currVal, float maxVal);

	CVector2D GetScreenCoords( float height );

	// After a collision, recalc the path to the next fixed waypoint.
	void Repath();

	//Calculate stamina points
	void CalculateRegen(float timestep);

	// Reset properties after the entity-template we use changes.
	void LoadBase();
	static void InitAttributes(const CEntity* _this);

	void Reorient(); // Orientation
	void Teleport(); // Fixes things if the position is changed by something externally.
	void StanceChanged(); // Sets m_stance to the right CStance object when our stance property is changed through scripts
	void CheckSelection(); // In case anyone tries to select/deselect this through JavaScript.
	void CheckGroup(); // Groups
	void CheckExtant(); // Existence

	void ClearOrders();
	void PopOrder();	//Use this if an order has finished instead of m_orderQueue.pop_front()
	void PushOrder( const CEntityOrder& order );

	void DispatchNotification( const CEntityOrder& order, int type );
	int DestroyNotifier( CEntity* target );	//Stop notifier from sending to us
	void DestroyAllNotifiers();

	int FindSector( int divs, float angle, float maxAngle, bool negative=true );
	jsval_t FlattenTerrain( JSContext* cx, uintN argc, jsval* argv );

	CEntityFormation* GetFormation();
	jsval_t GetFormationPenalty( JSContext* cx, uintN argc, jsval* argv );
	jsval_t GetFormationPenaltyBase( JSContext* cx, uintN argc, jsval* argv );
	jsval_t GetFormationPenaltyType( JSContext* cx, uintN argc, jsval* argv );
	jsval_t GetFormationPenaltyVal( JSContext* cx, uintN argc, jsval* argv );

	jsval_t GetFormationBonus( JSContext* cx, uintN argc, jsval* argv );
	jsval_t GetFormationBonusBase( JSContext* cx, uintN argc, jsval* argv );
	jsval_t GetFormationBonusType( JSContext* cx, uintN argc, jsval* argv );
	jsval_t GetFormationBonusVal( JSContext* cx, uintN argc, jsval* argv );

	void DispatchFormationEvent( int type );

	jsval_t RegisterDamage( JSContext* cx, uintN argc, jsval* argv );
	jsval_t RegisterOrderChange( JSContext* cx, uintN argc, jsval* argv );
	jsval_t GetAttackDirections( JSContext* cx, uintN argc, jsval* argv );

	jsval_t FindSector( JSContext* cx, uintN argc, jsval* argv );

	// Script constructor
	static JSBool Construct( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval );

	// Script-bound functions

	jsval_t ToString( JSContext* cx, uintN argc, jsval* argv );

	bool Kill( JSContext* cx, uintN argc, jsval* argv );
	jsval_t GetSpawnPoint( JSContext* cx, uintN argc, jsval* argv );

	jsval_t HasRallyPoint( JSContext* cx, uintN argc, jsval* argv );
	jsval_t GetRallyPoint( JSContext* cx, uintN argc, jsval* argv );
	jsval_t SetRallyPointAtCursor( JSContext* cx, uintN argc, jsval* argv );

	jsval_t AddAura( JSContext* cx, uintN argc, jsval* argv );
	jsval_t RemoveAura( JSContext* cx, uintN argc, jsval* argv );

	jsval_t SetActionParams( JSContext* cx, uintN argc, jsval* argv );
	jsval_t TriggerRun( JSContext* cx, uintN argc, jsval* argv );
	jsval_t SetRun( JSContext* cx, uintN argc, jsval* argv );
	jsval_t IsRunning( JSContext* cx, uintN argc, jsval* argv );
	jsval_t GetRunState( JSContext* cx, uintN argc, jsval* argv );
	
	jsval_t OnDamaged( JSContext* cx, uintN argc, jsval* argv );

	jsval_t GetVisibleEntities( JSContext* cx, uintN argc, jsval* argv );

	float GetDistance( JSContext* cx, uintN argc, jsval* argv );

	bool RequestNotification( JSContext* cx, uintN argc, jsval* argv );
	//Just in case we want to explicitly check the listeners without waiting for the order to be pushed
	bool ForceCheckListeners( JSContext* cx, uintN argc, jsval* argv );
	int GetCurrentRequest( JSContext* cx, uintN argc, jsval* argv );
	void CheckListeners( int type, CEntity *target );
	jsval_t DestroyAllNotifiers( JSContext* cx, uintN argc, jsval* argv );
	jsval_t DestroyNotifier( JSContext* cx, uintN argc, jsval* argv );

	jsval JSI_GetPlayer();
	void JSI_SetPlayer(jsval val);

	bool IsInFormation( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
	{
		return ( m_formation != 0 ? true : false );
	}

	bool Order( JSContext* cx, uintN argc, jsval* argv, CEntityOrder::EOrderSource source, bool Queued );

	// TODO: Replace these variants of Order() with a single function, and update scripts accordingly.
	inline bool OrderSingle( JSContext* cx, uintN argc, jsval* argv )
	{
		return( Order( cx, argc, argv, CEntityOrder::SOURCE_PLAYER, false ) );
	}
	inline bool OrderQueued( JSContext* cx, uintN argc, jsval* argv )
	{
		return( Order( cx, argc, argv, CEntityOrder::SOURCE_PLAYER, true ) );
	}
	inline bool OrderFromTriggers( JSContext* cx, uintN argc, jsval* argv )
	{
		return( Order( cx, argc, argv, CEntityOrder::SOURCE_TRIGGERS, true ) );
	}

	bool IsIdle( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
	{
		return( m_orderQueue.empty() );
	}

	bool IsDestroyed( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
	{
		return( entf_get(ENTF_DESTROYED) );
	}

	bool HasClass( JSContext* cx, uintN argc, jsval* argv )
	{
		debug_assert( argc >= 1 );
		return( m_classes.IsMember( ToPrimitive<CStrW>( cx, argv[0] ) ) );
	}

	jsval_t TerminateOrder( JSContext* UNUSED(cx), uintN argc, jsval* argv )
	{
		debug_assert( argc >= 1);
		if ( ToPrimitive<bool>( argv[0] ) )
			m_orderQueue.clear();
		else
			m_orderQueue.pop_front();
		return JSVAL_VOID;
	}

	jsval_t GetHeight( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
	{
		return ToJSVal(m_position.Y);
	}

	static void ScriptingInit();

	// Functions that call script code
	int GetAttackAction( HEntity target );
};

// General entity globals


// In its current incarnation, inefficient but pretty
#define SELECTION_TERRAIN_CONFORMANCE

extern int SELECTION_CIRCLE_POINTS;
extern int SELECTION_BOX_POINTS;
extern int SELECTION_SMOOTHNESS_UNIFIED;
extern int AURA_CIRCLE_POINTS;

#endif
