/*
   Copyright (C) 2004 by James Gregory
   Part of the GalaxyHack project
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License.
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY.
 
   See the COPYING file for more details.
*/

#include "Projectile.h"
#include "Globals.h"
#include "Group.h"
#include "Inlines.h"
#include "Random.h"

#include <stdexcept>
#include <cmath>

using std::runtime_error;

Projectile::Projectile(float ix, float iy, CoordsInt target, WeaponType iType, Uint16 iColor) {
	if (weaponLookup[iType].category == WCAT_Twin)
		hProj = new TwinLaserBolt(ix, iy, target, iType, iColor);

	else if (weaponLookup[iType].category == WCAT_Small)
		hProj = new SmallLaserBolt(ix, iy, target, iType, iColor);

	else if (weaponLookup[iType].category == WCAT_Missile)
		hProj = new Missile(ix, iy, target, iType);

	else if (weaponLookup[iType].category == WCAT_Torpedo)
		hProj = new Torpedo(ix, iy, target, iType);

	else throw runtime_error("Projectile constructor didn't recognise iType");
}

Projectile::Projectile(float ix, float iy) {
	hProj = new LaserExplosion(ix, iy);
}

////

Projectile_Base::Projectile_Base(float ix, float iy, CoordsInt target, WeaponType iType, Uint16 iColor):
x(ix), y(iy), color(iColor), timer(frameCounter), myType(iType) {
	targetSide = target.x;
	targetGroup = target.y;

	mySpeed = weaponLookup[myType].speed;
	myLength = weaponLookup[myType].length;

	sides[targetSide].groups[targetGroup].FiredAt(targetInfo);

	targetUnit = targetInfo.whichUnit;

	weHit = CheckToHit(weaponLookup[myType].accuracy, targetInfo.difficulty);
}

Projectile_Base::Projectile_Base(float ix, float iy):
x(ix), y(iy)
{}

void Projectile_Base::PropsToSpeedAndLength(float dx, float dy) {
	float propx;
	float propy;

	GetMoveProps(propx, propy, dx, dy);

	//vital to do this properly, this caused a most annoying bug
	float propz = propx*propx + propy*propy;
	propz = std::sqrt(propz);
	speedx = (propx / propz) * mySpeed;
	speedy = (propy / propz) * mySpeed;

	//this isn't quite right but who cares, we're in 3 dimensions, right?
	//the real answer is:
	//length in x = ((speed in x) / (speed in x + speed in y)) * length
	lengthx = static_cast<int>(myLength * propx);
	lengthy = static_cast<int>(myLength * propy);
}

void Projectile_Base::StandardLineDraw() {
	int x0 = static_cast<int>(x) - viewx;
	int y0 = static_cast<int>(y) - viewy;
	int x1 = static_cast<int>(x) + lengthx - viewx;
	int y1 = static_cast<int>(y) + lengthy - viewy;
	if (ClipLine(x0, y0, x1, y1))
		DrawLine(x0, y0, x1, y1, color);
}

bool Projectile_Base::Move() {
	if (frameCounter - timer > duration) {
		if (weHit)
			sides[targetSide].groups[targetGroup].BeenHit(targetUnit, weaponLookup[myType].power);
		return false;
	}
	else {
		x += speedx;
		y += speedy;

		return true;
	}
}

void Projectile_Base::SetDuration(float distance) {
	//+1 because move (and hence erasure) comes before drawing
	duration = static_cast<int>(((distance - myLength) / mySpeed) + 1);

	//if we're right on top of them..
	if (duration < 1) {
		//right right on top of them, in which case we need
		//to make up arbitrary lengths and speeds
		if (speedx == 0 && speedy == 0) {
			speedx = mySpeed / 2;
			speedy = mySpeed / 2;
			lengthx = myLength / 4;
			lengthy = myLength / 4;
		}

		duration = 1;
	}
}

///

LaserBolt_Base::LaserBolt_Base(float ix, float iy, CoordsInt target, WeaponType iType, Uint16 iColor):
Projectile_Base(ix, iy, target, iType, iColor) {
	PredictTarget();
}

