/*
   ================================================================
   CEILING STEERING FOR CRAWLER
   ================================================================

   Copyright (C) 1998 by 2015, Inc.
   All rights reserved.

   This source is may not be distributed and/or modified without
   expressly written permission by 2015, Inc.
 */

#include "g_local.h"
#include "ceilingsteering.h"
#include "actor.h"

/****************************************************************************

   CeilingObstacleAvoidance Class Definition

****************************************************************************/

CLASS_DECLARATION( Steering, CeilingObstacleAvoidance, NULL );

ResponseDef CeilingObstacleAvoidance::Responses[] =
{
	{ NULL, NULL }
};

CeilingObstacleAvoidance::CeilingObstacleAvoidance()
{
	avoidwalls = true;
}

void CeilingObstacleAvoidance::AvoidWalls( qboolean avoid )
{
	avoidwalls = avoid;
}

void CeilingObstacleAvoidance::ShowInfo( Actor &self )
{
	Steering::ShowInfo( self );

	gi.printf( "\navoidwalls: %d\n", avoidwalls );
}

qboolean CeilingObstacleAvoidance::Evaluate( Actor &self )
{
	Vector predictedposition;
	Vector normal;
	Vector angles;
	Vector dir;
	Vector right;
	Vector delta;
	float urgency;
	float dot;
	trace_t tracef;
	Entity  *ent;

	ResetForces();

	angles = self.movedir.toAngles();
	angles.AngleVectors( NULL, &right, NULL );

	origin = self.worldorigin;
	origin.z -= 1;
	predictedposition = origin + self.movedir * self.movespeed;//maxspeed;

	tracef = G_Trace( origin, self.mins, self.maxs, predictedposition, &self, self.edict->clipmask, "CeilingObstacleAvoidance forward" );
	if ( tracef.fraction < 1 )
	{
		urgency = 1.0 - tracef.fraction;
		normal = tracef.plane.normal;
		ent = tracef.ent->entity;
		if ( ent->getSolidType() != SOLID_BSP )
		{
			dot = -( right * ( ent->worldorigin - self.worldorigin ) );
		}
		else
		{
			if ( !avoidwalls )
			{
				return true;
			}

			dot = right * normal;
		}

		if ( dot < 0 )
		{
			// turn left
			steeringforce = Vector( 0, 90, 0 );;
		}
		else
		{
			// turn right
			steeringforce = Vector( 0, -90, 0 );;
		}
	}
	else
	{
		return true;
	}

	steeringforce *= urgency;

	return true;
}

/****************************************************************************

   CeilingObstacleAvoidance2 Class Definition

****************************************************************************/

CLASS_DECLARATION( Steering, CeilingObstacleAvoidance2, NULL );

ResponseDef CeilingObstacleAvoidance2::Responses[] =
{
	{ NULL, NULL }
};

CeilingObstacleAvoidance2::CeilingObstacleAvoidance2()
{
	avoidwalls = true;
}

void CeilingObstacleAvoidance2::AvoidWalls( qboolean avoid )
{
	avoidwalls = avoid;
}

void CeilingObstacleAvoidance2::ShowInfo( Actor &self )
{
	Steering::ShowInfo( self );

	gi.printf( "\navoidwalls: %d\n", avoidwalls );
}

qboolean CeilingObstacleAvoidance2::Evaluate( Actor &self )
{
	Vector predictedposition;
	Vector normal;
	Vector angles;
	Vector dir;
	Vector right;
	Vector delta;
	float urgency;
	float dot;
	trace_t tracef;
	Entity  *ent;

	ResetForces();

	angles = self.movedir.toAngles();
	angles.AngleVectors( NULL, &right, NULL );

	origin = self.worldorigin;
	origin.z -= 1;
	predictedposition = origin + self.movedir * self.movespeed;//maxspeed;

	tracef = G_Trace( origin, self.mins, self.maxs, predictedposition, &self, self.edict->clipmask, "ObstacleAvoidance2 forward" );
	if ( tracef.fraction < 1 )
	{
		urgency = 1.0 - tracef.fraction;
		normal = tracef.plane.normal;
		ent = tracef.ent->entity;
		if ( ent->getSolidType() != SOLID_BSP )
		{
			dot = -( right * ( ent->worldorigin - self.worldorigin ) );
		}
		else
		{
			if ( !avoidwalls )
			{
				return true;
			}

			dot = right * normal;
		}

		if ( dot < 0 )
		{
			// turn left
			steeringforce = Vector( 0, 22, 0 ) * urgency;
		}
		else
		{
			// turn right
			steeringforce = Vector( 0, 22, 0 ) * urgency;
		}
	}
	else
	{
		return true;
	}

	steeringforce *= urgency;

	return true;
}

