#include <stdlib.h>

#include "templates.h"
#include "m_random.h"
#include "i_system.h"
#include "doomdef.h"
#include "p_local.h"
#include "p_lnspec.h"
#include "s_sound.h"
#include "g_game.h"
#include "doomstat.h"
#include "r_state.h"
#include "c_cvars.h"
#include "p_enemy.h"
#include "a_sharedglobal.h"
#include "a_action.h"
#include "thingdef/thingdef.h"
#include "g_level.h"

#include "gi.h"
// [BB] new #includes.
#include "deathmatch.h"

bool P_LookForMonsters (AActor *actor);

static FRandom pr_look2 ("LookyLookyEx");
static FRandom pr_look3 ("IGotHookyEx");
static FRandom pr_lookforplayers ("LookForPlayersEx");
static FRandom pr_skiptarget("SkipTargetEx");


// [KS] *** Start additions by me - p_enemy.cpp ***

// LookForTIDinBlockEx
// LookForEnemiesInBlockEx
// P_NewLookTID (copied from P_LookForTID)
// P_NewLookPlayers (copied from P_LookForPlayers)
// Takes FOV and sight distances into account when acquiring a target.
// TODO: Not sure if using actor properties to pass parameters around indirectly is such a good idea. If there's a cleaner method, do it that way instead.
//  ~Kate S.  Last updated on 12/23/2007

AActor *LookForTIDinBlockEx (AActor *lookee, int index)
{
	FBlockNode *block;
	AActor *link;
	AActor *other;
	fixed_t 	dist;
	angle_t		an;
	
	for (block = blocklinks[index]; block != NULL; block = block->NextActor)
	{
		link = block->Me;

        if (!(link->flags & MF_SHOOTABLE))
			continue;			// not shootable (observer or dead)

		if (link == lookee)
			continue;

		if (link->health <= 0)
			continue;			// dead

		if (link->flags2 & MF2_DORMANT)
			continue;			// don't target dormant things

		if (link->tid == lookee->TIDtoHate)
		{
			other = link;
		}
		else if (link->target != NULL && link->target->tid == lookee->TIDtoHate)
		{
			other = link->target;
			if (!(other->flags & MF_SHOOTABLE) ||
				other->health <= 0 ||
				(other->flags2 & MF2_DORMANT))
			{
				continue;
			}
		}
		else
		{
			continue;
		}

		if (!(lookee->flags3 & MF3_NOSIGHTCHECK))
		{
			if (!P_CheckSight (lookee, other, 2))
				continue;			// out of sight

			dist = P_AproxDistance (other->x - lookee->x,
									other->y - lookee->y);

			if (lookee->LookExMaxDist && dist > lookee->LookExMaxDist)
				continue;			// [KS] too far

			if (lookee->LookExMinDist && dist < lookee->LookExMinDist)
				continue;			// [KS] too close

			if (lookee->LookExFOV && lookee->LookExFOV < ANGLE_MAX)
			{
				an = R_PointToAngle2 (lookee->x,
									  lookee->y, 
									  other->x,
									  other->y)
					- lookee->angle;

				if (an > (lookee->LookExFOV / 2) && an < (ANGLE_MAX - (lookee->LookExFOV / 2)))
				{
					// if real close, react anyway
					// [KS] but respect minimum distance rules
					if (lookee->LookExMinDist || dist > MELEERANGE)
						continue;	// outside of fov
				}
			}
			else
			{
				if(!(lookee->flags4 & MF4_LOOKALLAROUND) || (lookee->LookExFOV >= ANGLE_MAX)) // Just in case angle_t doesn't clamp stuff, because I can't be too sure.
				{
					an = R_PointToAngle2 (lookee->x,
										  lookee->y, 
										  other->x,
										  other->y)
						- lookee->angle;

					if (an > ANG90 && an < ANG270)
					{
						// if real close, react anyway
						// [KS] but respect minimum distance rules
						if (lookee->LookExMinDist || dist > MELEERANGE)
							continue;	// behind back
					}
				}
			}
		}

		return other;
	}
	return NULL;
}

