/* nuke.m,
 *
 * Nukes have a special class of its own for several reasons:
 *  1) It needs to collide with both air AND ground units (so it can't
 *     be a unit).
 *  2) It needs to collide with projectiles (so it can't be a
 *     projectile).
 *  3) It needs to hurt multiple units and projectiles at once.
 *     Projectiles and units can only hurt one thing per update.
 *
 * Note: all nukes are allies.
 */

#include <allegro.h>
#include <assert.h>
#include "common.h"
#include "linklist.h"
#include "map.h"
#include "pregen-circles.h"
#include "nuke.h"
#include "unit.h"


#define DEFAULT_RADIUS	280


static List *nuke_list;


@implementation Nuke
- (id) init
{
    [super init];

    health = 100;
    radius = 16.0;
    nuke_radius = DEFAULT_RADIUS;

    return self;
}

- (id) setNukeRadius:(int)r { nuke_radius = r; return self; }
- (id) setParentID:(int)n { pid = n; return self; }

- (void) draw:(BITMAP *)dest
{
    int a = 0x80 * (1.0 - radius/nuke_radius);
    int c = makecol(0xa0, 0x80, 0x80);
    int x_ = x - offsetX;
    int y_ = y - offsetY;

    pregen_circlefill_add(dest, x, y_, radius,      c, a/3);
    pregen_circlefill_add(dest, x, y_, radius/2. ,  c, a/3);
    pregen_circlefill_add(dest, x, y_, radius*4/5., c, a/3);
    pregen_circle(dest, x_, y_, radius, makecol(0, 0, 0));
    //pregen_circle(dest, x_, y_, radius/2, makecol(0, 0, 0));
}

- (int) update
{
    if (health <= 0)
	return THING_DEAD;

    if (radius == nuke_radius)
	return THING_DEAD;

    radius = MIN((radius * 1.05) + 1.0, nuke_radius);
    w = h = 2 * radius;
    return THING_NORMAL;
}

- (BOOL) collidesWith:(Thing<DetectsCollision> *)object
{
    /* Our blast is a circle, and the object probably is too, so I'm
       doing a circle collision detection. */

    double x2, y2;
    int w2, h2;

    [object getX:&x2 Y:&y2 W:&w2 H:&h2];

    return (SQ(x2 - x) + SQ(y2 - y) <= SQ((w2 + w) / 2) + SQ((h2 + h) / 2));
}

- (int) receiveDamage:(int)damage
{
    health -= damage;

    /* This is just to be consistent with units so GCC won't bitch. */
    return damage;
}

- (int) parentID { return pid; }
@end

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

Nuke *nuke_in_contact_with(Thing<DetectsCollision> *object)
{
    ListIterator *it;

    foreach (it, nuke_list) {
	Nuke *nukular = [it getItem];
	if ([nukular collidesWith:object])
	    return nukular;
    }

    return nil;
}

void draw_nukes(BITMAP *dest)
{
    ListIterator *it;

    foreach (it, nuke_list)
	[[it getItem] draw:dest];
}

void update_nukes(void)
{
    ListIterator *it, *nx;

    foreach_nx (it, nx, nuke_list) {
	Nuke *nukular = [it getItem];
	nx = [it next];

	if ([nukular update] == THING_DEAD) {
	    [nuke_list removeItem:nukular];
	    [nukular free];
	}

	/* Collision detection is checked during unit's update routine
	   so we don't need to make many loops through the unit lists.
	   A side-effect is that a unit will only receive damage from
	   one nuke if two nukes overlap, which is no problem. */
    }
}

Nuke *spawn_nuke(double x, double y, int pid)
{
    Nuke *nukular = [Nuke new];

    if (nukular) {
        [[nukular setX:x Y:y] setParentID:pid];
	[nuke_list insertItem:nukular];
    }

    return nukular;
}

void nuke_init(void)
{
    nuke_list = [LIST_NEW];
    assert(nuke_list);
}

void nuke_shutdown(void)
{
    if (nuke_list)
	nuke_list = [nuke_list free];
}