void LaserBolt_Base::PredictTarget() {
	/*
	Another way to do this would be:
	mx = m.x + c, so x = c / (m. - m)
	with x being time
	 
	int time = distance / (laserSpeed - targetInfo.speed);
	 
	this must be done in each dimension, so:
	 
	int timex = dx / (laserSpeedx - targetInfo.speedx);
	int timey = dy / (laserSpeedy - targetInfo.speedy);
	 
	watching out for division by 0 and/or negative values
	 
	However, we do not know the proportions of laserSpeed
	in x and y, because that's what we're trying to work out!
	 
	For this to work, we'd also need to know the direction of
	laserSpeed in each dimension, so we could deal with them
	moving towards us
	

	what we actually do is two iterations of where they will
	be in the amount of time it takes us to reach them
	*/

	float predx = targetInfo.currentx + targetInfo.weakSpot.x;
	float predy = targetInfo.currenty + targetInfo.weakSpot.y;
	
	float dx = predx - x;
	float dy = predy - y;
	float distance = FastDist(dx, dy);
		
	float time = distance / mySpeed;
	
	predx += targetInfo.speedx * time;
	predy += targetInfo.speedy * time;
		
	dx = predx - x;
	dy = predy - y;
	PropsToSpeedAndLength(dx, dy);
	 
	float totalDistance = FastDist(dx, dy);
	SetDuration(totalDistance);
}

///

SmallLaserBolt::SmallLaserBolt(float ix, float iy, CoordsInt target, WeaponType iType, Uint16 iColor):
LaserBolt_Base(ix, iy, target, iType, iColor) {}

void SmallLaserBolt::DrawSelfPixels() {
	StandardLineDraw();
}


////

TwinLaserBolt::TwinLaserBolt(float ix, float iy, CoordsInt target, WeaponType iType, Uint16 iColor):
LaserBolt_Base(ix, iy, target, iType, iColor) {}

void TwinLaserBolt::DrawSelfPixels() {
	if (speedy > speedx) {
		int x0 = static_cast<int>(x) - viewx - 3;
		int y0 = static_cast<int>(y) - viewy;
		int x1 = static_cast<int>(x) + lengthx - viewx - 3;
		int y1 = static_cast<int>(y) + lengthy - viewy;

		if (ClipLine(x0, y0, x1, y1))
			DrawLine(x0, y0, x1, y1, color);

		x0+= 6;
		x1+= 6;

		if (ClipLine(x0, y0, x1, y1))
			DrawLine(x0, y0, x1, y1, color);
	}
	else {
		int x0 = static_cast<int>(x) - viewx;
		int y0 = static_cast<int>(y) - viewy - 3;
		int x1 = static_cast<int>(x) + lengthx - viewx;
		int y1 = static_cast<int>(y) + lengthy - viewy - 3;

		if (ClipLine(x0, y0, x1, y1))
			DrawLine(x0, y0, x1, y1, color);

		y0+= 6;
		y1+= 6;

		if (ClipLine(x0, y0, x1, y1))
			DrawLine(x0, y0, x1, y1, color);
	}
}

////
void Projectile_Base::DrawBigLaser(int x0, int y0, int x1, int y1, Uint16 color) {
	x0-= viewx;
	y0-= viewy;
	x1-= viewx;
	y1-= viewy;

	if (ClipLine(x0, y0, x1, y1)) {
		DrawLine(x0, y0, x1, y1, color);

		//only have to cast immediately if not floats
		float dx = x1 - x0;
		float dy = y1 - y0;
		float distance = FastDist(dx, dy);

		float propx, propy;

		//dont need the sign
		if (distance) {
			propx = fabs(dx) / distance;
			propy = fabs(dy) / distance;
		}

		if (propy > propx) {
			++x0;
			++x1;

			if (ClipLine(x0, y0, x1, y1))
				DrawLine(x0, y0, x1, y1, color);

			x0-= 2;
			x1-= 2;

			if (ClipLine(x0, y0, x1, y1))
				DrawLine(x0, y0, x1, y1, color);
		}
		else {
			++y0;
			++y1;

			if (ClipLine(x0, y0, x1, y1))
				DrawLine(x0, y0, x1, y1, color);

			y0-= 2;
			y1-= 2;

			if (ClipLine(x0, y0, x1, y1))
				DrawLine(x0, y0, x1, y1, color);
		}
	}
}

