#define STRICT
#define ORBITER_MODULE

#include "orbitersdk.h"
#include <vector>

class OMPMissile: public VESSEL3 {
public:
	OMPMissile (OBJHANDLE hVessel, int flightmodel);
	void clbkSetClassCaps (FILEHANDLE cfg);
	void clbkPreStep (double simt, double simdt, double mjd);
	int clbkGeneric(int msgid, int prm, void *context);

private:
	static void Lift (VESSEL *v, double aoa, double M, double Re, void *context, double *cl, double *cm, double *cd);
	OBJHANDLE target;
	int state; //<0.. wait ticks, 0..inactive, 1..active, 2..collided, 3..collided while inactive
	THRUSTER_HANDLE th_main;
	PROPELLANT_HANDLE propellant;
	double tick;
	std::vector<OBJHANDLE> kills;
};

OMPMissile::OMPMissile (OBJHANDLE hVessel, int flightmodel):VESSEL3 (hVessel, flightmodel)
{
	target=NULL;
	state=-5;
	tick=0;
}

void OMPMissile::Lift (VESSEL *v, double aoa, double M, double Re, void *context, double *cl, double *cm, double *cd)
{	
	//Simple lift calculation up to 10 degree
	double step=60*RAD;
	*cl=0;
	if (aoa<-PI+step) *cl=0.6*(PI+aoa)/step;			//Lower left quadrant = positive cl
	else if (aoa>PI-step) *cl=0.6*(aoa-PI)/step;		//Upper left quadrant = negative cl
	else if (aoa>-step && aoa<step) *cl=0.6*aoa/step;	//Right quadrants

	//Correction moment up to 10 degree
	*cm=0;
	*cm=min(max(0.06*aoa/step,-0.06), 0.06);
	
	//Standard drag calculation
	double saoa = sin(aoa);
	*cd =	0.02*saoa*saoa +							//Proportional drag
			oapiGetInducedDrag(*cl, 0.05, 1) +			//Induced drag
			oapiGetWaveDrag(M, 0.8, 1.0, 1.1, 0.005);	//Sonic drag
}

void OMPMissile::clbkSetClassCaps (FILEHANDLE cfg)
{
	//Airbody definition
	SetSize (4);
	SetEmptyMass (110);
	SetPMI (_V(2,2,0.5));
	SetCrossSections (_V(1.37,1.37,0.1));
	SetRotDrag (_V(0.25,0.25,0.01));
	SetTouchdownPoints (_V(-1,0,0), _V(0,0,1), _V(1,0,0));
	CreateAirfoil3 (LIFT_VERTICAL,   _V(0,0,0), Lift, NULL, 3.7, 1.4, 0.1);
	CreateAirfoil3 (LIFT_HORIZONTAL, _V(0,0,0), Lift, NULL, 3.7, 1.4, 0.1);

	//Engine
	propellant = CreatePropellantResource (60);
	th_main = CreateThruster (_V(0,0,-0.5), _V(0,0,1), 1.2e4, propellant, 4452, 2226);
	CreateThrusterGroup (&th_main, 1, THGROUP_MAIN);
	
	//Exhaust
	PARTICLESTREAMSPEC contrail_main = {
		0, 5.0, 16, 200, 0.15, 10.0, 5, 3.0, PARTICLESTREAMSPEC::DIFFUSE,
		PARTICLESTREAMSPEC::LVL_PSQRT, 0, 2,
		PARTICLESTREAMSPEC::ATM_PLOG, 1e-4, 1
	};
	PARTICLESTREAMSPEC exhaust_main = {
		0, 2.0, 20, 200, 0.05, 0.1, 8, 1.0, PARTICLESTREAMSPEC::EMISSIVE,
		PARTICLESTREAMSPEC::LVL_SQRT, 0, 1,
		PARTICLESTREAMSPEC::ATM_PLOG, 1e-5, 0.1
	};
	AddExhaust (th_main, 8, 1, _V(0,0,-0.5), _V(0,0,-1));
	AddExhaustStream (th_main, _V(0,0,-5), &contrail_main);
	AddExhaustStream (th_main, _V(0,0,-1), &exhaust_main);

	//Visuals
	SetCameraOffset (_V(0,0,3.2));
	AddMesh ("OMPMissile");
}

int OMPMissile::clbkGeneric(int msgid, int prm, void *context)
{
	if (msgid==0x8000 && prm==0)
	{
		target=(OBJHANDLE)context;
		state=1;
	}
	return state;
}

