//Copyright (C) 2007 SiegeLord
//See ReadMe.txt and License.txt for copying information

#include "orders.h"
#include "world.h"
#include "camera.h"
#include "ship.h"
#include "explosion.h"
#include "shipregistry.h"
#include "quadtree.h"

extern SQuadNode* g_qnRootNode;
extern SShip* g_obMotherShip[2];
extern int g_nPlayer;

void StopShip(SShip* ship)
{
	if(ship->polar_vel.x > 0.0f)
		ship->ActivateThrusters(1, 0.0f);
	if(ship->polar_vel.x < 0.0f)
		ship->ActivateThrusters(3, 0.0f);
	if(ship->polar_vel.y > 0.0f)
		ship->ActivateThrusters(0, 0.0f);
	if(ship->polar_vel.y < 0.0f)
		ship->ActivateThrusters(2, 0.0f);
}

//note! it pushes front!
bool IdleSpreadApart(SShip* ship)
{
	list<SCollidableObject*> selected_list;

	float max_dim = max(ship->bbox.Max.x, ship->bbox.Max.y);
	max_dim /= 2.0f;

	vector2d cur_pos = ship->GetGlobalPosition();

	if(QuadTreeCircleTestMulti(g_qnRootNode, cur_pos, max_dim, &selected_list))
	{
		vector2d closest;
		float closest_dist_sqr = 100000000;
		for(list<SCollidableObject*>::iterator it = selected_list.begin(); it != selected_list.end(); it++)
		{
			vector2d pos = (*it)->GetGlobalPosition();
			float dist = vector2d(pos, cur_pos).GetLengthSQ();
			if(dist < closest_dist_sqr && (*it) != ship)
			{
				closest_dist_sqr = dist;
				closest = pos;
			}
		}
		if(closest_dist_sqr < 100000000)
		{
			vector2d dir = cur_pos - closest;
			if(Feq(closest_dist_sqr, 0.0f))
			{
				dir.x = Rand(-1, 1);
				dir.y = Rand(-1, 1);
			}
			dir.Normalize();
			closest = closest + dir * max_dim;
			vector2d dest(closest.GetLength(), atan2(closest.y, closest.x));
			SMoveOrder* order = new SMoveOrder(dest);
			order->ship = ship;
			ship->order_list.push_front(order);
			return true;
		}
	}
	selected_list.clear();
	return false;
}

bool SIdleOrder::Handle()
{
	if(Rand() > 0.9)
	{
		list<SCollidableObject*> selected_list;

		vector2d cur_pos = ship->GetGlobalPosition();

		if(QuadTreeCircleTestMulti(g_qnRootNode, cur_pos, ship->sense_range, &selected_list))
		{
			SShip* target_ship = 0;
			for(list<SCollidableObject*>::iterator it = selected_list.begin(); it != selected_list.end(); it++)
			{
				SShip* ship1 = (SShip*)(*it);
				if(ship1->player != ship->player && (target_ship == 0 || ship1->hitpoints < target_ship->hitpoints))
				{
					target_ship = ship1;
				}
			}
			if(target_ship != 0)
			{
				SAttackOrder* order = new SAttackOrder();
				order->ship = ship;
				order->target_ship_id = target_ship->ID;

				ship->order_list.push_back(order);
				return true;
			}
		}
		selected_list.clear();
	}

	//see if we can move away!
	if(Rand() > 0.9)
	{
		IdleSpreadApart(ship);
	}

	StopShip(ship);
	return false;
}

bool SStopOrder::Handle()
{
	timer += g_fDelta;

	StopShip(ship);

	return Feq(ship->polar_vel.x, 0.0f) && timer > time_to_stop;
}

SMoveOrder::SMoveOrder(vector2d &dest)
{
	order_type = move_order;
	destination = dest;
	SnapVar(destination.x, GetMinWorldRadius(), GetMaxWorldRadius());
}

bool HandleThrusters(SShip* ship, vector2d& destination)
{
	if(ship->polar_accel == g_vecZero)//this one can't move
	{
		return true;
	}
	vector2d diff;
	diff.x = destination.x - ship->polar_pos.x;
	diff.y = SubtractAngles(destination.y, ship->polar_pos.y);

	//note the different scales... has to do with angles and distances naturally
	if(fabs(diff.x) < 1.0f && fabs(diff.y) < 0.005)
		return true;

	if(fabs(2 * ship->polar_accel.x * diff.x) < ship->polar_vel.x * ship->polar_vel.x)
	{
		if(ship->polar_vel.x > 0.0f)
			ship->ActivateThrusters(1, 0.0f);
		else
			ship->ActivateThrusters(3, 0.0f);
	}
	else
	{
		if(fabs(diff.x) > 0.1)
		{
			if(diff.x > 0)
				ship->ActivateThrusters(3, ship->polar_max_vel.x);
			else
				ship->ActivateThrusters(1, -ship->polar_max_vel.x);
		}
	}

	if(fabs(2 * ship->polar_accel.y * diff.y) < ship->polar_vel.y * ship->polar_vel.y)
	{
		if(ship->polar_vel.y > 0.0f)
			ship->ActivateThrusters(0, 0.0f);
		else
			ship->ActivateThrusters(2, 0.0f);
	}
	else
	{
		if(fabs(diff.y) > 0.001)
		{
			if(diff.y > 0)
				ship->ActivateThrusters(2, ship->polar_max_vel.y);
			else
				ship->ActivateThrusters(0, -ship->polar_max_vel.y);
		}
	}
	return false;
}

