#include "empire.h"

_action action[3];

#define AI_UNIT_BEGIN        0
#define AI_UNIT_SCROLL       1
#define AI_UNIT_MAKE_PATHMAP 2
#define AI_UNIT_THINK        3
#define AI_UNIT_PAUSE        4
#define AI_UNIT_ACT          5

bool ai_unit_animating;
bool HQ_threat;
_action ai_unit_animation;
int in_battle, atk, def;
vector<_unit*> lander;
vector<_unit*> need_transport;
vector<_loc> base;
vector<_aitarget> targets;
_loc capital;  //location of the AI HQ - gets set at the beginning of each AI turn
vector<_aitarget> list_targets(_unit *u);

int do_ai(int plyr)
{
	return do_ai(plyr, false);
}

int do_ai(int plyr, bool reset_everything)
{
	static bool first = true;
	static list<_unit *> active_units;
	static _actionset a;
	static int draw_stats, last_statpos;
	static int initial_delay, delay, aistatus, anum;
	int t1, t2, t3, t4, i;
	_tile *thetile, *t;
	_loc temploc;
	_unit *u;
	unitprops *p;

	if (reset_everything)
	{  //if the user returned to the main menu in the middle of an AI turn
		first = 1;
		return 1;
	}

	if (first)
	{
		lander.clear();
		need_transport.clear();
		player[plyr].sort_units();
		active_units.clear();
		i = 0;
		while (i < 50)
		{
			if (player[plyr].unit[i].exists == 1)
			{
				p = &unitstats[player[plyr].unit[i].type];
				if ((p->movetype == M_LANDER) && (p->loadcapacity > 0))
				{  //this creates a vector of pointers to landers, handy later on
					lander.push_back(&player[plyr].unit[i]);
				}

				active_units.push_back(&player[plyr].unit[i]);
			}
			i++;
		}

		t2 = 0;
		while (t2 < worldmap.h)
		{
			t1 = 0;
			while (t1 < worldmap.l)
			{
				thetile = &worldmap.tile[t1][t2];
				if (thetile->owned_by(plyr))
				{
					if (thetile->is_unit_producing())
					{  //if it belongs to the player and produces units
						temploc.x = t1;
						temploc.y = t2;
						base.push_back(temploc);  //create a vector of base locations
					}
					else if (thetile->is_HQ())
					{  //mark the location of the player's HQ
						capital.x = t1;
						capital.y = t2;
					}
				}
				t1++;
			}
			t2++;
		}

		HQ_threat = enemy_threat(plyr);

		i = 0;
		first = false;
		in_battle = 0;
		atk = -1;
		def = -1;
		draw_stats = 0;
		last_statpos = -1;
		initial_delay = 0;
		delay = 0;
		aistatus = AI_UNIT_BEGIN;
		counter = 0;
	}

	if (initial_delay < 15)
	{  //have a little delay before the units start doing stuff
		initial_delay++;
	}
	else if (in_battle == 0)
	{
		if (check_explodes())
		{  //if check_explodes found an explosion to do
			return 30;  //get out of the AI function early and indicate an explosion
		}

		if (!active_units.empty())
		{
			u = *active_units.begin();
			if (u->ready == 0)
			{  //if it isn't ready, why is it in active_units anyway?
				active_units.pop_front();
				delay = 0;
				aistatus = AI_UNIT_BEGIN;
			}
			else
			{
				switch (aistatus)
				{
				case AI_UNIT_BEGIN:
					if (delay < 15)
					{  //have a little delay between units as well
						delay++;
					}
					else
					{
						aistatus = AI_UNIT_SCROLL;
					}
					break;
				case AI_UNIT_SCROLL:
					if (scroll_to_location(u->tilex, u->tiley) == 1)
					{  //smooth scroll to the unit, which returns 1 when finished
						aistatus = AI_UNIT_MAKE_PATHMAP;
						return 20;  //returning a 2 in the tens column prevents the player from bringing up a menu
					}
					break;
				case AI_UNIT_MAKE_PATHMAP:
					worldmap.create_pathmap(plyr, unitstats[u->type].movetype, u->tilex, u->tiley);
					show_movable_tiles(u);
					counter = 0;  //reset the counter, since creating the pathmap might take a while
					aistatus = AI_UNIT_THINK;
					break;
				case AI_UNIT_THINK:
					show_movable_tiles(u);
					t1 = (u->tilex - worldmap.scroll_x) * MAP_TILE_SIZE;
					t2 = (u->tiley - worldmap.scroll_y) * MAP_TILE_SIZE;
					switch(u->owner)
					{
					case 0:
						t3 = ORANGE;
						break;
					case 1:
						t3 = BLUE;
						break;
					case 2:
						t3 = GREEN;
						break;
					case 3:
						t3 = YELLOW;
						break;
					default:
						t3 = BLACK;
						break;
					}  //draw a rectangle around the unit, colored the same as the unit's team
					buffer_rect(t1 + 1, t2 + 1, t1 + MAP_TILE_SIZE - 1, t2 + MAP_TILE_SIZE - 1, t3);
					targets.resize(0);
					a = ponder_action(u);
					delay = 0;
					aistatus = AI_UNIT_PAUSE;
					counter = 0;  //this may have taken a while, but nothing happened on
					break;        //screen, so just set the counter like it took no time
					case AI_UNIT_PAUSE:
						if (delay < 25)  //the unit's range is displayed for 25 logic loops (.5 seconds)
						{  //in other words, it shows the range while thinking, then pauses before acting
							show_movable_tiles(u);
							u->draw(false);
							t1 = (u->tilex - worldmap.scroll_x) * MAP_TILE_SIZE;
							t2 = (u->tiley - worldmap.scroll_y) * MAP_TILE_SIZE;
							switch(u->owner)
							{
							case 0:
								t3 = ORANGE;
								break;
							case 1:
								t3 = BLUE;
								break;
							case 2:
								t3 = GREEN;
								break;
							case 3:
								t3 = YELLOW;
								break;
							default:
								t3 = BLACK;
								break;
							}  //draw a rectangle around the unit, colored the same as the unit's team
							buffer_rect(t1 + 1, t2 + 1, t1 + MAP_TILE_SIZE - 1, t2 + MAP_TILE_SIZE - 1, t3);
							delay++;
						}
						if (delay == 25)
						{
							aistatus = AI_UNIT_ACT;
							anum = 0;
							if (u->tilex - worldmap.scroll_x < 4)
							{
								draw_stats = 2;
							}
							else
							{
								draw_stats = 1;
							}
						}
						break;
					case AI_UNIT_ACT:
						if ((!u->is_moving()) && (!ai_unit_animating))
						{  //if the unit has is finished with an action or starting the first

							t = &worldmap.tile[u->tilex][u->tiley];
							if (unitstats[u->type].cancapture)
							{
								if ((t->is_building()) && ((t->is_unit_producing()) || (t->is_HQ())))
								{  //if it's a building that gets permanently captured
									if (!t->owned_by(u->owner))
									{  //if it's not owned by the player
										capture_building(u);
									}  // (this means you can capture teammates' buildings)
								}
							}

							switch(anum)
							{
							case 0:
							case 1:
							case 2:
								act(u, a.action[anum]);
								anum++;
								if (u->exists == 0)
								{  //if a battle or being loaded into a transport causes it to disappear
									u->ready = 0;
									active_units.pop_front();
									aistatus = AI_UNIT_BEGIN;
									delay = 0;
								}
								break;
							case 3:  //if the final action has just been done, this unit is finished
								u->ready = 0;
								active_units.pop_front();
								aistatus = AI_UNIT_BEGIN;
								delay = 0;
								update_cities();
								break;
							}
						}
						else if (ai_unit_animating)
						{
							switch(ai_unit_animation.type)
							{
							case action_teleport:
								ai_teleport(u, ai_unit_animation.loc);
								break;
							}
						}
						else
						{
							u->do_movement();
							u->draw();
						}
						break;
				}
			}  //end of "if the unit is ready"
		}
		else  //if active_units is empty
		{
			if (delay == 0)
			{
				empdebug("AI is deciding what units to build...");

				if (!make_new_units(plyr))
				{
					empdebug("AI has finished building units.");
					counter = 0;  //this also tends to take a while, but has no on-screen effect
					delay++;
				}
			}
			else if (delay < 35)
			{  //pause for a little over half a second after the units are made
				delay++;
			}
			else
			{
				first = true;
				delay = 0;
				empdebug("Finished AI turn");
				return 1;  //indicate that it is done
			}
		}
	}
	else  //if there is a battle animation going on
	{
		t1 = atk / 100;
		t2 = atk % 100;
		t3 = def / 100;
		t4 = def % 100;
		in_battle = do_battle(&player[t1].unit[t2], &player[t3].unit[t4]);
		return 20;
	}
	return 0;  //indicate that it is not finished
}