void OMPMissile::clbkPreStep (double simt, double simdt, double mjd)
{
	//Wait the ticks
	if (state<0) {state++; return;}

	//Check target
	if (target && !oapiIsVessel(target)) target=NULL;

	//Engage engine if active
	if (state) SetThrusterLevel_SingleStep(th_main, 1);
	
	//Check all vessels while no target or state is set
	int i=0;
	OBJHANDLE subject;
	while ((subject=oapiGetVesselByIndex(i++)) && (!state || !target))
	{
		if (subject==GetHandle()) continue;
		VECTOR3 g,l;
		oapiGetGlobalPos(subject, &g);
		Global2Local(g, l);
		//Check for possible collision with simple test for 1 meter match. Note that this means that missile start offset should be greater than 1 meter.
		if (!state && -0.5<l.x && l.x<0.5 && -0.5<l.y && l.y<0.5 && -0.5<l.z && l.z<0.5) state=3, tick=0, target=subject;
		//Check for possible target acquisition from seeker
		if (state==1 && !target && acos(l.z/length(l))<12*RAD) target=subject;
	}

	//Pursuit target while active
	while (state==1 && target)
	{
		VECTOR3 g,l,n;
		oapiGetGlobalPos(target, &g);
					
		//Collision detection with target supporting "shoot-through"
		GetRelativeVel(target, n);
		n=n*simdt;											//Propagate along relative velocity trajectory
		GetGlobalPos(l);
		g-=l;
		double r=length(n);									//Detection range
		double t=max(min(dotp(g,n)/r/r, 1), 0);				//"Periapsis" point along the trajectory
		double s=oapiGetSize(target);
		r=length(-g+n*t);									//Line-point distance
		if (r<s)
		{
			//Collision detected
			state=2;
			tick=0;
			break;
		}

		//Frustum culling
		if (r>5e4) {target=NULL; break;}					//TODO: make missile targeting range a server-set constant
		MATRIX3 m;
		GetRotationMatrix(m);		
		l=_V(m.m13, m.m23, m.m33);							//Current direction vector
		normalise(g);										//Target direction vector, also	Matrix Z vector	
		if (acos(dotp(l,g))>12*RAD) {target=NULL; break;}	//TODO: make missile targeting angle a server-set constant

		//Head towards target
		normalise(n=crossp(_V(m.m12, m.m22, m.m32), g));	//Matrix X vector
		normalise(l=-crossp(n, g));							//Matrix Y vector
		SetRotationMatrix(_M(n.x, l.x, g.x, n.y, l.y, g.y, n.z, l.z, g.z));
		break;
	}

	//Detect ground impact while active
	if (state==1 && GroundContact()) state=2, tick=0, target=NULL;

	//Detect ground impact while inactive - Note that this is not GroundContact due to the way OMP avoids planet collision
	if (!state && GetAltitude()<0.2) state=3, tick=0, target=NULL;

	//Explode 10 seconds after MECO while active
	if (state==1 && GetPropellantMass(propellant)==0)
	{
		if (!tick) tick=simt;
		if (simt-tick>10) state=2, tick=0, target=NULL;
	}

	//Detonation
	if (state>1)
	{		
		if (!tick)
		{
			//Start explosion
			tick=simt;
			SetThrusterMax0(th_main, 0);
			SetThrusterRef(th_main, _V(0,0,0));
			SetPropellantMass(propellant, 1);
			SetThrusterIsp(th_main, 1E12);
			//Check all vessels for explosion vicinity
			i=0;
			while (subject=oapiGetVesselByIndex(i++))
			{
				if (subject==GetHandle()) continue;
				VECTOR3 l;
				oapiGetRelativePos(subject, GetHandle(), &l);
				double s=max(oapiGetSize(subject), 10); //TODO: make minimum explosion radius a server-set constant
				if (length(l)<s || subject==target)
				{
					kills.push_back(subject);
					if (s>GetSize()) SetSize(s), target=subject;
				}
			}
			PARTICLESTREAMSPEC explosion = {
				0, GetSize()/2, 20, 10, 0.5, 10, 8, 1.0, PARTICLESTREAMSPEC::EMISSIVE,
				PARTICLESTREAMSPEC::LVL_SQRT, 0, 1,
				PARTICLESTREAMSPEC::ATM_PLOG, 1e-5, 0.1
			};
			AddExhaustStream (th_main, _V(0,0,0), &explosion);
			DelMesh(0);
			if (state>2)
			{
				//Kill vessels immediately in inactive explosion
				for(std::vector<OBJHANDLE>::iterator j=kills.begin();j!=kills.end();j++)
					if (oapiIsVessel(*j)) oapiDeleteVessel(*j);
				kills.clear();
			}
			else if (target)
			{
				//Glue missile to target via attachment - note that this will cause OMP to send relative SI to target vessel
				VESSEL *t=oapiGetVesselInterface(target);
				ATTACHMENTHANDLE p=t->CreateAttachment(false, _V(0,0,0), _V(0,0,1), _V(0,1,0), "OMPMP");
				ATTACHMENTHANDLE c=CreateAttachment(true, _V(0,0,0), _V(0,0,1), _V(0,1,0), "OMPMC");
				t->AttachChild(GetHandle(), p, c);
			}
		}
		else
		{
			//Random thruster direction
			VECTOR3 d=_V(rand()-RAND_MAX/2,rand()-RAND_MAX/2,rand()-RAND_MAX/2);
			if (-0.5<d.x && d.x<0.5 && -0.5<d.y && d.y<0.5) d.z=RAND_MAX/2;
			normalise(d);
			SetThrusterDir(th_main, d);
			SetThrusterLevel_SingleStep(th_main, 1);
		}
		if (simt-tick>3) //3 seconds explosion fun, then delete missile and optional target
		{
			//Kill vessels if there still are any
			for(std::vector<OBJHANDLE>::iterator j=kills.begin();j!=kills.end();j++)
				if (oapiIsVessel(*j)) oapiDeleteVessel(*j);
			oapiDeleteVessel(GetHandle());
			target=NULL;
			state=0;
		}		
	}
}

DLLCLBK VESSEL *ovcInit (OBJHANDLE hvessel, int flightmodel)
{
	return new OMPMissile (hvessel, flightmodel);
}

DLLCLBK void ovcExit (VESSEL *vessel)
{
	if (vessel) delete (OMPMissile*)vessel;
}