/****************************************************************************

   CeilingChase Class Definition

****************************************************************************/

CLASS_DECLARATION( Steering, CeilingChase, NULL );

ResponseDef CeilingChase::Responses[] =
{
	{ NULL, NULL }
};

CeilingChase::CeilingChase()
{
	goalent = NULL;
	goal = vec_zero;
	usegoal = false;
}

void CeilingChase::SetGoalPos( Vector goalpos )
{
	goal = goalpos;
	usegoal = true;
	goalent = NULL;
}

void CeilingChase::SetTarget( Entity *ent )
{
	goalent = ent;
	usegoal = false;
}

void CeilingChase::ShowInfo( Actor &self )
{
	Steering::ShowInfo( self );

	gi.printf( "\nseek:\n" );
	seek.ShowInfo( self );

	gi.printf( "goal: ( %f, %f, %f )\n", goal.x, goal.y, goal.z );

	if ( goalent )
	{
		gi.printf( "\ngoalent: #%d '%s'\n", goalent->entnum, goalent->targetname.c_str() );
	}
	else
	{
		gi.printf( "\ngoalent: NULL\n" );
	}

	gi.printf( "avoid:\n" );
	avoid.ShowInfo( self );

	gi.printf( "\ntime: %f\n", avoidtime );

	gi.printf( "usegoal: %d\n", usegoal );
	gi.printf( "wander: %d\n", wander );
	gi.printf( "stuck: %d\n", stuck );
	gi.printf( "avoidvec : ( %f, %f, %f )\n", avoidvec.x, avoidvec.y, avoidvec.z );
}

void CeilingChase::Begin( Actor &self )
{
	seek.Begin( self );
	avoid.AvoidWalls( false );
	avoid.Begin( self );
	turnto.Begin( self );
	anim = self.animname;
	stuck = 0;
	wander = 0;
}

Vector CeilingChase::ChooseRandomDirection( Actor &self )
{
	Vector dir;
	Vector ang;
	Vector bestdir;
	float bestfraction;
	trace_t trace;
	trace_t groundtrace;
	int i;
	int j;
	int t;
	int u;
	Vector s;
	Vector start;
	Vector end;
	Vector groundend;

	s = Vector( 0, 0, -STEPSIZE );
	start = self.worldorigin + s;

	// quantize to nearest 45 degree
	u = ( ( int )( self.worldangles.y * ( 1 / 45 ) + 22.5 ) ) * 45;
	bestfraction = -1;
	//
	// in case we don't find anything!
	//
	bestdir = self.worldorigin - ( Vector( self.orientation[ 0 ] ) * 100 );

	for( i = 0; i <= 180; i += 20 )
	{
		if ( rand() < 0.3 )
		{
			i += 20;
		}
		t = i;
		if ( rand() < 0.5 )
		{
			// sometimes we choose left first, other times right.
			t = -t;
		}
		for( j = -1; j < 2; j += 2 )
		{
			if ( ( j == 1 ) && ( i == 180 ) )
			{
				ang.y = self.worldangles.y + ( t * j );
			}
			else
			{
				ang.y = u + t * j;
			}

			ang.AngleVectors( &dir, NULL, NULL );

			end = self.worldorigin + dir * 140 + s;
			trace = G_Trace( start, self.mins, self.maxs, end, &self,
			                 self.edict->clipmask, "CeilingChase::ChooseRandomDirection 1" );
			if ( ( trace.fraction > bestfraction ) && ( !trace.startsolid ) && !( trace.allsolid ) )
			{
				if ( trace.endpos != avoidvec )
				{
					// check if we're near the ground
					end = self.worldorigin + dir * 32 + s;
					groundend = end;
					groundend.z += STEPSIZE * 2;
					groundtrace = G_Trace( end, self.mins, self.maxs, groundend, &self,
					                       self.edict->clipmask, "CeilingChase::ChooseRandomDirection 2" );
					if ( groundtrace.fraction != 1 )
					{
						bestdir = trace.endpos;
						bestfraction = trace.fraction;
					}
				}
			}

			if ( i == 0 )
			{
				break;
			}
		}
	}

	return bestdir;
}