_actionset ponder_action(_unit *u)
{
	_actionset aset;
	action[0].type = action_none;
	action[0].loc.x = -1;
	action[0].loc.y = -1;
	action[1].type = action_none;
	action[1].loc.x = -1;
	action[1].loc.y = -1;
	action[2].type = action_none;  //third action only used for unloading landers
	action[2].loc.x = -1;
	action[2].loc.y = -1;
	targets = list_targets(u);
	unitprops *p = &unitstats[u->type];

	empdebug("AI thought:");
	empdebug(unitstats[u->type].name);
	empdebug("Current location:");
	empdebug(u->tilex);
	empdebug(u->tiley);

	if (HQ_threat)
	{
		empdebug("Check HQ park...");
		check_HQ_park(u);
		empdebug("Check capturing...");
		check_capturing(u);
	}
	else
	{
		empdebug("Check capturing...");
		check_capturing(u);
		empdebug("Check HQ park...");
		check_HQ_park(u);
	}

	empdebug("Check attackable units...");
	check_attackable_units(u);
	empdebug("Check low health...");
	check_low_health(u);

	if (action[0].type == action_none)
	{  //if it didn't capture, doesn't need to get fixed, and can't attack this turn
		if (p->cancapture)
		{  //if it can capture things, head for the closest capturable building
			empdebug("Check closest capturable building...");
			check_closest_capturable(u);
		}
		if (p->attacktype != -1)
		{
			if (action[0].type == action_none)
			{
				empdebug("Check closest attackable unit...");
				check_closest_attackable(u);
			}
		}
		else if ((p->basetype == LAND) && (p->loadtype == 1))
		{
			empdebug("Check APC options...");
			check_APC_options(u);
		}
		else if ((p->basetype == AIR) && (p->loadtype == 1))
		{
			empdebug("Check T_Copter options...");
			check_tcopter_options(u);
		}
		else if ((p->basetype == SEA) && (p->loadtype == 2))
		{  //if the unit is a lander
			empdebug("Check Lander options...");
			check_lander_options(u);
		}
	}
	empdebug("Check available teleports...");
	check_teleports(u);  //if the unit is moving somewhere, see if it can get there faster by teleporting
	//if the unit has nowhere to go, see if it can get somewhere by teleporting
	if ((p->movetype == M_INFANTRY) || (p->movetype == M_MECH))
	{
		empdebug("Check for a convenient APC...");
		check_convenient_APC(u);
		if (action[0].type == action_none)
		{
			empdebug("Check for an available T_Copter...");
			check_available_copter(u);
		}
	}
	if (action[0].type == action_none)
	{  //if there are no enemies to attack or teleport tiles handy, cover an enemy base
		empdebug("Check if there's an enemy base to cover...");
		check_enemy_bases(u);
	}
	if ((p->basetype == LAND) && (action[0].type == action_none))
	{  //check for a lander if the unit can't do anything and could be moved in a lander
		empdebug("Check for an available Lander...");
		check_available_lander(u);
	}
	if (action[0].type == action_none)
	{  //make sure it isn't preventing other units from being built
		empdebug("Check to make sure it's not blocking a base...");
		check_blocking_base(u);
	}
	if (p->loadtype == 0)
	{  //if it isn't a transport
		if (p->basetype == LAND)
		{
			if (action[0].type == action_none)
			{  //if it still isn't doing anything
				need_transport.push_back(u);
			}
		}
	}
	aset.action[0] = action[0];
	aset.action[1] = action[1];
	aset.action[2] = action[2];

	empdebug("Done thinking for this unit");
	empdebug("Results:");
	empdebug("action[0]:");
	empdebug(action[0].type);
	empdebug(action[0].loc.x);
	empdebug(action[0].loc.y);
	empdebug("action[1]:");
	empdebug(action[1].type);
	empdebug(action[1].loc.x);
	empdebug(action[1].loc.y);
	empdebug("action[2]:");
	empdebug(action[2].type);
	empdebug(action[2].loc.x);
	empdebug(action[2].loc.y);

	return aset;
}

