#pragma once

const vec3f UP(0.0, 0.0, 1.0);

class MobileModel {
	public:
		vec3f pos;
		vec3f vel;
		vec3f acc;

		// For interpolation
		vec3f startPos;
		vec3f startVel;
		vec3f targetPos;
		vec3f targetVel;
		double startTime;
		double interpTime;
		bool hasTargetPos;

		GLfloat dir;
		display_list* dlist;
		inline void Draw()
			{
				glPushMatrix();
				DrawFast();
				glPopMatrix();
			}
		inline void DrawFast()
			{
				glTranslate(pos);
				glRotate(dir, UP);
				dlist->call_list();
			}
		inline void update(double dtime)
		{
			vel += acc*dtime;
			pos += vel*dtime;
		}

		// Returns true if the interpolation has reached or passed the endpoint
		// Returns false if there's still interpolation time left
		bool updateInterp(double timenow, double dtime);
		void setInterpParam(vec3f tgtPos, vec3f tgtVel, double timenow, double interpDuration);

		inline void bounce(vec3f normal)//, float elasticity=1.0)
		{	vec3f tmpv = vel;
			tmpv.normalize();
			tmpv = tmpv + 2*(normal.dot(-tmpv))*normal;
			vel = tmpv * vel.length();// * elasticity;
		}
};

struct FollowerDynamics
{
	float ApproachRadius;
	float ApproachAccel;
	float ApproachVel;
	float closeAccelScale;
	vec3f followerOffset;

	FollowerDynamics(
		// Default values
		vec3f follow_offset=vec3f(0,0,0),
		float inApproachVel=25,
		float inApproachAccel=5,
		float inApproachRadius=30,
		float inCloseAccelScale=1.0 ) :

			followerOffset(follow_offset), ApproachVel(inApproachVel),
			ApproachAccel(inApproachAccel), ApproachRadius(inApproachRadius),
			closeAccelScale(inCloseAccelScale)

		{		}
};

vec3f calcFollow(const MobileModel& follower, const MobileModel& target, const FollowerDynamics& dyn);
vec3f calcHoverAccel(const MobileModel& hoverer, float tgtHoverHeight, float enginePower, float gravity, MapObject& m);

enum HoverStatus_type { HOVER_OFF, HOVER_STARTUP, HOVER_GOING, HOVER_SHUTDOWN };

enum TankState_type { TS_CREATING, TS_NORMAL, TS_EXPLODING, TS_WAIT_FOR_SERVER, TS_NUM_STATES };

#define NUM_TANK_MODELS 5

class TankObject {
	public:
		static display_list tankModel[NUM_TANK_MODELS];
		static display_list tankTurret[NUM_TANK_MODELS];
		static vec3f tankTurretOffset[NUM_TANK_MODELS];
		static unsigned short int tankArmorStrength[NUM_TANK_MODELS];

		static timet TankStateDuration[TS_NUM_STATES];
	public:
		void SetTankModel(int modelnum);
		void Spawn();
		bool ReceiveHit(class ProjectileInfo* bullet);
		void Reset(class WorldInformation* containerWorld);
		class WorldInformation* myworld;

		bool leftside;

		TankObject();
		MobileModel model;

		vec3f dir3d;
		vec3f raxis;
		vec3f mnrm;

		short int armor;
		short int kills;
		short int deaths;
		short int modelnum;
		TankState_type state;
		timet stateStartTime;

		float Acceleration;
		float BrakeStrength;
		float MaxVelocity;

		float FullHoverHeight;
		float HoverEngineStrength;
		float MaxDamageFreeBounceDepth;

		HoverStatus_type hoverStatus;
		timet hoverTimeRef;
		timet hoverStartupTime;
		timet hoverShutdownTime;

		static void LoadModelData();
		static void ReleaseModelData();
		static void RegisterModelData();
		inline void DrawModel()
			{ model.Draw(); }
		void Draw();

		void HandleInput(float dtime, InputStatus_type input);
		void update(double dtime);
		void CheckHoverStatus();

		void BounceDamage(float bouncedepth, float velmag);

		void FireWeapon();
		timet lastWeaponFire;
		timet weaponFireRate;

		void StartHover();
		void StopHover();
		float GetHoverAmount();
		void SetState(TankState_type newState);

		bool ContainsPoint(vec3f p);

		bool active;
};

typedef enum { PT_MGBULLET, PT_CANNON, PT_NUM_PROJECTILE_TYPES } ProjectileType;
typedef enum { PS_INACTIVE=0, PS_CHARGING, PS_FLYING, PS_EXPLODING } ProjectileStatus;
class ProjectileInfo {
	public:
		ProjectileInfo() { active = PS_INACTIVE; sourceObjectPtr = NULL; myworld = NULL; }
		ProjectileInfo(ProjectileType newType, vec3f pos, vec3f vel, class WorldInformation* world);
		class WorldInformation* myworld;
		MobileModel model;
		ProjectileType type;
		timet startTime;
		timet lifespan;
		void* sourceObjectPtr;
		void Draw();
		void update(timet dtime);
		ProjectileStatus active;
};