////



// this function clips the sent line
int Projectile_Base::ClipLine(int& x1, int& y1,int& x2, int& y2) {
	//The screen dimensions to clip to
	static const int min_clip_x = 0;
	int max_clip_x = globalSettings.screenWidth - 1;
	static const int min_clip_y = 0;
	int max_clip_y = globalSettings.screenHeight - 1;

	// internal clipping codes
	#define CLIP_CODE_C  0x0000
	#define CLIP_CODE_N  0x0008
	#define CLIP_CODE_S  0x0004
	#define CLIP_CODE_E  0x0002
	#define CLIP_CODE_W  0x0001

	#define CLIP_CODE_NE 0x000a
	#define CLIP_CODE_SE 0x0006
	#define CLIP_CODE_NW 0x0009
	#define CLIP_CODE_SW 0x0005

	int xc1=x1, yc1=y1, xc2=x2, yc2=y2;

	int p1_code=0, p2_code=0;

	// determine codes for p1 and p2
	if (y1 < min_clip_y)
		p1_code|=CLIP_CODE_N;
	else if (y1 > max_clip_y)
		p1_code|=CLIP_CODE_S;

	if (x1 < min_clip_x)
		p1_code|=CLIP_CODE_W;
	else if (x1 > max_clip_x)
		p1_code|=CLIP_CODE_E;

	if (y2 < min_clip_y)
		p2_code|=CLIP_CODE_N;
	else if (y2 > max_clip_y)
		p2_code|=CLIP_CODE_S;

	if (x2 < min_clip_x)
		p2_code|=CLIP_CODE_W;
	else if (x2 > max_clip_x)
		p2_code|=CLIP_CODE_E;

	// try and trivially reject
	if ((p1_code & p2_code))
		return 0;

	// test for totally visible, if so leave points untouched
	if (p1_code==0 && p2_code==0)
		return 1;

	// determine end clip point for p1
	switch(p1_code) {
	case CLIP_CODE_C:

		break;

	case CLIP_CODE_N:
		yc1 = min_clip_y;
		xc1 = x1 + 0.5+(min_clip_y-y1)*(x2-x1)/(y2-y1);
		break;

	case CLIP_CODE_S:
		yc1 = max_clip_y;
		xc1 = x1 + 0.5+(max_clip_y - y1)*(x2-x1)/(y2-y1);
		break;

	case CLIP_CODE_W:
		xc1 = min_clip_x;
		yc1 = y1 + 0.5+(min_clip_x - x1)*(y2-y1)/(x2-x1);
		break;

	case CLIP_CODE_E:
		xc1 = max_clip_x;
		yc1 = y1 + 0.5+(max_clip_x - x1)*(y2-y1)/(x2-x1);
		break;

		// these cases are more complex, must compute 2 intersections
	case CLIP_CODE_NE:
		// north hline intersection
		yc1 = min_clip_y;
		xc1 = x1 + 0.5+(min_clip_y - y1)*(x2 - x1)/(y2 - y1);

		// test if intersection is valid, of so then done, else compute next
		if (xc1 < min_clip_x || xc1 > max_clip_x) {
			// east vline intersection
			xc1 = max_clip_x;
			yc1 = y1 + 0.5 + (max_clip_x - x1)*(y2 - y1)/(x2 - x1);
		}
		break;

	case CLIP_CODE_SE:
		// south hline intersection
		yc1 = max_clip_y;
		xc1 = x1 + 0.5 + (max_clip_y-y1) * (x2-x1) / (y2-y1);

		// test if intersection is valid, of so then done, else compute next
		if (xc1 < min_clip_x || xc1 > max_clip_x) {
			// east vline intersection
			xc1 = max_clip_x;
			yc1 = y1 + 0.5+(max_clip_x-x1)*(y2-y1)/(x2-x1);
		}
		break;

	case CLIP_CODE_NW:
		// north hline intersection
		yc1 = min_clip_y;
		xc1 = x1 + 0.5+(min_clip_y-y1)*(x2-x1)/(y2-y1);

		// test if intersection is valid, of so then done, else compute next
		if (xc1 < min_clip_x || xc1 > max_clip_x) {
			xc1 = min_clip_x;
			yc1 = y1 + 0.5+(min_clip_x-x1)*(y2-y1)/(x2-x1);
		}
		break;

	case CLIP_CODE_SW:
		// south hline intersection
		yc1 = max_clip_y;
		xc1 = x1 + 0.5+(max_clip_y-y1)*(x2-x1)/(y2-y1);

		// test if intersection is valid, of so then done, else compute next
		if (xc1 < min_clip_x || xc1 > max_clip_x) {
			xc1 = min_clip_x;
			yc1 = y1 + 0.5+(min_clip_x-x1)*(y2-y1)/(x2-x1);
		}
		break;
	}

	// determine clip point for p2
	switch(p2_code) {
	case CLIP_CODE_C:

		break;

	case CLIP_CODE_N:
		yc2 = min_clip_y;
		xc2 = x2 + (min_clip_y-y2)*(x1-x2)/(y1-y2);
		break;

	case CLIP_CODE_S: {
			yc2 = max_clip_y;
			xc2 = x2 + (max_clip_y-y2)*(x1-x2)/(y1-y2);
		}
		break;

	case CLIP_CODE_W: {
			xc2 = min_clip_x;
			yc2 = y2 + (min_clip_x-x2)*(y1-y2)/(x1-x2);
		}
		break;

	case CLIP_CODE_E: {
			xc2 = max_clip_x;
			yc2 = y2 + (max_clip_x-x2)*(y1-y2)/(x1-x2);
		}
		break;

		// these cases are more complex, must compute 2 intersections
	case CLIP_CODE_NE: {
			// north hline intersection
			yc2 = min_clip_y;
			xc2 = x2 + 0.5+(min_clip_y-y2)*(x1-x2)/(y1-y2);

			// test if intersection is valid, of so then done, else compute next
			if (xc2 < min_clip_x || xc2 > max_clip_x) {
				// east vline intersection
				xc2 = max_clip_x;
				yc2 = y2 + 0.5+(max_clip_x-x2)*(y1-y2)/(x1-x2);
			} // end if

		}
		break;

	case CLIP_CODE_SE: {
			// south hline intersection
			yc2 = max_clip_y;
			xc2 = x2 + 0.5+(max_clip_y-y2)*(x1-x2)/(y1-y2);

			// test if intersection is valid, of so then done, else compute next
			if (xc2 < min_clip_x || xc2 > max_clip_x) {
				// east vline intersection
				xc2 = max_clip_x;
				yc2 = y2 + 0.5+(max_clip_x-x2)*(y1-y2)/(x1-x2);
			} // end if


		}
		break;

	case CLIP_CODE_NW: {
			// north hline intersection
			yc2 = min_clip_y;
			xc2 = x2 + 0.5+(min_clip_y-y2)*(x1-x2)/(y1-y2);

			// test if intersection is valid, of so then done, else compute next
			if (xc2 < min_clip_x || xc2 > max_clip_x) {
				xc2 = min_clip_x;
				yc2 = y2 + 0.5+(min_clip_x-x2)*(y1-y2)/(x1-x2);
			} // end if

		}
		break;

	case CLIP_CODE_SW: {
			// south hline intersection
			yc2 = max_clip_y;
			xc2 = x2 + 0.5+(max_clip_y-y2)*(x1-x2)/(y1-y2);

			// test if intersection is valid, of so then done, else compute next
			if (xc2 < min_clip_x || xc2 > max_clip_x) {
				xc2 = min_clip_x;
				yc2 = y2 + 0.5+(min_clip_x-x2)*(y1-y2)/(x1-x2);
			} // end if

		}
		break;

	default:break;

	} // end switch

	// do bounds check
	if ((xc1 < min_clip_x) || (xc1 > max_clip_x) ||
	        (yc1 < min_clip_y) || (yc1 > max_clip_y) ||
	        (xc2 < min_clip_x) || (xc2 > max_clip_x) ||
	        (yc2 < min_clip_y) || (yc2 > max_clip_y) )
		return 0;

	// store vars back
	x1 = xc1;
	y1 = yc1;

	x2 = xc2;
	y2 = yc2;

	return 1;
}


