/* toothpaste.m,
 *
 * Toothpaste projectiles go straight if no enemy is locked, or
 * follows a spline to the locked enemy if one is locked.  A unit is
 * locked if another toothpaste hits something.
 */

#include <allegro.h>
#include <assert.h>
#include <math.h>
#include "common.h"
#include "debris/spark.h"
#include "linklist.h"
#include "map.h"
#include "player.h"
#include "projectiles/toothpaste.h"
#include "seborrhea/seborrhea.h"
#include "sound.h"
#include "unit.h"
#include "unit-seeking.h"


#define TOOTHPASTE_LENGTH	30
#define TOOTHPASTE_STEPS	25


static Sebum<SebSample> *fire_sample;
static Unit *target[MAX_PLAYERS];


@implementation Toothpaste
- (BOOL) findClosestUnitWithinJumpRange
{
    Unit *new_target = find_closest_unit(x, y, ACTIVE_ENEMY_LISTS);
    double x_, y_;

    if (!new_target) {
	target[parent_id] = nil;
	return NO;
    }

    [new_target getX:&x_ Y:&y_];
    if (SQ(x-x_) + SQ(y-y_) > SQ(jump_range))
	return NO;

    angle = angle_towards_unit(x, y, new_target);
    path = -1;
    return YES;
}

+ (id) initialize
{
    fire_sample = (Sebum<SebSample> *)[base_sebum getSebumByName:"weapon/paste-snd"];
    return self;
}

- init
{
    [super init];

    health = 3;
    angle = M_PI_2;
    speed = 10.0;
    w = 3;
    h = 3;

    length = TOOTHPASTE_LENGTH;
    jump_range = 75;

    min = deg2rad(80.0);
    max = deg2rad(100.0);

    path = -1;		/* negative path means don't follow a path. */
    the_path = NULL;

    return self;
}

- (void) freePathSpline { FREE_SPLINE(the_path); }

- (id) free
{
    [self freePathSpline];
    return [super free];
}

- (id) setMinAngle:(double)theta_min maxAngle:(double)theta_max
{
    min = theta_min;
    max = theta_max;
    return self;
}

- (void) die
{
    spawn_debris([Spark class], x, y, MEDIUM_LAYER);
    health = 0;

    /* If not target locked, find closest target within jump_range. */
    if (not target[parent_id] &&
	[self findClosestUnitWithinJumpRange]) {
	path = -1;
	path_max = 10;
    }
}

static void thick_line(BITMAP *dest, int x1, int y1, int x2, int y2, int c)
{
    int i;

    for (i = -1; i <= 1; i++)
	line(dest, x1+i, y1, x2+i, y2, c);
}

- (void) draw:(BITMAP *)dest
{
    drawing_mode(DRAW_MODE_TRANS, NULL, 0, 0);

    if (path < 0) {// || (path >= path_max-1)) {
	int c = makecol(0xff, 0xa0, 0xff); 
	set_add_blender(0x40, 0x40, 0x40, 0x40);

        thick_line(dest, x, y - offsetY,
		   x + length*cos(angle),
		   y - length*sin(angle) - offsetY, c);
    }
    else {
	int i, c = makecol(0x80, 0x40, 0x80); 
	set_add_blender(0x10, 0x10, 0x10, 0x20);

#if 0
        for (i = MAX(2, path-6); i < path-2; i++) {}
	for (i = MAX(2, path-4); (i < path-2) && (path < path_max-2); i++) {
	    int origin_y = the_path[i]->y-offsetY;

	    thick_line(dest, the_path[i-2]->x, the_path[i-2]->y-offsetY, the_path[i]->x, origin_y, c);
	    thick_line(dest, the_path[i-1]->x, the_path[i-1]->y-offsetY, the_path[i]->x, origin_y, c);
	    thick_line(dest, the_path[i+1]->x, the_path[i+1]->y-offsetY, the_path[i]->x, origin_y, c);
	    thick_line(dest, the_path[i+2]->x, the_path[i+2]->y-offsetY, the_path[i]->x, origin_y, c);
	}
#endif

	for (i = MAX(0, path-2); (i <= path+3) && (i < path_max); i++) {
	    thick_line(dest,
		       the_path[i].x,    the_path[i].y-offsetY, 
		       the_path[path].x, the_path[path].y-offsetY, c);
	}
    }

    drawing_mode(DRAW_MODE_SOLID, NULL, 0, 0);
}