//typedef enum { PARTICLE_SMOKE, PARTICLE_FLAME, PARTICLE_BURST, NUM_PARTICLE_TYPES } Particle_type;
typedef enum { PARTICLE_GENERIC, NUM_PARTICLE_TYPES } Particle_type;

#define COL_R 0
#define COL_G 1
#define COL_B 2
#define COL_A 3

inline void glColor(vec3f col, float alpha)
{  glColor4f(col[0],col[1],col[2],alpha);  }

class Particle {
	public:
		Particle() { energy = 0; }
		Particle_type type;
		vec3f pos;
		vec3f vel;
		vec3f col;
		float size;
		float dSize;
		float alpha;
		float energy;		// handles lifespan, color, fading for different types
		float dEnergy;		// energy loss per second
		//float calpha;		// current alpha

		inline void Draw(const vec3f& partu, const vec3f& partv)
		{
			vec3f uoff = partu * size;
			vec3f voff = partv * size;

			vec3f center = pos;
			glColor(col,alpha * energy/100.0);
			glBegin(GL_TRIANGLE_STRIP);
				glTexCoord2d(1,1);	glVertex( center + uoff + voff );
				glTexCoord2d(1,0);	glVertex( center + uoff - voff );
				glTexCoord2d(0,1);	glVertex( center - uoff + voff );
				glTexCoord2d(0,0);	glVertex( center - uoff - voff );
			glEnd();
		}

		inline void update(timet dtime, float k_drag, vec3f grav, vec3f wind)
		{
			energy		-= dEnergy * dtime;
			pos			+= vel * dtime;
			vel			+= (-vel * (1.0-k_drag) + grav + wind)*dtime;
			size			+= dSize * dtime;
			//alpha			= energy / 100.0;
			//col[COL_A]	 = energy / 100.0;
		}
		// Constructs a default particle based on the type
		//Particle(Particle_type newType);
		/*
		bool active;
		vec3f acc;
		float drag;
		float size;
		timet startTime;
		timet lifeSpan;
		vec3ub startCol;
		vec3ub endCol;
		bool fadeCol;			// fade colors from start to end over lifespan ?
		bool fadeInt;			// fade particle intensity over lifespan ?
		*/
};

#define MAX_PARTICLE_EMITTERS 100
#define MAX_GLOBAL_PARTICLES	100
#define MAX_EMITTER_PARTICLES 100

typedef enum {
	PARTICLE_EMITTER_GENERIC,
	PARTICLE_EMITTER_DUST_KICKUP,
	PARTICLE_EMITTER_RAINBOW_VORTEX,
	PARTICLE_EMITTER_FLARE_TRAIL,
	PARTICLE_EMITTER_FLARE_CLOUD,
	PARTICLE_EMITTER_FIRE,
	NUM_PARTICLE_EMITTER_TYPES,
} ParticleEmitter_type;

#define FIND_FREE_PARTICLE -1
#define LIFETIME_DEFAULT_EMITTER	-1
#define LIFETIME_ETERNAL_EMITTER -2

class ParticleEmitter {
	public:
		static timet DefaultEmitterLifetime[NUM_PARTICLE_EMITTER_TYPES];

	public:

		ParticleEmitter_type type;

		MobileModel model;					// Particle source movement info
		MobileModel* master;					// Attached to a particular object
		Particle sourceParticle;			// Particle at source
		Particle particle[MAX_EMITTER_PARTICLES];	// Emitted particles

		int numEmittedParticles;			// Paperwork
		int totalParticleCount;

		float particleEmissionRate;		// in "particles per second"
		float particlesToEmit;				// allows partial emissions
		timet lifetime;						// Remaining emitter lifetime in sec

		// Physics characteristics of emitter:
		float k_drag;
		vec3f grav;
		vec3f wind;

	public:
		bool active;

	public:
		ParticleEmitter() { active = false; }
		ParticleEmitter(ParticleEmitter_type inType, vec3f pos, vec3f vel, float inLifetime=0);
		void InitEmitter( ParticleEmitter_type inType );
		void update(timet dtime);
		void launch(int particleIndex = FIND_FREE_PARTICLE);
		void launch(Particle& p, int particleIndex = FIND_FREE_PARTICLE);

		void attach( MobileModel* target );
};

class ParticleEngine {
	public:
		Particle particle[MAX_GLOBAL_PARTICLES];
		ParticleEmitter emitter[MAX_PARTICLE_EMITTERS];
		
		ParticleEngine();
		
		bool LoadData();
		void ReleaseModels();
		bool RegisterModels();
		void Draw();
		void update(timet dtime);
		bool launch(Particle p);
		bool launch(ParticleEmitter& pe);
		ParticleEmitter* NewEmitter();

		static gs_image_type particleTexImage[NUM_PARTICLE_TYPES];
		static tex_object_2D particleTexOb[NUM_PARTICLE_TYPES];
		static char* ParticleImageFileNames[NUM_PARTICLE_TYPES];

		// Removes any emitters attached to the target
		void unattach( MobileModel* target );

		WorldInformation* myworld;
		bool inServer;
};