// this function draws a line from xo,yo to x1,y1 using differential error
// terms (based on Bresenahams work)
void Projectile_Base::DrawLine(int x0, int y0, int x1, int y1, Uint16 color) {
	int dx,         // difference in x's
	dy,             // difference in y's
	dx2,            // dx,dy * 2
	dy2,
	x_inc,          // amount in pixel space to move during drawing
	y_inc,          // amount in pixel space to move during drawing
	error,          // the discriminant i.e. error i.e. decision variable
	index;          // used for looping

	static const int lpitch_2 = JSDL.screen->pitch >> 1; // Uint16 strided lpitch

	// pre-compute first pixel address in video buffer based on 16bit data
	Uint16* vb_start2 = reinterpret_cast<Uint16*>(JSDL.screen->pixels) + x0 + y0 * lpitch_2;

	// compute horizontal and vertical deltas
	dx = x1 - x0;
	dy = y1 - y0;

	// test which direction the line is going in i.e. slope angle
	if (dx>=0)
		x_inc = 1;
	else {
		x_inc = -1;
		dx    = -dx;  // need absolute value
	}

	// test y component of slope
	if (dy>=0)
		y_inc = lpitch_2;
	else {

		y_inc = -lpitch_2;
		dy    = -dy;  // need absolute value
	}

	//compute (dx,dy) * 2
	dx2 = dx << 1;
	dy2 = dy << 1;

	//now based on which delta is greater we can draw the line
	//if |slope| <= 1
	if (dx > dy) {
		// initialize error term
		error = dy2 - dx;

		// draw the line
		for (index=0; index <= dx; index++) {
			// set the pixel
			*vb_start2 = color;

			// test if error has overflowed
			if (error >= 0) {
				error-=dx2;

				// move to next line
				vb_start2+=y_inc;
			}

			// adjust the error term
			error+=dy2;

			// move to the next pixel
			vb_start2+=x_inc;
		}
	}
	else
		//else |slope| > 1
	{
		// initialize error term
		error = dx2 - dy;

		// draw the line
		for (index=0; index <= dy; index++) {
			// set the pixel
			*vb_start2 = color;

			// test if error overflowed
			if (error >= 0) {
				error-=dy2;

				// move to next line
				vb_start2+=x_inc;
			}

			// adjust the error term
			error+=dx2;

			// move to the next pixel

			vb_start2+=y_inc;
		}
	}
}

