/* unit-seeking.m,
 *
 * Functions for target seeking.
 */

#include <assert.h>
#include <math.h>
#include "common.h"
#include "map.h"
#include "unit-intern.h"
#include "unit-seeking.h"


double angle_towards_unit(double x1, double y1, Unit *unit)
{
    double x2, y2, deltax, deltay;

    if (!unit)
	return -M_PI_2;		/* Down. */

    [unit getX:&x2 Y:&y2];
    deltax = x2-x1;
    deltay = y1-y2;
    return atan2(deltay, deltax);
}

/*------------------------------------------------------------*/

static Unit *find_closest_unit_in_list_with_some_genius
	(double x, double y, double theta, double r, double *curr_min_dist, List *list)
{
    ListIterator *it;
    Unit *unit, *closest_unit = nil;
    double ux, uy, deltax, deltay;

    foreach (it, list) {
	int flags;
        unit = [it getItem];
	assert(unit);
	flags = [unit flags];

	/* Ignore these units. */
	if (flags & FLAG_INVINCIBLE || flags & FLAG_DYING)
	    continue;

	[unit getX:&ux Y:&uy];
        deltax = SQ(ux - x);
        deltay = SQ(uy - y);

	if (deltax + deltay >= *curr_min_dist)	/* Too far! */
	    continue;

	/* Check if inside turning radiuses.  If so, we won't hit this
	   unit even when turning at the maximum rate. */
	if (r > 0.0) {
	    double xx = r*sin(theta);	/* Sin is correct. */
	    double yy = r*cos(theta);	/* Cos is correct. */

	    deltax = SQ(ux - (x-xx));
	    deltay = SQ(uy - (y-yy));
	    if (deltax + deltay < SQ(r))
		continue;

	    deltax = SQ(ux - (x+xx));
	    deltay = SQ(uy - (y+yy));
	    if (deltax + deltay < SQ(r))
		continue;
	}

	/* New closest unit that can be hit! */
	closest_unit = unit;
	*curr_min_dist = deltax + deltay;
    }

    return closest_unit;
}

Unit *find_closest_unit_with_some_genius(double x, double y, double theta, double r, int lists)
{
#define FIND_UNIT(l)		find_closest_unit_in_list_with_some_genius(x, y, theta, r, &curr_min_dist, l)
#define NEW_CLOSEST_UNIT	(!closest_unit || (curr_min_dist < prev_min_dist))

    double curr_min_dist = SQ(screen_w) + SQ(screen_h);
    double prev_min_dist;
    Unit *unit, *closest_unit = nil;

    if (lists & ACTIVE_AIR_LIST) {
	prev_min_dist = curr_min_dist;
	unit = FIND_UNIT(active_air_list);
	if (unit) {
	    if (NEW_CLOSEST_UNIT)
		closest_unit = unit;
	}
    }

    if (lists & ACTIVE_GROUND_LIST) {
	prev_min_dist = curr_min_dist;
	unit = FIND_UNIT(active_ground_list);
	if (unit) {
	    if (NEW_CLOSEST_UNIT)
		closest_unit = unit;
	}
    }

    if (lists & ALLY_LIST) {
	prev_min_dist = curr_min_dist;
	unit = FIND_UNIT(ally_list);
	if (unit) {
	    if (NEW_CLOSEST_UNIT)
		closest_unit = unit;
	}
    }

    return closest_unit;

#undef NEW_CLOSEST_UNIT
#undef FIND_UNIT
}

Unit *find_closest_unit(double x, double y, int lists)
{
    /* No genius. */
    return find_closest_unit_with_some_genius(x, y, 0, -1, lists);
}

/*------------------------------------------------------------*/

Unit *find_closest_ally_according_to_rotation(double x, double y, double theta)
{
    double ux, uy, phi, phi_best = 2*M_PI;
    Unit *unit, *closest_unit = nil;
    ListIterator *it;

    foreach (it, ally_list) {
	unit = [it getItem];

	[unit getX:&ux Y:&uy];
	phi = atan2(y-uy, ux-x);
	phi = ABS(phi - theta);
	simplify_angle(&phi);
	phi = ABS(phi);

	/* New best unit. */
	if (phi_best > phi) {
	    phi_best = phi;
	    closest_unit = unit;
	}
    }

    return closest_unit;
}

/*------------------------------------------------------------*/

#define UNIT_NOT_IN_LIST	0
#define UNIT_OFF_SCREEN		1
#define UNIT_STILL_TARGETTED	2

static int unit_exists_in_list(List *list, Unit *unit)
{
    ListIterator *it;
    double x, y;
    
    foreach (it, list) {
	if ([it getItem] == unit) {
	    [unit getX:&x Y:&y];

	    /* If the unit is no longer on the screen, stop tracking it. */
	    if (x < 0 || x > screen_w ||
		y < offsetY || y > screen_h + offsetY)
		return UNIT_OFF_SCREEN;

	    /* Our target is still around. */
	    return UNIT_STILL_TARGETTED;
	}
    }

    return UNIT_NOT_IN_LIST;
}

BOOL unit_exists(Unit *unit, int lists)
{
    int ret;

    if (lists & ACTIVE_AIR_LIST) {
	ret = unit_exists_in_list(active_air_list, unit);
	if (ret == UNIT_STILL_TARGETTED)
	    return YES;
	if (ret == UNIT_OFF_SCREEN)
	    return YES;
    }

    if (lists & ACTIVE_GROUND_LIST) {
	ret = unit_exists_in_list(active_ground_list, unit);
	if (ret == UNIT_STILL_TARGETTED)
	    return YES;
	if (ret == UNIT_OFF_SCREEN)
	    return YES;
    }

    if (lists & ALLY_LIST) {
	ret = unit_exists_in_list(ally_list, unit);
	if (ret == UNIT_STILL_TARGETTED)
	    return YES;
	if (ret == UNIT_OFF_SCREEN)
	    return NO;
    }

    return NO;
}