AActor *LookForEnemiesInBlockEx (AActor *lookee, int index)
{
	FBlockNode *block;
	AActor *link;
	AActor *other;
	fixed_t 	dist;
	angle_t		an;
	
	for (block = blocklinks[index]; block != NULL; block = block->NextActor)
	{
		link = block->Me;

        if (!(link->flags & MF_SHOOTABLE))
			continue;			// not shootable (observer or dead)

		if (link == lookee)
			continue;

		if (link->health <= 0)
			continue;			// dead

		if (link->flags2 & MF2_DORMANT)
			continue;			// don't target dormant things

		if (!(link->flags3 & MF3_ISMONSTER))
			continue;			// don't target it if it isn't a monster (could be a barrel)

		other = NULL;
		if (link->flags & MF_FRIENDLY)
		{
			if (deathmatch &&
				lookee->FriendPlayer != 0 && link->FriendPlayer != 0 &&
				lookee->FriendPlayer != link->FriendPlayer)
			{
				// This is somebody else's friend, so go after it
				other = link;
			}
			else if (link->target != NULL && !(link->target->flags & MF_FRIENDLY))
			{
				other = link->target;
				if (!(other->flags & MF_SHOOTABLE) ||
					other->health <= 0 ||
					(other->flags2 & MF2_DORMANT))
				{
					other = NULL;;
				}
			}
		}
		else
		{
			other = link;
		}

		// [MBF] If the monster is already engaged in a one-on-one attack
		// with a healthy friend, don't attack around 60% the time.
		
		// [GrafZahl] This prevents friendlies from attacking all the same 
		// target.
		
		if (other)
		{
			AActor *targ = other->target;
			if (targ && targ->target == other && pr_skiptarget() > 100 && lookee->IsFriend (targ) &&
				targ->health*2 >= targ->GetDefault()->health)
			{
				continue;
			}
		}

		// [KS] Hey, shouldn't there be a check for MF3_NOSIGHTCHECK here?

		if (other == NULL || !P_CheckSight (lookee, other, 2))
			continue;			// out of sight

		dist = P_AproxDistance (other->x - lookee->x,
								other->y - lookee->y);

		if (lookee->LookExMaxDist && dist > lookee->LookExMaxDist)
			continue;			// [KS] too far

		if (lookee->LookExMinDist && dist < lookee->LookExMinDist)
			continue;			// [KS] too close

		if (lookee->LookExFOV && lookee->LookExFOV < ANGLE_MAX)
		{
			an = R_PointToAngle2 (lookee->x,
								  lookee->y, 
								  other->x,
								  other->y)
				- lookee->angle;

			if (an > (lookee->LookExFOV / 2) && an < (ANGLE_MAX - (lookee->LookExFOV / 2)))
			{
				// if real close, react anyway
				// [KS] but respect minimum distance rules
				if (lookee->LookExMinDist || dist > MELEERANGE)
					continue;	// outside of fov
			}
		}
		else
		{
			if(!(lookee->flags4 & MF4_LOOKALLAROUND) || (lookee->LookExFOV >= ANGLE_MAX)) // Just in case angle_t doesn't clamp stuff, because I can't be too sure.
			{
				an = R_PointToAngle2 (lookee->x,
									  lookee->y, 
									  other->x,
									  other->y)
					- lookee->angle;

				if (an > ANG90 && an < ANG270)
				{
					// if real close, react anyway
					// [KS] but respect minimum distance rules
					if (lookee->LookExMinDist || dist > MELEERANGE)
						continue;	// behind back
				}
			}
		}

		return other;
	}
	return NULL;
}