void act(_unit *u, _action a)
{
	int i;
	int q;
	char dbg[200];
	DyBytes data;
	vector<int> movelist;

	switch(a.type)
	{
		case action_move:
			if ((u->tilex == a.loc.x) && (u->tiley == a.loc.y))
				break;  // units don't move if they're already at the destination
			
			if ((a.loc.x < 0) || (a.loc.y < 0) || (a.loc.x >= worldmap.l) || (a.loc.y >= worldmap.h))
			{
				sprintf(dbg, "Error: AI %s unit at (%d, %d) wanted to move to point (%d, %d)", unit_name(u->type).c_str(), u->tilex, u->tiley, a.loc.x, a.loc.y);
				empdebug(dbg);
				break;
			}
			
			sprintf(dbg, "AI %s wants to move from (%d,%d) to (%d,%d)", unit_name(u->type).c_str(), u->tilex, u->tiley, a.loc.x, a.loc.y);
			empdebug(dbg);
			set_best_path(u, a.loc.x, a.loc.y);
			
			if (!global.netgame || (global.netgame && net.server))
			{
				empdebug("  ...moving");
				play_moving_sound(u->type);
				game->unitselected = u;
				//u->do_movement();
				gamestate::getinstance()->state = UNIT_MOVING;
			}
			
			if (global.netgame)
			{
				movelist = u->get_movelist();
				
				if (net.server)
				{
					data.clear();
					data.appendByte(NET_MOVEUNIT);
					data.appendByte(u->tilex);
					data.appendByte(u->tiley);
					data.appendByte(movelist.size());
					i = 0;
					while ((unsigned)i < movelist.size())
					{
						data.appendByte(movelist[i]);
						i++;
					}
					net.engine->broadcastTCP(data);
					game->unitselected = u;
					gamestate::getinstance()->state = UNIT_MOVING;
				}
				else
				{
					empdebug("  AI is network client, sending NET_CLIENT_MOVEUNIT");
					
					data.clear();
					data.appendByte(NET_CLIENT_MOVEUNIT);
					data.appendByte(u->tilex);
					data.appendByte(u->tiley);
					data.appendByte(movelist.size());
					i = 0;
					while ((unsigned)i < movelist.size())
					{
						data.appendByte(movelist[i]);
						i++;
					}
					net.engine->sendTCP(net.thread, data);
					gamestate::getinstance()->state = NET_WAITING_FOR_RESPONSE;
				}
			}
			break;
		case action_attack:
			if (global.netgame && !net.server)
			{
				data.clear();
				data.appendByte(NET_CLIENT_ATTACK);
				data.appendByte(u->tilex);
				data.appendByte(u->tiley);
				data.appendByte(a.loc.x);
				data.appendByte(a.loc.y);
				net.engine->sendTCP(net.thread, data);
				gamestate::getinstance()->state = NET_WAITING_FOR_RESPONSE;
			}
			else
			{
				in_battle = 1;
				def = any_unit_here(a.loc.x, a.loc.y);
				atk = (u->owner * 100) + u->number;
			}
			break;
		case action_load:  // This is now taken care of by the movement code
			/*q = unit_here_thorough(u->owner, u->tilex, u->tiley, u->number);
			player[u->owner].unit[q].load_unit(u);
			worldmap.tile[u->tilex][u->tiley].set_unit(u->owner, q);
			player[u->owner].unit[u->number].exists = 0;*/
			break;
		case action_unload1:
			if (global.netgame && !net.server)
			{
				data.clear();
				data.appendByte(NET_CLIENT_UNLOAD);
				data.appendByte(u->tilex);
				data.appendByte(u->tiley);
				data.appendByte(0);
				data.appendByte(a.loc.x);
				data.appendByte(a.loc.y);
				net.engine->sendTCP(net.thread, data);
			}
			else
			{
				if (global.netgame)
				{
					data.clear();
					data.appendByte(NET_UNLOAD);
					data.appendByte(u->tilex);
					data.appendByte(u->tiley);
					data.appendByte(0);
					data.appendByte(a.loc.x);
					data.appendByte(a.loc.y);
					net.engine->broadcastTCP(data);
				}
			
				u->load[0].loaded = 0;
				i = player[u->owner].create_unit(u->load[0].type, a.loc.x, a.loc.y, u->owner);
				player[u->owner].unit[i].health = u->load[0].health;
				q = 0;
				while (q < 2)
				{
					if (u->load[0].subload[q].loaded == 1)
					{
						player[u->owner].unit[i].load[q].loaded = 1;
						player[u->owner].unit[i].load[q].type = u->load[0].subload[q].type;
						player[u->owner].unit[i].load[q].health = u->load[0].subload[q].health;
						u->load[0].subload[q].loaded = 0;
					}
					q++;
				}
			}
			break;
		case action_unload2:
			if (global.netgame && !net.server)
			{
				data.clear();
				data.appendByte(NET_CLIENT_UNLOAD);
				data.appendByte(u->tilex);
				data.appendByte(u->tiley);
				data.appendByte(0);
				data.appendByte(a.loc.x);
				data.appendByte(a.loc.y);
				net.engine->sendTCP(net.thread, data);
			}
			else
			{
				if (global.netgame)
				{
					data.clear();
					data.appendByte(NET_UNLOAD);
					data.appendByte(u->tilex);
					data.appendByte(u->tiley);
					data.appendByte(0);
					data.appendByte(a.loc.x);
					data.appendByte(a.loc.y);
					net.engine->broadcastTCP(data);
				}
				
				u->load[1].loaded = 0;
				i = player[u->owner].create_unit(u->load[1].type, a.loc.x, a.loc.y, u->owner);
				player[u->owner].unit[i].health = u->load[1].health;
				q = 0;
				while (q < 2)
				{
					if (u->load[1].subload[q].loaded == 1)
					{
						player[u->owner].unit[i].load[q].loaded = 1;
						player[u->owner].unit[i].load[q].type = u->load[1].subload[q].type;
						player[u->owner].unit[i].load[q].health = u->load[1].subload[q].health;
						u->load[1].subload[q].loaded = 0;
					}
					q++;
				}
			}
			break;
		case action_standby:
		case action_none:
			break;
		case action_merge:  // This is now taken care of by the movement code
			//merge_units(u, u->tilex, u->tiley);
			break;
		case action_teleport:
			if (global.netgame && !net.server)
			{
				data.clear();
				data.appendByte(NET_CLIENT_TELEPORT);
				data.appendByte(u->number);
				data.appendByte(a.destination.x);
				data.appendByte(a.destination.y);
				net.engine->sendTCP(net.thread, data);

				game->state = NET_WAITING_FOR_RESPONSE;
			}
			else
			{
				if (global.netgame)
				{
					data.clear();
					data.appendByte(NET_TELEPORT);
					data.appendByte(u->owner);
					data.appendByte(u->number);
					data.appendByte(a.destination.x);
					data.appendByte(a.destination.y);
					net.engine->broadcastTCP(data);
				}
				
				ai_unit_animating = true;
				ai_unit_animation.type = action_teleport;
				ai_unit_animation.loc = a.loc;
				ai_unit_animation.destination = a.destination;
			}
			break;
		default:
			break;
	}
}

