/* Projectile.m
 *
 * Projectiles collide with units and nukes, but not other
 * projectiles.
 */

#include <allegro.h>
#include <assert.h>
#include <math.h>
#include "common.h"
#include "debris/spark.h"
#include "linklist.h"
#include "map.h"
#include "nuke.h"
#include "player.h"
#include "projectile.h"
#include "unit-intern.h"
#include "unit.h"


static List *projectile_list;


@implementation Projectile
        /* Initialization. */
- (BOOL) loadData { return YES; }

- init
{
    [super init];

    sprite = (Sebum<SebImage> *)[base_sebum getSebumByName:"dummy8x8"];
    w = [sprite width];
    h = [sprite height];
    rotatable_sprite = NO;

    angle = -M_PI_2;
    speed = 2.0;
    recalculate_velocity = YES;

    alliance = NO;

    return self;
}

- (id) setAlliance:(BOOL)good { alliance = good; return self; }

- (id) setAngle:(double)theta
{
    angle = theta;
    recalculate_velocity = YES;
    return self;
}

- (void) die
{
    /* This function is called when projectiles hit an enemy.  It does
       not actually remove the projectile from the lists - that is
       done by update projectile. */

    while (health-- > 0)
	spawn_debris([Spark class], x, y, MEDIUM_LAYER);
}

	/* Drawing. */
- (void) draw:(BITMAP *)dest
{
    if (rotatable_sprite)
	[sprite drawTo:dest X:x-offsetX Y:y-offsetY Angle:angle];
    else
	[super draw:dest];
}

	/* Update. */
- (int) update
{
    /* The angle was changed, recalculate xv/yv. */
    if (recalculate_velocity) {
	recalculate_velocity = NO;
	xv = speed * cos(angle);
	yv = speed * sin(angle);
    }

    x += xv;
    y -= yv;

    return [super update];
}

	/* Collisions. */
- (BOOL) collidesWith:(Thing<DetectsCollision> *)object
{
    /* This is for unit/projectile collisions.  Usually we want the
       unit to check for collisions as they are more likely to be
       fancy (as MiniBoss1's collision detection).  This is not always
       the case though (ie. railguns). */

    return [object collidesWith:self];

#if 0
    double x1, y1, x2, y2;
    int w1, h1, w2, h2;

    [self   getX:&x1 Y:&y1 W:&w1 H:&h1];
    [object getX:&x2 Y:&y2 W:&w2 H:&h2];

    if (w2 <= 0 || h2 <= 0)
	return NO;

    return ((x1 - w1/2 <= x2 + w2/2) && (x1 + w1/2 >= x2 - w2/2) &&
	    (y1 - h1/2 <= y2 + h2/2) && (y1 + h1/2 >= y2 - h2/2));
#endif
}

- (void) getX:(double *)x_ Y:(double *)y_ W:(int *)w_ H:(int *)h_
{
    if (x_) *x_ = x;
    if (y_) *y_ = y;
    if (w_) *w_ = w;
    if (h_) *h_ = h;
}

+ (int) collisionLists { return ALLY_LIST|COLLIDES_WITH_NUKES; }
+ (enum DAMAGE_TYPE) damageType { return DAMAGE_TYPE_PROJECTILE; }
- (int) damage { return MAX(0, health); }
- (int) parentID { return -1; }
@end

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

@implementation AllyProjectile
+ (int) collisionLists { return ACTIVE_ENEMY_LISTS; }
- (id) setParentID:(int)n { parent_id = n; return self; }
- (int) parentID { return parent_id; }
@end

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

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

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

static BOOL collide_projectile_with_nuke(Projectile *proj)
{
    Nuke *nukular = nuke_in_contact_with(proj);

    if (nukular) {
	[nukular receiveDamage:[proj damage]];
	[proj die];
	return YES;
    }
    return NO;    
}

static BOOL collide_projectile_with_unit_in_list(Projectile *proj, List *list)
{
    ListIterator *it;

    foreach (it, list) {
	Unit *unit = [it getItem];
	if ([unit collisionLists] & COLLIDES_WITH_PROJECTILES) {
	    if ([proj collidesWith:unit]) {
		[unit receiveDamage:[proj damage] type:[[proj class] damageType]
		      from:[proj parentID]];
		[proj die];
		return YES;
	    }
	}
    }
    return NO;
}

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

    /* Ally projectiles collision with enemies. */
    foreach_nx (it, nx, projectile_list) {
	Projectile *proj = [it getItem];
	int collision_lists = [[proj class] collisionLists];
	nx = [it next];

	if (collision_lists & COLLIDES_WITH_NUKES &&
	    collide_projectile_with_nuke(proj));
	else if (collision_lists & ACTIVE_AIR_LIST &&
		 collide_projectile_with_unit_in_list(proj, active_air_list));
	else if (collision_lists & ACTIVE_GROUND_LIST &&
		 collide_projectile_with_unit_in_list(proj, active_ground_list));
	else if (collision_lists & ALLY_LIST)
	    collide_projectile_with_unit_in_list(proj, ally_list);

	if ([proj update] == THING_DEAD) {
	    [projectile_list removeItem:proj];
	    [proj free];
	}
    }
}

Projectile *spawn_projectile(Class class, double x, double y, BOOL is_goodie)
{
    Projectile *proj = [class new];

    if (proj) {
        [[proj setX:x Y:y] setAlliance:is_goodie];
	[projectile_list insertItem:proj];
    }

    return proj;
}

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

void projectile_init(void)
{
    projectile_list = [LIST_NEW];
    assert(projectile_list);
}

void projectile_shutdown(void)
{
    if (projectile_list)
	projectile_list = [projectile_list free];

    mark_all_projectile_data_unnecessary();
}