bool P_NewLookTID (AActor *actor, angle_t fov, fixed_t mindist, fixed_t maxdist, bool chasegoal)
{
	AActor *other;

	actor->LookExMinDist = mindist;
	actor->LookExMaxDist = maxdist;
	actor->LookExFOV = fov;
	other = P_BlockmapSearch (actor, 0, LookForTIDinBlockEx);

	bool reachedend = false;
	fixed_t 	dist;
	angle_t		an;

	if (other != NULL)
	{
		if (!(actor->flags3 & MF3_NOSIGHTCHECK))
		{
			dist = P_AproxDistance (other->x - actor->x,
									other->y - actor->y);

			if (maxdist && dist > maxdist)
			{
				other = NULL;			// [KS] too far
				goto endcheck;
			}

			if (mindist && dist < mindist)
			{
				other = NULL;			// [KS] too close
				goto endcheck;
			}

			if (fov && fov < ANGLE_MAX)
			{
				an = R_PointToAngle2 (actor->x,
									  actor->y, 
									  other->x,
									  other->y)
					- actor->angle;

				if (an > (fov / 2) && an < (ANGLE_MAX - (fov / 2)))
				{
					// if real close, react anyway
					// [KS] but respect minimum distance rules
					if (mindist || dist > MELEERANGE)
					{
						other = NULL;	// outside of fov
						goto endcheck;
					}
				}
			}
			else
			{
				if(!((actor->flags4 & MF4_LOOKALLAROUND) || (fov >= ANGLE_MAX))) // Just in case angle_t doesn't clamp stuff, because I can't be too sure.
				{
					an = R_PointToAngle2 (actor->x,
										  actor->y, 
										  other->x,
										  other->y)
						- actor->angle;

					if (an > ANG90 && an < ANG270)
					{
						// if real close, react anyway
						// [KS] but respect minimum distance rules
						if (mindist || dist > MELEERANGE)
						{
							other = NULL;	// behind back
							goto endcheck;
						}
					}
				}
			}
		}
	}

  endcheck:

	if (other != NULL)
	{
		if (actor->goal && actor->target == actor->goal)
			actor->reactiontime = 0;

		actor->target = other;
		actor->LastLookActor = other;
		return true;
	}

	// The actor's TID could change because of death or because of
	// Thing_ChangeTID. If it's not what we expect, then don't use
	// it as a base for the iterator.
	if (actor->LastLookActor != NULL &&
		actor->LastLookActor->tid != actor->TIDtoHate)
	{
		actor->LastLookActor = NULL;
	}

	FActorIterator iterator (actor->TIDtoHate, actor->LastLookActor);
	int c = (pr_look3() & 31) + 7;	// Look for between 7 and 38 hatees at a time
	while ((other = iterator.Next()) != actor->LastLookActor)
	{
		if (other == NULL)
		{
			if (reachedend)
			{
				// we have cycled through the entire list at least once
				// so let's abort because even if we continue nothing can
				// be found.
				break;
			}
			reachedend = true;
			continue;
		}

		if (!(other->flags & MF_SHOOTABLE))
			continue;			// not shootable (observer or dead)

		if (other == actor)
			continue;			// don't hate self

		if (other->health <= 0)
			continue;			// dead

		if (other->flags2 & MF2_DORMANT)
			continue;			// don't target dormant things

		if (--c == 0)
			break;

		if (!(actor->flags3 & MF3_NOSIGHTCHECK))
		{
			if (!P_CheckSight (actor, other, 2))
				continue;			// out of sight

			dist = P_AproxDistance (other->x - actor->x,
									other->y - actor->y);

			if (maxdist && dist > maxdist)
				continue;			// [KS] too far

			if (mindist && dist < mindist)
				continue;			// [KS] too close

			if (fov && fov < ANGLE_MAX)
			{
				an = R_PointToAngle2 (actor->x,
									  actor->y, 
									  other->x,
									  other->y)
					- actor->angle;

				if (an > (fov / 2) && an < (ANGLE_MAX - (fov / 2)))
				{
					// if real close, react anyway
					// [KS] but respect minimum distance rules
					if (mindist || dist > MELEERANGE)
						continue;	// outside of fov
				}
			}
			else
			{
				if(!((actor->flags4 & MF4_LOOKALLAROUND) || (fov >= ANGLE_MAX))) // Just in case angle_t doesn't clamp stuff, because I can't be too sure.
				{
					an = R_PointToAngle2 (actor->x,
										  actor->y, 
										  other->x,
										  other->y)
						- actor->angle;

					if (an > ANG90 && an < ANG270)
					{
						// if real close, react anyway
						// [KS] but respect minimum distance rules
						if (mindist || dist > MELEERANGE)
							continue;	// behind back
					}
				}
			}
		}
		
		// [RH] Need to be sure the reactiontime is 0 if the monster is
		//		leaving its goal to go after something else.
		if (actor->goal && actor->target == actor->goal)
			actor->reactiontime = 0;

		actor->target = other;
		actor->LastLookActor = other;
		return true;
	}
	actor->LastLookActor = other;
	if (actor->target == NULL)
	{
		// [RH] use goal as target
		if (actor->goal != NULL && chasegoal)
		{
			actor->target = actor->goal;
			return true;
		}
		// Use last known enemy if no hatee sighted -- killough 2/15/98:
		if (actor->lastenemy != NULL && actor->lastenemy->health > 0)
		{
			if (!actor->IsFriend(actor->lastenemy))
			{
				actor->target = actor->lastenemy;
				actor->lastenemy = NULL;
				return true;
			}
			else
			{
				actor->lastenemy = NULL;
			}
		}
	}
	return false;
}