void set_best_path(_unit *u, int tx, int ty)
{
	int i, q;
	vector<int> list;
	list.reserve(10);
	vector<_aipath> t = best_aipath(u->owner, unitstats[u->type].movetype, u->tilex, u->tiley, tx, ty);
	q = t.size();
	i = 0;
	while (i < q)
	{
		list.push_back(t[i].dir);
		i++;
	}
	u->set_moves(list);
}

int any_unit_here(int tx, int ty)
{
	if (worldmap.in_bounds(tx, ty))
	{
		return worldmap.tile[tx][ty].unit_here();
	}
	return -1;
}

int next_to_enemy(int plyr, int tx, int ty)
{
	int z;
	z = any_unit_here(tx + 1, ty);
	if (z != -1)
	{
		if (z / 100 != plyr)
		{
			return 1;
		}
	}
	z = any_unit_here(tx - 1, ty);
	if (z != -1)
	{
		if (z / 100 != plyr)
		{
			return 1;
		}
	}
	z = any_unit_here(tx, ty + 1);
	if (z != -1)
	{
		if (z / 100 != plyr)
		{
			return 1;
		}
	}
	z = any_unit_here(tx, ty - 1);
	if (z != -1)
	{
		if (z / 100 != plyr)
		{
			return 1;
		}
	}
	return 0;   
}