Missile::Missile(float ix, float iy, CoordsInt target, WeaponType iType):
Projectile_Base(ix, iy, target, iType, missileGrey), exploding(0), targetDead(0) {
	//get it to sort out some props because it will be drawn before it first gets to move
	Move();
	x-= speedx;
	y-= speedy;
}

bool Missile::Move() {
	if (exploding) {
		if (weHit)
			sides[targetSide].groups[targetGroup].BeenHit(targetUnit, weaponLookup[myType].power);
		return false;
	}

	if (!targetDead) {
		targetCoords = sides[targetSide].groups[targetGroup].GetUnitCenter(targetUnit);

		if (!sides[targetSide].groups[targetGroup].GetUnitAlive(targetUnit)) {
			targetCoords.x += Random() % static_cast<int>(mySpeed / 2) - static_cast<int>(mySpeed);
			targetCoords.y += Random() % static_cast<int>(mySpeed / 2) - static_cast<int>(mySpeed);
			targetDead = 1;
		}
	}

	float dx = targetCoords.x - x;
	float dy = targetCoords.y - y;
	float distance = FastDist(dx, dy);

	if (distance < weaponLookup[myType].speed)
		exploding = true;

	else {
		PropsToSpeedAndLength(dx, dy);

		x+= speedx;
		y+= speedy;
	}

	return true;
}

void Missile::DrawSelfPixels() {
	StandardLineDraw();
}

Torpedo::Torpedo(float ix, float iy, CoordsInt target, WeaponType iType):
Projectile_Base(ix, iy, target, iType, torpedoBlue), exploding(false) {
	float dx = targetInfo.currentx + targetInfo.weakSpot.x - x;
	float dy = targetInfo.currenty + targetInfo.weakSpot.y - y;
	float distance = FastDist(dx, dy);

	PropsToSpeedAndLength(dx, dy);

	SetDuration(distance);
}