qboolean CeilingChase::Evaluate( Actor &self )
{
	qboolean result;
	trace_t trace;

	if ( !usegoal && ( !goalent || goalent->deadflag ) )
	{
		return false;
	}

	ResetForces();

	if ( !wander )
	{
		if ( self.lastmove == STEPMOVE_OK )
		{
			stuck = 0;
		}
		else
		{
			stuck++;
			if ( stuck >= 2 )
			{
				stuck = 3;
				wander = 1;
			}
		}
	}

	switch( wander )
	{
	case 1:
		stuck--;
		if ( !stuck )
		{
			wander = 0;
			break;
		}
		wanderstart = self.worldorigin;
		avoidvec = ChooseRandomDirection( self );
		wandertime = level.time + 1;
		wander = 2;

	case 2:
		seek.SetTargetPosition( avoidvec );
		seek.SetTargetVelocity( vec_zero );
		seek.SetPosition( self.worldorigin );
		seek.SetDir( self.movedir );
		seek.SetMaxSpeed( self.movespeed );
		result = seek.Evaluate( self );
		if ( result )
		{
			if ( ( level.time > wandertime ) && ( self.lastmove != STEPMOVE_OK ) )
			{
				wander = 0;
				stuck = 0;
			}
			self.Accelerate( seek.steeringforce );
			return true;
		}
		wander = 0;
		break;

		turnto.SetDirection( ( wanderstart - self.worldorigin ).toYaw() );
		wander = 3;
		wandertime = level.time + 1;

	case 3:
		if ( level.time < wandertime )
		{
			turnto.Evaluate( self );
			self.Accelerate( turnto.steeringforce );
			return true;
		}

		wander = 0;
		break;
	}

	if ( goalent && ( goalent->edict->solid != SOLID_NOT ) && ( goalent->edict->solid != SOLID_TRIGGER ) )
	{
		trace = G_Trace( self.worldorigin, self.mins, self.maxs, self.worldorigin +
		                 Vector( self.orientation[ 0 ] ) * self.movespeed * 0.1, &self, self.edict->clipmask, "CeilingChase" );
		if ( trace.ent->entity == goalent )
		{
			return false;
		}
	}

	if ( goalent )
	{
		seek.SetTargetPosition( goalent->worldorigin );
		seek.SetTargetVelocity( goalent->velocity );
	}
	else
	{
		seek.SetTargetPosition( goal );
		seek.SetTargetVelocity( vec_zero );
	}

	seek.SetPosition( self.worldorigin );
	seek.SetDir( self.movedir );
	seek.SetMaxSpeed( self.movespeed );
	result = seek.Evaluate( self );

	steeringforce = seek.steeringforce;

	if ( !result )
	{
		return false;
	}

	if ( avoidtime < level.time )
	{
		avoid.SetMaxSpeed( self.movespeed );
		avoid.SetPosition( self.worldorigin );
		avoid.SetDir( self.movedir );
		avoid.Evaluate( self );

		if ( avoid.steeringforce == vec_zero )
		{
			avoidtime = level.time + 0.1;
		}
		else
		{
			steeringforce += avoid.steeringforce;
		}
	}

	self.Accelerate( steeringforce );

	return true;
}

void CeilingChase::End( Actor &self )
{
	seek.End( self );
	avoid.End( self );
	turnto.End( self );
}