int intmax(int a, int b)
{
	if (a > b)
	{
		return a;
	}
	return b;
}

int intmin(int a, int b)
{
	if (a > b)
	{
		return b;
	}
	return a;
}

bool enemy_threat(int plyr)
{  //checks to see whether enemy units are near the HQ
	int i = 0, j;
	_unit *u;

	worldmap.backup_pathmap();
	while (i < numplayers)
	{
		if ((i != plyr) && (player[i].playing == 1))
		{
			j = 0;
			while (j < 50)
			{
				u = &player[i].unit[j];
				if (u->exists == 1)
				{
					worldmap.create_limited_pathmap(i, unitstats[u->type].movetype, u->tilex, u->tiley, u->move + 2);
					if (worldmap.tile[capital.x][capital.y].get_step() < 99)
					{
						worldmap.restore_pathmap();
						return true;
					}
				}
				j++;
			}
		}
		i++;
	}
	worldmap.restore_pathmap();

	return false;
}

bool enemy_within_radius(int plyr, int center_x, int center_y, int r)
{
	int x, y, t;
	y = center_y - r;
	while (y <= center_y + r)
	{
		x = center_x - r;
		while (x <= center_x + r)
		{
			if ((worldmap.in_bounds(x, y)) && (tile_distance(x, y, center_x, center_y) <= r))
			{
				t = any_unit_here(x, y);
				if (t != -1)
				{
					if (player[t / 100].team != player[plyr].team)
					{  //doesn't check whether the enemy unit can get to the base
						return true;  //only if it's within R tiles
					}  //yeah, I know it needs work
				}
			}
			x++;
		}
		y++;
	}
	return false;
}