void Torpedo::DrawSelfPixels() {
	if (exploding)
		return;

	int x0 = static_cast<int>(x) - viewx;
	int y0 = static_cast<int>(y) - viewy;
	int x1 = static_cast<int>(x) + lengthx - viewx;
	int y1 = static_cast<int>(y) + lengthy - viewy;

	if (ClipLine(x0, y0, x1, y1)) {
		DrawLine(x0, y0, x1, y1, color);

		//only have to cast immediately if not floats
		float dx = x1 - x0;
		float dy = y1 - y0;
		float distance = FastDist(dx, dy);

		float propx, propy;

		//dont need the sign
		if (distance) {
			propx = fabs(dx) / distance;
			propy = fabs(dy) / distance;
		}

		if (propy > propx) {
			++x0;
			++x1;

			if (ClipLine(x0, y0, x1, y1))
				DrawLine(x0, y0, x1, y1, color);
		}
		else {
			++y0;
			++y1;

			if (ClipLine(x0, y0, x1, y1))
				DrawLine(x0, y0, x1, y1, color);
		}
	}
}

bool Torpedo::Move() {
	if (exploding) {
		if (explodeTimer)
			--explodeTimer;
		else
			//already dealt damage
			return false;
	} else if (!Projectile_Base::Move()) {
		exploding = true;
		explodeTimer = explosionExplodeFrames * framesPerAnimFrame;
	}

	return true;
}

void Torpedo::DrawSelfBitmap() {
	if (exploding) {
		SDL_Rect tempRect = {static_cast<int>(x) - viewx, static_cast<int>(y) - viewy, 0, 0};

		if (explodeTimer > framesPerAnimFrame * 5)
			JSDL.Blt(genPictures[GENPIC_SMSHEXPLODE1], tempRect);
		else if (explodeTimer > framesPerAnimFrame * 4)
			JSDL.Blt(genPictures[GENPIC_SMSHEXPLODE2], tempRect);
		else if (explodeTimer > framesPerAnimFrame * 3)
			JSDL.Blt(genPictures[GENPIC_SMSHEXPLODE3], tempRect);
		else if (explodeTimer > framesPerAnimFrame * 2)
			JSDL.Blt(genPictures[GENPIC_SMSHEXPLODE4], tempRect);
		else if (explodeTimer > framesPerAnimFrame)
			JSDL.Blt(genPictures[GENPIC_SMSHEXPLODE5], tempRect);
		else
			JSDL.Blt(genPictures[GENPIC_SMSHEXPLODE6], tempRect);
	}
}

bool Projectile_Base::CheckToHit(int accuracy, int difficulty) {
	if (difficulty == 0)
		return true;
	else if (Random() % 100 + accuracy - difficulty > 99)
		return true;
	else
		return false;
}

LaserExplosion::LaserExplosion(float ix, float iy):
Projectile_Base(ix, iy), explodeTimer(explosionExplodeFrames * framesPerAnimFrame)
{}

bool LaserExplosion::Move() {
	if (explodeTimer)
		--explodeTimer;
	else
		return false;

	return true;
}

void LaserExplosion::DrawSelfBitmap() {
	SDL_Rect tempRect = {static_cast<int>(x) - viewx, static_cast<int>(y) - viewy, 0, 0};

	if (explodeTimer > framesPerAnimFrame * 5)
		JSDL.Blt(genPictures[GENPIC_SMSHEXPLODE1], tempRect);
	else if (explodeTimer > framesPerAnimFrame * 4)
		JSDL.Blt(genPictures[GENPIC_SMSHEXPLODE2], tempRect);
	else if (explodeTimer > framesPerAnimFrame * 3)
		JSDL.Blt(genPictures[GENPIC_SMSHEXPLODE3], tempRect);
	else if (explodeTimer > framesPerAnimFrame * 2)
		JSDL.Blt(genPictures[GENPIC_SMSHEXPLODE4], tempRect);
	else if (explodeTimer > framesPerAnimFrame)
		JSDL.Blt(genPictures[GENPIC_SMSHEXPLODE5], tempRect);
	else
		JSDL.Blt(genPictures[GENPIC_SMSHEXPLODE6], tempRect);
}

