/*
   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 "RTSUnit_Base.h"
#include "Globals.h"
#include "Group.h"
#include "Inlines.h"
#include "Projectile.h"
#include "Random.h"

using std::find;

AutoFireUnit::AutoFireUnit(const string& iName, int iMySide, int iMyGroup):
RTSUnit_Base(iName, iMySide, iMyGroup) {}

void AutoFireUnit::LoadWeapCoords(istream_iterator<char>& iter, istream_iterator<char>& fileEnd) {
	smallPositions.clear();
	
	iter = find(iter, fileEnd, ':');
	++iter;
	++iter;

	CoordsInt tempCoords;

	for (int i = 0; i != smallNumber; ++i) {
		tempCoords.x = IterToInt(iter, fileEnd);
		++iter;
		tempCoords.y = IterToInt(iter, fileEnd);
		++iter;
		smallPositions.push_back(tempCoords);
	}
	
	for (int i = 0; i != smallNumber; ++i) {
		if (smallPositions[i].x < 0 || smallPositions[i].x >= width || smallPositions[i].y < 0 || smallPositions[i].y >= height) {
			string error = "Position for a small weapon is outside of the unit for " + name;
			throw runtime_error(error.c_str());
		}
		
		if (myType == UT_CaShUnit) {
			for (int j = 0; j != smallNumber; ++j) {
				if (i == j)
					continue;
				if (smallPositions[i] == smallPositions[j]) {
					string error = "Capital ship " + name + " has more than one small weapon in the same position";
					throw runtime_error(error.c_str());
				}
			}
		}
	}

	iter = find(iter, fileEnd, ':');
	++iter;

	bigPosition.x = IterToInt(iter, fileEnd);
	++iter;
	bigPosition.y = IterToInt(iter, fileEnd);
	
	if (bigPosition.x < 0 || bigPosition.x >= width || bigPosition.y < 0 || bigPosition.y >= height) {
		string error = "Position for big weapon is outside of the unit for " + name;
		throw runtime_error(error.c_str());
	}
}

void AutoFireUnit::InitSmall() {
	smallTargets.resize(smallNumber);
	smallTimer.resize(smallNumber);
	smallAiming.resize(smallNumber);

	//smallStage must be initialized with ready
	smallStage.resize(smallNumber, w_Ready);
}

void AutoFireUnit::SetupSmallForFiring(int nSmall, vector<CoordsInt>& inRange) {
	smallStage[nSmall] = w_Aiming;

	int ran = Random() % inRange.size();

	//x is side, y is group
	smallTargets[nSmall].x = inRange[ran].x;
	smallTargets[nSmall].y = inRange[ran].y;

	smallTimer[nSmall] = frameCounter;
	smallAiming[nSmall] = Random() % maxAiming;
}

//we ignore theCommands
void AutoFireUnit::FireSmall(AICommands& theCommands) {
	//loop through this unit's weapons
	for (int i = 0; i != smallNumber; ++i) {
		//if aiming time is up...
		if (smallStage[i] == w_Firing) {
			smallStage[i] = w_Reloading;
			smallTimer[i] = frameCounter;

			float startx;
			if (bFlip)
				startx = myx + width - smallPositions[i].x;
			else
				startx = myx + smallPositions[i].x;

			projectiles.push_back(Projectile(startx, myy + smallPositions[i].y, smallTargets[i], smallType, sides[mySide].laserColor));
		}
	}
}

void AutoFireUnit::Upkeep() {
	RTSUnit_Base::Upkeep();

	//check for small reloading
	for (int i = 0; i != smallNumber; ++i) {
		if (smallStage[i] == w_Reloading && frameCounter - smallTimer[i] > weaponLookup[smallType].reload)
			smallStage[i] = w_Ready;
	}

	//check for small aiming (time decided when we choose
	//a target)
	for (int i = 0; i != smallNumber; ++i) {
		if (smallStage[i] == w_Aiming && frameCounter - smallTimer[i] > smallAiming[i])
			smallStage[i] = w_Firing;
	}
}

void AutoFireUnit::SelectSmallTargets() {
	vector<CoordsInt> inRange;
	FindInRange(inRange, weaponLookup[smallType].range);

	if (inRange.size() > 0) {
		//loop through this unit's weapons
		for (int i = 0; i != smallNumber; ++i) {
			//if the weapon is ready for new orders...
			if (smallStage[i] == w_Ready)
				SetupSmallForFiring(i, inRange);
		}
	}
}

void AutoFireUnit::FindInRange(vector<CoordsInt>& inRange, int range) {
	for (int i = 0; i != sides.size(); ++i) {
		//only enemy sides
		if (sides[i].myFlag == sides[mySide].myFlag)
			continue;

		for (int j = 0; j != sides[i].groups.size(); ++j) {
			if (!sides[i].groups[j].GetAlive())
				continue;

			CoordsInt d = sides[mySide].groups[myGroup].GetDxDyClose(i, j);

			//comparison done on squared distances so
			//we don't have to do square roots
			//because they get squared it doesn't matter if these
			//answers are negative or positive
			if (range * range >= d.x*d.x + d.y*d.y) {
				CoordsInt tempCoords = {i, j};
				inRange.push_back(tempCoords);
			}
		}
	}
}

CoordsInt AutoFireUnit::GetWeakSpot() const {
	CoordsInt ret;
    //relative to centre of unit
	ret.x = Random() % (width >> 1)  - (width >> 2);
	ret.y = Random() % (height >> 1) - (height >> 2);	
	return ret;
}

void AutoFireUnit::GetWeapCoords(vector<CoordsInt>& giveSmall, CoordsInt& giveBig) const {
	giveSmall = smallPositions;
	giveBig = bigPosition;
}

///

CapitalShip::CapitalShip(int iMySide, int iMyGroup, const string& iName, CapShipType iCSType):
AutoFireUnit(iName, iMySide, iMyGroup) {
	//load if found either in struct or on hard drive
	if (sides[mySide].sdStruct.unitData.find(name) != sides[mySide].sdStruct.unitData.end()
	|| DoesFileExist(GetFleetDir(mySide) + name + ".dat"))
		LoadData();

	else {
		myType = UT_CaShUnit;
		myCSType = iCSType;
		DefaultTypeDepStats();
		LoadPicture();
	}
}

void CapitalShip::Upkeep() {
	AutoFireUnit::Upkeep();

	if (armourCurrent < 1) {
		alive = false;
		explodeTimer = csExplodeFrames * framesPerAnimFrame;
	}
}

void CapitalShip::Explode() {
	if (sides[mySide].groups[myGroup].GetOnScreen()) {
		if (explodeTimer > framesPerAnimFrame * 8) {
			switch (myCSType) {
			case CST_Heavy:
				JSDL.Blt(genPictures[GENPIC_HCSEXPLODE1], USRect);
				break;
			case CST_Medium:
				JSDL.Blt(genPictures[GENPIC_MCSEXPLODE1], USRect);
				break;
			case CST_Light:
				JSDL.Blt(genPictures[GENPIC_LCSEXPLODE1], USRect);
				break;
			}
		} else if (explodeTimer > framesPerAnimFrame * 7) {
			switch (myCSType) {
			case CST_Heavy:
				JSDL.Blt(genPictures[GENPIC_HCSEXPLODE2], USRect);
				break;
			case CST_Medium:
				JSDL.Blt(genPictures[GENPIC_MCSEXPLODE2], USRect);
				break;
			case CST_Light:
				JSDL.Blt(genPictures[GENPIC_LCSEXPLODE2], USRect);
				break;
			}
		} else if (explodeTimer > framesPerAnimFrame * 6) {
			switch (myCSType) {
			case CST_Heavy:
				JSDL.Blt(genPictures[GENPIC_HCSEXPLODE3], USRect);
				break;
			case CST_Medium:
				JSDL.Blt(genPictures[GENPIC_MCSEXPLODE3], USRect);
				break;
			case CST_Light:
				JSDL.Blt(genPictures[GENPIC_LCSEXPLODE3], USRect);
				break;
			}
		} else if (explodeTimer > framesPerAnimFrame * 5) {
			switch (myCSType) {
			case CST_Heavy:
				JSDL.Blt(genPictures[GENPIC_HCSEXPLODE4], USRect);
				break;
			case CST_Medium:
				JSDL.Blt(genPictures[GENPIC_MCSEXPLODE4], USRect);
				break;
			case CST_Light:
				JSDL.Blt(genPictures[GENPIC_LCSEXPLODE4], USRect);
				break;
			}
		} else if (explodeTimer > framesPerAnimFrame * 4) {
			switch (myCSType) {
			case CST_Heavy:
				JSDL.Blt(genPictures[GENPIC_HCSEXPLODE5], USRect);
				break;
			case CST_Medium:
				JSDL.Blt(genPictures[GENPIC_MCSEXPLODE5], USRect);
				break;
			case CST_Light:
				JSDL.Blt(genPictures[GENPIC_LCSEXPLODE5], USRect);
				break;
			}
		} else if (explodeTimer > framesPerAnimFrame * 3) {
			switch (myCSType) {
			case CST_Heavy:
				JSDL.Blt(genPictures[GENPIC_HCSEXPLODE6], USRect);
				break;
			case CST_Medium:
				JSDL.Blt(genPictures[GENPIC_MCSEXPLODE6], USRect);
				break;
			case CST_Light:
				JSDL.Blt(genPictures[GENPIC_LCSEXPLODE6], USRect);
				break;
			}
		} else if (explodeTimer > framesPerAnimFrame * 2) {
			switch (myCSType) {
			case CST_Heavy:
				JSDL.Blt(genPictures[GENPIC_HCSEXPLODE7], USRect);
				break;
			case CST_Medium:
				JSDL.Blt(genPictures[GENPIC_MCSEXPLODE7], USRect);
				break;
			case CST_Light:
				JSDL.Blt(genPictures[GENPIC_LCSEXPLODE7], USRect);
				break;
			}
		} else if (explodeTimer > framesPerAnimFrame) {
			switch (myCSType) {
			case CST_Heavy:
				JSDL.Blt(genPictures[GENPIC_HCSEXPLODE8], USRect);
				break;
			case CST_Medium:
				JSDL.Blt(genPictures[GENPIC_MCSEXPLODE8], USRect);
				break;
			case CST_Light:
				JSDL.Blt(genPictures[GENPIC_LCSEXPLODE8], USRect);
				break;
			}
		} else {
			switch (myCSType) {
			case CST_Heavy:
				JSDL.Blt(genPictures[GENPIC_HCSEXPLODE9], USRect);
				break;
			case CST_Medium:
				JSDL.Blt(genPictures[GENPIC_MCSEXPLODE9], USRect);
				break;
			case CST_Light:
				JSDL.Blt(genPictures[GENPIC_LCSEXPLODE9], USRect);
				break;
			}
		}
	}
}

void CapitalShip::ChangeCSType(CapShipType newType) {
	myCSType = newType;

	//includes 3 character size extension
	string oldPicName = picName;
	string newPicName = picName.substr(0, picName.size() - 3);
	DefaultTypeDepStats();
	//just got overwritten by defaulting stats
	picName = oldPicName;
	ChangeUnitPic(newPicName);
}

void CapitalShip::DefaultTypeDepStats() {
	engineName = globalSettings.defaultCSEngine;

	bigPosition.x = 0;
	bigPosition.y = 0;

	picName = globalSettings.defaultCSPic;

	switch (myCSType) {
	case CST_Heavy:
		width = HCSWidth;
		height = HCSHeight;
		capacity = HCSCap;
		armourName = globalSettings.defaultHCSArmour;
		break;

	case CST_Medium:
		width = MCSWidth;
		height = MCSHeight;
		capacity = MCSCap;
		armourName = globalSettings.defaultMCSArmour;
		break;

	case CST_Light:
		width = LCSWidth;
		height = LCSHeight;
		capacity = LCSCap;
		armourName = globalSettings.defaultLCSArmour;
		break;
	}

	RTSUnit_Base::DefaultTypeDepStats();
}

int CapitalShip::GetFrCapacity() const {
	switch (myCSType) {
	case CST_Heavy:
		return HCSFrCap;
		break;

	case CST_Medium:
		return MCSFrCap;
		break;

	case CST_Light:
		return LCSFrCap;
		break;
	}
}

//these are default coords for new cap ships, they are overwritten by those found in unit files
void CapitalShip::SetSmallNumber() {
	smallPositions.clear();

	if (smallType == WT_None)
		smallNumber = 0;
	else {
		CoordsInt tempCoords;

		switch (myCSType) {
		case CST_Heavy:
			smallNumber	= HCSCap;

			tempCoords.x = 70;
			tempCoords.y = 26;
			smallPositions.push_back(tempCoords);
			tempCoords.x = 200;
			smallPositions.push_back(tempCoords);
			tempCoords.x = 330;
			smallPositions.push_back(tempCoords);
			tempCoords.x = 450;
			smallPositions.push_back(tempCoords);

			tempCoords.x = 70;
			tempCoords.y = 82;
			smallPositions.push_back(tempCoords);
			tempCoords.x = 180;
			smallPositions.push_back(tempCoords);
			tempCoords.x = 300;
			smallPositions.push_back(tempCoords);
			tempCoords.x = 450;
			smallPositions.push_back(tempCoords);

			tempCoords.x = 450;
			tempCoords.y = HCSHeight / 2;
			smallPositions.push_back(tempCoords);
			break;

		case CST_Medium:
			smallNumber = MCSCap;

			tempCoords.x = 70;
			tempCoords.y = 26;
			smallPositions.push_back(tempCoords);
			tempCoords.x = 180;
			smallPositions.push_back(tempCoords);
			tempCoords.x = 300;
			tempCoords.y = 30;
			smallPositions.push_back(tempCoords);

			tempCoords.x = 70;
			tempCoords.y = 82;
			smallPositions.push_back(tempCoords);
			tempCoords.x = 180;
			smallPositions.push_back(tempCoords);
			tempCoords.x = 300;
			tempCoords.y = 76;
			smallPositions.push_back(tempCoords);
			break;

		case CST_Light:
			smallNumber = LCSCap;

			tempCoords.x = 50;
			tempCoords.y = LCSHeight / 2;
			smallPositions.push_back(tempCoords);
			tempCoords.x = 150;
			smallPositions.push_back(tempCoords);
			tempCoords.x = 250;
			smallPositions.push_back(tempCoords);
			break;
		}
	}
}

Frigate::Frigate(int iMySide, int iMyGroup, const string& iName):
AutoFireUnit(iName, iMySide, iMyGroup) {
	//load if found either in struct or on hard drive
	if (sides[mySide].sdStruct.unitData.find(name) != sides[mySide].sdStruct.unitData.end()
	        || DoesFileExist(GetFleetDir(mySide) + name + ".dat"))
		LoadData();

	else {
		myType = UT_FrUnit;
		DefaultTypeDepStats();
		LoadPicture();
	}
}

void Frigate::DefaultTypeDepStats() {
	width = FrWidth;
	height = FrHeight;
	picName = globalSettings.defaultFrPic;
	engineName = globalSettings.defaultFrEngine;
	armourName = globalSettings.defaultFrArmour;

	bigPosition.x = 75;
	bigPosition.y = 25;

	RTSUnit_Base::DefaultTypeDepStats();
}

void Frigate::Upkeep() {
	AutoFireUnit::Upkeep();

	if (armourCurrent < 1) {
		alive = false;
		explodeTimer = frExplodeFrames * framesPerAnimFrame;
	}
}

void Frigate::Explode() {  
	if (sides[mySide].groups[myGroup].GetOnScreen()) {
		if (explodeTimer > framesPerAnimFrame * 6)
			JSDL.Blt(genPictures[GENPIC_FREXPLODE1], USRect);
		else if (explodeTimer > framesPerAnimFrame * 5)
			JSDL.Blt(genPictures[GENPIC_FREXPLODE2], USRect);
		else if (explodeTimer > framesPerAnimFrame * 4)
			JSDL.Blt(genPictures[GENPIC_FREXPLODE3], USRect);
		else if (explodeTimer > framesPerAnimFrame * 3)
			JSDL.Blt(genPictures[GENPIC_FREXPLODE4], USRect);
		else if (explodeTimer > framesPerAnimFrame * 2)
			JSDL.Blt(genPictures[GENPIC_FREXPLODE5], USRect);
		else if (explodeTimer > framesPerAnimFrame)
			JSDL.Blt(genPictures[GENPIC_FREXPLODE6], USRect);
		else
			JSDL.Blt(genPictures[GENPIC_FREXPLODE7], USRect);
	}
}

void Frigate::SetSmallNumber() {
	smallPositions.clear();

	if (smallType == WT_None)
		smallNumber = 0;
	else {
		smallNumber = FrSmNumber;
		CoordsInt tempCoords;

		tempCoords.x = 30;
		tempCoords.y = FrHeight / 2;
		smallPositions.push_back(tempCoords);
		tempCoords.x = 70;
		tempCoords.y = FrHeight / 2;
		smallPositions.push_back(tempCoords);
	}
}

void Frigate::DrawSelfPixels() {
	if (weaponLookup[bigType].category == WCAT_Large && bigStage == w_Firing && alive) {
		CoordsInt tmp = sides[bigTarget.x].groups[bigTarget.y].GetUnitCenter(bigTargetUnit);

		int startx;
		if (bFlip)
			startx = static_cast<int>(myx) + width - bigPosition.x;
		else
			startx = static_cast<int>(myx) + bigPosition.x;

		int finx = tmp.x + targetWeakSpot.x;
		int finy = tmp.y + targetWeakSpot.y;

		Projectile_Base::DrawBigLaser(startx, static_cast<int>(myy) + bigPosition.y, finx, finy, sides[mySide].laserColor);
	}
}