void ai_teleport(_unit *u, _loc dest)
{
	static bool first = true;
	static int time;
	int t1, t2;

	if (first)
	{
		time = 0;
		first = false;
	}

	if (time == 0)
	{
		if (scroll_to_location(u->tilex, u->tiley) == 0)
		{
			time--;
		}
	}
	else if (time == 1)
	{
		t1 = u->tilex * MAP_TILE_SIZE;
		t2 = u->tiley * MAP_TILE_SIZE;
		create_particle(t1 + MAP_TILE_SIZE / 2, t2 + MAP_TILE_SIZE / 2, P_SHOCKWAVE);
		create_particle(t1, t2, P_TELEPORT);
		play_sound(S_TELEPORT);
	}
	else if (time == 9)
	{
		u->exists = 0;
	}
	else if (time == 25)
	{
		if (scroll_to_location(dest.x, dest.y) == 0)
		{
			time--;
		}
	}
	else if (time == 26)
	{
		u->set_tiles(dest.x, dest.y);
		create_particle(dest.x * MAP_TILE_SIZE + MAP_TILE_SIZE / 2, dest.y * MAP_TILE_SIZE + MAP_TILE_SIZE / 2, P_SHOCKWAVE);
		create_particle(dest.x * MAP_TILE_SIZE, dest.y * MAP_TILE_SIZE, P_TELEPORT);
		play_sound(S_TELEPORT);
	}
	else if (time == 34)
	{
		u->exists = 1;
	}
	else if (time > 50)
	{
		first = true;
		ai_unit_animating = false;
	}
	time++;
}