bool P_NewLookEnemies (AActor *actor, angle_t fov, fixed_t mindist, fixed_t maxdist, bool chasegoal)
{
	AActor *other;

	actor->LookExMinDist = mindist;
	actor->LookExMaxDist = maxdist;
	actor->LookExFOV = fov;
	other = P_BlockmapSearch (actor, 10, LookForEnemiesInBlockEx);

	if (other != NULL)
	{
		if (actor->goal && actor->target == actor->goal)
			actor->reactiontime = 0;

		actor->target = other;
//		actor->LastLook.Actor = other;
		return true;
	}

	if (actor->target == NULL)
	{
		// [RH] use goal as target
		if (actor->goal != NULL)
		{
			actor->target = actor->goal;
			return true;
		}
		// Use last known enemy if no hatee sighted -- killough 2/15/98:
		if (actor->lastenemy != NULL && actor->lastenemy->health > 0)
		{
			if (!actor->IsFriend(actor->lastenemy))
			{
				actor->target = actor->lastenemy;
				actor->lastenemy = NULL;
				return true;
			}
			else
			{
				actor->lastenemy = NULL;
			}
		}
	}
	return false;
}

bool P_NewLookPlayers (AActor *actor, angle_t fov, fixed_t mindist, fixed_t maxdist, bool chasegoal)
{
	int 		c;
	int 		stop;
	int			pnum;
	player_t*	player;
	angle_t 	an;
	fixed_t 	dist;

	if (actor->TIDtoHate != 0)
	{
		if (P_NewLookTID (actor, fov, mindist, maxdist, chasegoal))
		{
			return true;
		}
		if (!(actor->flags3 & MF3_HUNTPLAYERS))
		{
			return false;
		}
	}
	else if (actor->flags & MF_FRIENDLY)
	{
		if (!deathmatch) // [SP] If you don't see any enemies in deathmatch, look for players.
			return P_NewLookEnemies (actor, fov, mindist, maxdist, chasegoal);
		else if ( P_NewLookEnemies (actor, fov, mindist, maxdist, chasegoal) )
			return true;
	}	// [SP] if false, and in deathmatch, intentional fall-through

	if (!(gameinfo.gametype & (GAME_DoomStrifeChex)) &&
		(NETWORK_GetState( ) == NETSTATE_SINGLE) &&
		players[0].health <= 0)
	{ // Single player game and player is dead; look for monsters
		return P_LookForMonsters (actor);
	}

	c = 0;
	if (actor->TIDtoHate != 0)
	{
		pnum = pr_look2() & (MAXPLAYERS-1);
	}
	else
	{
		pnum = actor->LastLookPlayerNumber;
	}
	stop = (pnum - 1) & (MAXPLAYERS-1);
		
	// [BB] On the server it's possible that there are no players. In this case the
	// for loop below would get stuck, so we may not enter it. Since there are no
	// players in the game in this case, we can safely return false.
	if ( ( NETWORK_GetState( ) == NETSTATE_SERVER ) && ( SERVER_CalcNumPlayers() == 0 ) )
		return false;

	for (;;)
	{
		pnum = (pnum + 1) & (MAXPLAYERS-1);
		if (!playeringame[pnum])
			continue;

		if (actor->TIDtoHate == 0)
		{
			actor->LastLookPlayerNumber = pnum;
		}

		if (++c == MAXPLAYERS-1 || pnum == stop)
		{
			// done looking
			if (actor->target == NULL)
			{
				// [RH] use goal as target
				// [KS] ...unless we're ignoring goals and we don't already have one
				if (actor->goal != NULL && chasegoal)
				{
					actor->target = actor->goal;
					return true;
				}
				// Use last known enemy if no players sighted -- killough 2/15/98:
				if (actor->lastenemy != NULL && actor->lastenemy->health > 0)
				{
					if (!actor->IsFriend(actor->lastenemy))
					{
						actor->target = actor->lastenemy;
						actor->lastenemy = NULL;
						return true;
					}
					else
					{
						actor->lastenemy = NULL;
					}
				}
			}
			return actor->target == actor->goal && actor->goal != NULL;
		}

		player = &players[pnum];

		if (!(player->mo->flags & MF_SHOOTABLE))
			continue;			// not shootable (observer or dead)

		if (player->cheats & CF_NOTARGET)
			continue;			// no target

		if (player->health <= 0)
			continue;			// dead

		if (!P_CheckSight (actor, player->mo, 2))
			continue;			// out of sight

		dist = P_AproxDistance (player->mo->x - actor->x,
								player->mo->y - actor->y);

		if (maxdist && dist > maxdist)
			continue;			// [KS] too far

		if (mindist && dist < mindist)
			continue;			// [KS] too close

		// [SP] Deathmatch fixes - if we have MF_FRIENDLY we're definitely in deathmatch
		// We're going to ignore our master, but go after his enemies.
		if ( actor->flags & MF_FRIENDLY )
		{
			if ( actor->FriendPlayer == 0 )
				continue; // I have no friends, I will ignore players.
			if ( actor->FriendPlayer == player->mo->FriendPlayer )
				continue; // This is my master.
		}

		if (fov)
		{
			an = R_PointToAngle2 (actor->x,
								  actor->y, 
								  player->mo->x,
								  player->mo->y)
				- actor->angle;

			if (an > (fov / 2) && an < (ANGLE_MAX - (fov / 2)))
			{
				// if real close, react anyway
				// [KS] but respect minimum distance rules
				if (mindist || dist > MELEERANGE)
					continue;	// outside of fov
			}
		}
		else
		{
			if(!((actor->flags4 & MF4_LOOKALLAROUND) || (fov >= ANGLE_MAX)))
			{
				an = R_PointToAngle2 (actor->x,
									  actor->y, 
									  player->mo->x,
									  player->mo->y)
					- actor->angle;

				if (an > ANG90 && an < ANG270)
				{
					// if real close, react anyway
					// [KS] but respect minimum distance rules
					if (mindist || dist > MELEERANGE)
						continue;	// behind back
				}
			}
		}
		if ((player->mo->flags & MF_SHADOW && !(i_compatflags & COMPATF_INVISIBILITY)) ||
			player->mo->flags3 & MF3_GHOST)
		{
			if ((P_AproxDistance (player->mo->x - actor->x,
					player->mo->y - actor->y) > 2*MELEERANGE)
				&& P_AproxDistance (player->mo->momx, player->mo->momy)
				< 5*FRACUNIT)
			{ // Player is sneaking - can't detect
				return false;
			}
			if (pr_lookforplayers() < 225)
			{ // Player isn't sneaking, but still didn't detect
				return false;
			}
		}
		
		// [RH] Need to be sure the reactiontime is 0 if the monster is
		//		leaving its goal to go after a player.
		if (actor->goal && actor->target == actor->goal)
			actor->reactiontime = 0;

		actor->target = player->mo;
		return true;
	}
}