bool SMoveOrder::Handle()
{
	return HandleThrusters(ship, destination);
}

bool SDieOrder::Handle()
{
	delay -= g_fDelta;
	if(delay < 0)
	{
		ship->dead = true;
		if(ship->type == mothership_type)
			g_obMotherShip[ship->player] = 0;
		vector2d pos = ship->GetGlobalPosition();
		CreateExplosion(pos, 0.9f, 0xff05afff);
		return true;
	}
	else if(Rand() > 0.9)
	{
		vector2d pos;
		pos.x = Rand(ship->bbox.Min.x, ship->bbox.Max.x);
		pos.y = Rand(ship->bbox.Min.y, ship->bbox.Max.y);
		ship->net_transform.TransformVector(&pos);
		CreateExplosion(pos, 0.3f, 0xff05afff);
	}
	return false;
}

bool SAttackOrder::Handle()
{
	SShip* target_ship = 0;
	if(target_ship_id > -1)
	{
		target_ship = GetShipByID(target_ship_id);
		if(target_ship)
			target_loc = target_ship->GetGlobalPosition();
		else//target was lost, we are done
			return true;
	}

	ship->TargetAllWeapons(target_loc, target_ship);
	float range = ship->GetSmallestWeaponRange();

	vector2d dir = target_loc - ship->GetGlobalPosition();
	float dir_len = dir.GetLength();

	if(dir_len > range)
	{
		if(ship->polar_accel == g_vecZero)
			return 1;//we can't reach it
		vector2d new_pos = target_loc - dir * range / dir_len;//this makes sure we move in the direction of the target, but not within range

		move_dest.x = new_pos.GetLength();
		move_dest.y = atan2f(new_pos.y, new_pos.x);

		HandleThrusters(ship, move_dest);
		moving = true;
	}
	else
	{
		if(!IdleSpreadApart(ship))
			StopShip(ship);
		moving = false;
	}


	//else//stand ground
	//	HandleThrusters(ship, ship->GetGlobalPosition());

	return false;
}

void HandleOrders(list<SShip*> *selected_ships)
{
	list<SCollidableObject*> selected_list;//used for tests

	if(IsMKeyJustPressed(1))
	{
		vector2d pos = GetWorldMousePos();

		for(list<SShip*>::iterator it = selected_ships->begin(); it != selected_ships->end(); it++)
		{
			SShip* ship = *it;
			if(ship->order_list.size() > 0 && ship->order_list.front()->order_type == die_order)
				continue;

			if(ship->player != g_nPlayer)//can't order enemy ships about...
				continue;

			if(!IsShiftDown())
			{
				ship->order_list.clear();
			}
			else
			{
				//pop away the blasted idle order...
				if(ship->order_list.front()->order_type == idle_order)
				{
					delete ship->order_list.front();
					ship->order_list.pop_front();
				}
			}

			bool found_target = false;
			if(QuadTreePointTestMulti(g_qnRootNode, pos, &selected_list))
			{
				for(list<SCollidableObject*>::iterator it2 = selected_list.begin(); it2 != selected_list.end(); it2++)
				{
					SShip* target_ship = (SShip*)(*it2);
					if(target_ship->player != ship->player)
					{//Attack!!
						SAttackOrder* order = new SAttackOrder;

						target_ship->flashy_timer = 1.0f;
						order->target_ship_id = target_ship->ID;
						order->target_loc = target_ship->GetGlobalPosition();
						order->ship = ship;
						order->move_dest = target_ship->polar_pos;
						ship->order_list.push_back(order);

						found_target = true;
						break;
					}
				}
				selected_list.clear();
			}
			if(!found_target)//we just move there then
			{
				vector2d dest;
				dest.x = pos.GetLength();
				dest.y = atan2f(pos.y, pos.x);

				SMoveOrder* order = new SMoveOrder(dest);
				order->ship = ship;

				ship->order_list.push_back(order);
				ship->CeaseFire();
			}
		}
	}
	if(IsKeyDown(KEY_S))
	{
		for(list<SShip*>::iterator it = selected_ships->begin(); it != selected_ships->end(); it++)
		{
			if((*it)->player != g_nPlayer)//can't order enemy ships about...
				continue;
			if((*it)->order_list.size() > 0 && (*it)->order_list.front()->order_type != die_order)
			{
				(*it)->order_list.clear();
				(*it)->CeaseFire();
			}
		}
	}
	if(IsKeyJustPressed(KEY_DEL))
	{
		for(list<SShip*>::iterator it = selected_ships->begin(); it != selected_ships->end(); it++)
		{
			if((*it)->player != g_nPlayer)//can't order enemy ships about...
				continue;
			if((*it)->order_list.size() > 0 && (*it)->order_list.front()->order_type == die_order)
				continue;
			(*it)->order_list.clear();
			SDieOrder* order = new SDieOrder();
			order->delay = Rand(0.0f, 2.0f);
			order->ship = (*it);
			(*it)->order_list.push_back(order);
		}
	}
}
