/*
   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"

#include <boost/filesystem/operations.hpp>

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;
	}
}

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::SelectSmallTargets() {
	//loop through this unit's weapons, and work out if we're in range for each one
	for (int i = 0; i != smallNumber; ++i) {
		//only work out ranges if the weapon is ready for new orders and there's something in range...
		if (smallStage[i] != w_Ready)
			continue;

		int startx;
		if (bFlip)
			startx = static_cast<int>(myx) + width - smallPositions[i].x;
		else
			startx = static_cast<int>(myx) + smallPositions[i].x;

		vector<CoordsInt> inRange;
		CoordsInt myCoords = {startx, static_cast<int>(myy) + smallPositions[i].y};
		sides[mySide].groups[myGroup].FindInRange(inRange, myCoords, weaponLookup[smallType].range);

		if (inRange.size() > 0)
			SetupSmallForFiring(i, inRange);
	}
}

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_HCSEXLODE1], USRect);
				break;
			case CST_Medium:
				JSDL.Blt(genPictures[GENPIC_MCSEXLODE1], USRect);
				break;
			case CST_Light:
				JSDL.Blt(genPictures[GENPIC_LCSEXLODE1], USRect);
				break;
			}
		} else if (explodeTimer > framesPerAnimFrame * 7) {
			switch (myCSType) {
			case CST_Heavy:
				JSDL.Blt(genPictures[GENPIC_HCSEXLODE2], USRect);
				break;
			case CST_Medium:
				JSDL.Blt(genPictures[GENPIC_MCSEXLODE2], USRect);
				break;
			case CST_Light:
				JSDL.Blt(genPictures[GENPIC_LCSEXLODE2], USRect);
				break;
			}
		} else if (explodeTimer > framesPerAnimFrame * 6) {
			switch (myCSType) {
			case CST_Heavy:
				JSDL.Blt(genPictures[GENPIC_HCSEXLODE3], USRect);
				break;
			case CST_Medium:
				JSDL.Blt(genPictures[GENPIC_MCSEXLODE3], USRect);
				break;
			case CST_Light:
				JSDL.Blt(genPictures[GENPIC_LCSEXLODE3], USRect);
				break;
			}
		} else if (explodeTimer > framesPerAnimFrame * 5) {
			switch (myCSType) {
			case CST_Heavy:
				JSDL.Blt(genPictures[GENPIC_HCSEXLODE4], USRect);
				break;
			case CST_Medium:
				JSDL.Blt(genPictures[GENPIC_MCSEXLODE4], USRect);
				break;
			case CST_Light:
				JSDL.Blt(genPictures[GENPIC_LCSEXLODE4], USRect);
				break;
			}
		} else if (explodeTimer > framesPerAnimFrame * 4) {
			switch (myCSType) {
			case CST_Heavy:
				JSDL.Blt(genPictures[GENPIC_HCSEXLODE5], USRect);
				break;
			case CST_Medium:
				JSDL.Blt(genPictures[GENPIC_MCSEXLODE5], USRect);
				break;
			case CST_Light:
				JSDL.Blt(genPictures[GENPIC_LCSEXLODE5], USRect);
				break;
			}
		} else if (explodeTimer > framesPerAnimFrame * 3) {
			switch (myCSType) {
			case CST_Heavy:
				JSDL.Blt(genPictures[GENPIC_HCSEXLODE6], USRect);
				break;
			case CST_Medium:
				JSDL.Blt(genPictures[GENPIC_MCSEXLODE6], USRect);
				break;
			case CST_Light:
				JSDL.Blt(genPictures[GENPIC_LCSEXLODE6], USRect);
				break;
			}
		} else if (explodeTimer > framesPerAnimFrame * 2) {
			switch (myCSType) {
			case CST_Heavy:
				JSDL.Blt(genPictures[GENPIC_HCSEXLODE7], USRect);
				break;
			case CST_Medium:
				JSDL.Blt(genPictures[GENPIC_MCSEXLODE7], USRect);
				break;
			case CST_Light:
				JSDL.Blt(genPictures[GENPIC_LCSEXLODE7], USRect);
				break;
			}
		} else if (explodeTimer > framesPerAnimFrame) {
			switch (myCSType) {
			case CST_Heavy:
				JSDL.Blt(genPictures[GENPIC_HCSEXLODE8], USRect);
				break;
			case CST_Medium:
				JSDL.Blt(genPictures[GENPIC_MCSEXLODE8], USRect);
				break;
			case CST_Light:
				JSDL.Blt(genPictures[GENPIC_LCSEXLODE8], USRect);
				break;
			}
		} else {
			switch (myCSType) {
			case CST_Heavy:
				JSDL.Blt(genPictures[GENPIC_HCSEXLODE9], USRect);
				break;
			case CST_Medium:
				JSDL.Blt(genPictures[GENPIC_MCSEXLODE9], USRect);
				break;
			case CST_Light:
				JSDL.Blt(genPictures[GENPIC_LCSEXLODE9], 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;
	}
}

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;
		}
	}
}

void CapitalShip::DrawSelfPixels() {
	if (sides[mySide].groups[myGroup].GetSelected()) {
		for (int i = 0; i != smallNumber; ++i) {
			int xcoord;
			if (bFlip)
				xcoord = static_cast<int>(myx) + width - smallPositions[i].x;
			else
				xcoord = static_cast<int>(myx) + smallPositions[i].x;
			int ycoord = static_cast<int>(myy) + smallPositions[i].y;
	
			JSDL.CircleColor(JSDL.screen, xcoord - viewx, ycoord - viewy, weaponLookup[smallType].range, 0xFFFFFFFF);
		}
	}
}

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::SelectSmallTargets() {
	vector<CoordsInt> inRange;
	CoordsInt myCoords = GetCenter();
	sides[mySide].groups[myGroup].FindInRange(inRange, myCoords, 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 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_FREXLODE1], USRect);
		else if (explodeTimer > framesPerAnimFrame * 5)
			JSDL.Blt(genPictures[GENPIC_FREXLODE2], USRect);
		else if (explodeTimer > framesPerAnimFrame * 4)
			JSDL.Blt(genPictures[GENPIC_FREXLODE3], USRect);
		else if (explodeTimer > framesPerAnimFrame * 3)
			JSDL.Blt(genPictures[GENPIC_FREXLODE4], USRect);
		else if (explodeTimer > framesPerAnimFrame * 2)
			JSDL.Blt(genPictures[GENPIC_FREXLODE5], USRect);
		else if (explodeTimer > framesPerAnimFrame)
			JSDL.Blt(genPictures[GENPIC_FREXLODE6], USRect);
		else
			JSDL.Blt(genPictures[GENPIC_FREXLODE7], 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);
	}
}