//
// ACTION ROUTINES
//

//
// A_Look
// Stay in state until a player is sighted.
// [RH] Will also leave state to move to goal.
//
// A_LookEx (int flags, fixed minseedist, fixed maxseedist, fixed maxheardist, fixed fov, state wakestate)
// [KS] Borrowed the A_Look code to make a parameterized version.
//

enum LO_Flags
{
	LOF_NOSIGHTCHECK = 1,
	LOF_NOSOUNDCHECK = 2,
	LOF_DONTCHASEGOAL = 4,
	LOF_NOSEESOUND = 8,
	LOF_FULLVOLSEESOUND = 16,
};

DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LookEx)
{
	ACTION_PARAM_START(6);
	ACTION_PARAM_INT(flags, 0);
	ACTION_PARAM_FIXED(minseedist, 1);
	ACTION_PARAM_FIXED(maxseedist, 2);
	ACTION_PARAM_FIXED(maxheardist, 3);
	ACTION_PARAM_ANGLE(fov, 4);
	ACTION_PARAM_STATE(seestate, 5);

	AActor *targ = NULL; // Shuts up gcc
	fixed_t dist;

	if (self->flags5 & MF5_INCONVERSATION)
		return;

	// [RH] Set goal now if appropriate
	if (self->special == Thing_SetGoal && self->args[0] == 0) 
	{
		NActorIterator iterator (NAME_PatrolPoint, self->args[1]);
		self->special = 0;
		self->goal = iterator.Next ();
		self->reactiontime = self->args[2] * TICRATE + level.maptime;
		if (self->args[3] == 0) self->flags5 &=~ MF5_CHASEGOAL;
		else self->flags5 |= MF5_CHASEGOAL;
	}

	self->threshold = 0;		// any shot will wake up

	if (self->TIDtoHate != 0)
	{
		targ = self->target;
	}
	else
	{
		if (!(flags & LOF_NOSOUNDCHECK))
		{
			targ = self->LastHeard;
			if (targ != NULL)
			{
				// [RH] If the soundtarget is dead, don't chase it
				if (targ->health <= 0)
				{
					targ = NULL;
				}
				else
				{
					dist = P_AproxDistance (targ->x - self->x,
											targ->y - self->y);

					// [KS] If the target is too far away, don't respond to the sound.
					if (maxheardist && dist > maxheardist)
					{
						targ = NULL;
						self->LastHeard = NULL;
					}
				}
			}

			if (targ && targ->player && (targ->player->cheats & CF_NOTARGET))
			{
				return;
			}
		}
	}

	// [RH] Andy Baker's stealth monsters
	if (self->flags & MF_STEALTH)
	{
		self->visdir = -1;
	}

	if (targ && (targ->flags & MF_SHOOTABLE))
	{
		if (self->IsFriend (targ))	// be a little more precise!
		{
			if (!(self->flags4 & MF4_STANDSTILL))
			{
				if (!(flags & LOF_NOSIGHTCHECK))
				{
					// If we find a valid target here, the wandering logic should *not*
					// be activated! If would cause the seestate to be set twice.
					if (P_NewLookPlayers(self, fov, minseedist, maxseedist, !(flags & LOF_DONTCHASEGOAL)))
						goto seeyou;
				}

				// Let the self wander around aimlessly looking for a fight
				if (self->SeeState != NULL)
				{
					if (!(self->flags & MF_INCHASE))
					{
						if (seestate)
						{
							self->SetState (seestate);
						}
						else
						{
							self->SetState (self->SeeState);
						}
					}
				}
				else
				{
					CALL_ACTION(A_Wander, self);
				}
			}
		}
		else
		{
			self->target = targ; //We already have a target?

			if (targ != NULL)
			{
				if (self->flags & MF_AMBUSH)
				{
					dist = P_AproxDistance (targ->x - self->x,
											targ->y - self->y);
					if (P_CheckSight (self, self->target, 2) &&
						(!minseedist || dist > minseedist) &&
						(!maxseedist || dist < maxseedist))
					{
						goto seeyou;
					}
				}
				else
					goto seeyou;
			}
		}
	}

	if (!(flags & LOF_NOSIGHTCHECK))
	{
		if (!P_NewLookPlayers(self, fov, minseedist, maxseedist, !(flags & LOF_DONTCHASEGOAL)))
			return;
	}
	else
	{
		return;
	}
				
	// go into chase state
  seeyou:
	// [RH] Don't start chasing after a goal if it isn't time yet.
	if (self->target == self->goal)
	{
		if (self->reactiontime > level.maptime)
			self->target = NULL;
	}
	else if (self->SeeSound && !(flags & LOF_NOSEESOUND))
	{
		if (flags & LOF_FULLVOLSEESOUND)
		{ // full volume
			S_Sound (self, CHAN_VOICE, self->SeeSound, 1, ATTN_NONE);
		}
		else
		{
			S_Sound (self, CHAN_VOICE, self->SeeSound, 1, ATTN_NORM);
		}
	}

	if (self->target && !(self->flags & MF_INCHASE))
	{
		if (seestate)
		{
			self->SetState (seestate);
		}
		else
		{
			self->SetState (self->SeeState);
		}
	}
}

// [KS] *** End additions by me ***