- (void) calculateLockSpline:(int)steps
{
    double dest_x, dest_y, deltax, deltay, l, theta;
    double cp1x, cp1y, cp2x, cp2y;

    [target[parent_id] getX:&dest_x Y:&dest_y];

#if 0
    /* Non-stupid splines */
    deltax = dest_x - x;
    deltay = y - dest_y;
    l = sqrt(SQ(deltax) + SQ(deltay)) * 0.75;

    theta = MID(min, atan2(deltay, deltax), max);
    cp1x = +l * cos(theta);
    cp1y = -l * sin(theta);
    cp2x = (dest_x < x) ? 10 : -10;
    cp2y = 10;
#else
    /* Stupid splines! */
    deltax = dest_x - x;
    deltay = y - dest_y;
    l = sqrt(SQ(deltax) + SQ(deltay)) * 0.90;

    theta = MID(min, atan2(deltay, deltax), max);
    cp1x = +l*cos(theta);
    cp1y = -l*sin(theta);
    cp2x = (dest_x < x) ? -30 : 30;
    cp2y = -15;
#endif

    {
        List *list = [LIST_NEW];
        ControlPoint *cp1 = [ControlPoint new];
        ControlPoint *cp2 = [ControlPoint new];

        [[cp1 setX:x Y:y steps:steps] setControlPoint:1 X:cp1x Y:cp1y];
        [[cp2 setX:dest_x Y:dest_y]   setControlPoint:0 X:cp2x Y:cp2y];

        [list insertItem:cp2];
        [list insertItem:cp1];

	/* Don't need to allocate memory if the_path already exists,
	   so just use reticulate_spline.  Note that reticulate_spline
	   does not free any memory. */
	if (the_path)
	    the_path = reticulate_spline(the_path, list);
	else
	    the_path = articulate_spline(list);

        [list free];
    }

    path = 0;
    path_max = steps;
}

- (int) update
{
    if (health < 0 ||
	(health == 0 && path >= 0))
	return THING_DEAD;
    else if (path < 0)
        return [super update];

    /* Reached the end of the spline. */
    else if (path >= path_max-1) {
	/* Bastard keeps evading!  Just ignore it.  This happens lots
	   for path followers :/ */
	if (path_max <= 5)
	    target[parent_id] = nil;

	/* Target died.  Find new one. */
	if (!target[parent_id] ||
	    !unit_exists(target[parent_id], ACTIVE_ENEMY_LISTS)) {
	    if (not [self findClosestUnitWithinJumpRange])
		return THING_DEAD;
	}

	/* We missed, probably because the target moved away too fast.
	   Try again, with less steps. */
	else
	    [self calculateLockSpline:path_max-4];
    }

    else {
	double deltax = 0.0, deltay = 0.0;

	/* Must travel at least 5 pixels from current location.  This
	   is to correct turning point slow down. */
	while ((SQ(deltax) + SQ(deltay) < SQ(5.0)) && (path < path_max-1)) {
	    path++;    
	    deltax = the_path[path].x - x;
	    deltay = the_path[path].y - y;
	}
	x = the_path[path].x;
	y = the_path[path].y;
    }

    return THING_NORMAL;
}

- (id) setParentID:(int)n
{
    parent_id = n;

    if (target[parent_id]) {
	/* Make sure the target hasn't been killed. */
	if (not unit_exists(target[parent_id], ACTIVE_ENEMY_LISTS))
	    target[parent_id] = nil;
	else {
	    /* We have a path to follow! */
	    [self calculateLockSpline :TOOTHPASTE_STEPS];
	}
    }

    return self;
}

+ (enum DAMAGE_TYPE) damageType { return DAMAGE_TYPE_TOOTHPASTE; }

- (BOOL) collidesWith:(Thing<DetectsCollision> *)object
{
    if ([super collidesWith:object]) {
	/* If no target locked, the hitee is the new target. */
	if (path < 0 || !target[parent_id])
	    target[parent_id] = (Unit *)object;

	return YES;
    }
    return NO;
}
@end

@implementation ToothpasteHigh
- init
{
    [super init];
    health = 2;
    length = TOOTHPASTE_LENGTH*2.0;
    jump_range = 100;
    return self;
}
@end

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

void unlockToothpaste(int pid)
{
    assert(pid < MAX_PLAYERS);
    target[pid] = nil;
}

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

void fireToothpaste(double x, double y, int level, int *reload, int pid)
{
#define SPAWN_ALLY_PROJ(class,x,y)		(Toothpaste *)spawn_projectile(class,x,y,YES)

    /* Level 0: 1 x  3 hp/shot * 50/8  shots/sec = 18.8 hp/sec.
       Level 1: 2 x  2 hp/shot * 50/9  shots/sec = 22.2 hp/sec.
       Level 2: 3 x  2 hp/shot * 50/10 shots/sec = 30.0 hp/sec.

       Note: setParentID *must* be at the end due to laziness.
    */

    y -= 10;
    if (level == 0) {
        [SPAWN_ALLY_PROJ([Toothpaste class], x, y) setParentID:pid];
        *reload = 8;
    }
    elif (level == 1) {
	Class proj = [ToothpasteHigh class];
        [[SPAWN_ALLY_PROJ(proj, x-16, y) setMinAngle:deg2rad(90.0) maxAngle:deg2rad(110.0)] setParentID:pid];
	[[SPAWN_ALLY_PROJ(proj, x+16, y) setMinAngle:deg2rad(70.0) maxAngle:deg2rad(90.0)]  setParentID:pid];
        *reload = 9;
    }
    else {
	Class proj = [ToothpasteHigh class];
        [SPAWN_ALLY_PROJ(proj, x, y) setParentID:pid];
	[[SPAWN_ALLY_PROJ(proj, x-4, y) setMinAngle:deg2rad(-60.0)  maxAngle:deg2rad(-40.0)]  setParentID:pid];
        [[SPAWN_ALLY_PROJ(proj, x+4, y) setMinAngle:deg2rad(-120.0) maxAngle:deg2rad(-140.0)] setParentID:pid];
        *reload = 10;
    }
    
    play_voice_on_channels(fire_sample, x, 192, PRIMARY_WEAPON_CHANNELS);

#undef SPAWN_ALLY_PROJ
}
