/* miniboss0.m,
 *
 * The miniboss for the desert level, a triple-turreted tank.  50%
 * Bonus damage for laser and vulcan.
 */

#include <allegro.h>
#include <assert.h>
#include <math.h>
#include "common.h"
#include "map.h"
#include "debris/explosion.h"
#include "debris/large-chunk.h"
#include "debris/tank-debri.h"
#include "nuke.h"
#include "projectiles/fireball.h"
#include "projectiles/pulse-laser.h"
#include "projectiles/railgun.h"
#include "rotate.h"
#include "seborrhea/seborrhea-allegro.h" /* XXX */
#include "seborrhea/seborrhea.h"
#include "unit-seeking.h"
#include "units/miniboss0.h"


#define foreach_child(c)	for (c = 0; c < MINIBOSS0_NUM_CHILDREN; c++)
#define GUN_TURRET_HEALTH	400
#define PULSE_LASER_HEALTH	200
#define RAILGUN_HEALTH		400
#define TOTAL_HEALTH		(GUN_TURRET_HEALTH+PULSE_LASER_HEALTH+RAILGUN_HEALTH)
#define TRACK_HEALTH		300
#define TANK_SPEED		0.65


typedef struct {
    int x, y;
} displacement_t;

static displacement_t weapon_displacements[MINIBOSS0_NUM_CHILDREN] = {
    {  28, -30}, { -28, -14}, {   0,  23}
};

static displacement_t overlay_dislacements[MINIBOSS0_NUM_CHILDREN] = {
    {  23, -32}, { -28, -20}, {   0,  25}
};


static SebFile *boss_sebum;
static SAMPLE *engine_sample;	/* XXX */
static BITMAP *meter_gray, *meter_purple; /* XXX */

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

@interface MiniBoss0BodyDebri: Debri
@end

@interface GunTurret: MiniBoss0AbstractChild
{
    DualWeapon *gun[2];
}
@end

@interface GunTurretOverlay: MiniBoss0AbstractOverlay
@end

@interface LaserTurret: MiniBoss0AbstractChild
@end

@interface LaserTurretOverlay: MiniBoss0AbstractOverlay
@end

@interface RailgunTurret: MiniBoss0AbstractChild
@end

@interface RailgunTurretOverlay: MiniBoss0AbstractOverlay
@end

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

@implementation MiniBoss0
	/* Initialization. */
+ (BOOL) loadPrerequisites
{
    return (LOAD_PROJECTILE_DATA_FOR(Fireball) &&
	    LOAD_PROJECTILE_DATA_FOR(PulseLaser) &&
	    LOAD_PROJECTILE_DATA_FOR(Railgun));
}

+ (BOOL) loadData
{
    boss_sebum = [SebFile new];
    if (not [boss_sebum loadSebumDirectory:"data/miniboss0"])
	return NO;

    meter_gray   = [(SebImageAllegro *)[base_sebum getSebumByName:"glue/meter-gray"] bitmap];
    meter_purple = [(SebImageAllegro *)[base_sebum getSebumByName:"glue/meter-purple"] bitmap];
    return YES;
}

+ (void) shutdown
{
    FREE_SEBFILE(boss_sebum);
    meter_gray   = NULL;
    meter_purple = NULL;
}

- init
{
    [super init];

    animation.anim = (SebAnimation *)[boss_sebum getSebumByName:"body"];
    animation.loop = YES;
    animation.delay = 3;
    animation.frame = 0;
    sprite = [animation.anim getFrame:0];
    w = [sprite width];
    h = [sprite height];

    flags |= FLAG_INVINCIBLE;
    activate_y = -200;

    travel_range = 610;
    speed = TANK_SPEED;
    last_track_y = -1;

    ring_gun = [[[[RingWeapon new]
		     setProjectile:[Fireball class]]
		    setShotDelay:100]
		   setStepSize:deg2rad(20.0)];

    return self;
}

- free
{
    if (engine_sample) {
	stop_sample(engine_sample);	/* XXX */
	engine_sample = NULL;
    }
    return [super free];
}

- (void) drawHealthMeter:(BITMAP *)dest
{
    int c, ww, life = 0;

    foreach_child (c)
	if (child[c])
	    life += [child[c] health];

    ww = 75 * life/TOTAL_HEALTH;
    blit(meter_gray,   dest,     0, 0, dest->w/2-75, dest->h-10,  150, 8);
    blit(meter_purple, dest, 75-ww, 1, dest->w/2-ww, dest->h-9,  2*ww, 6);
}

- (BOOL) readyToActivate
{
#define SPAWN_CHILD(c)		[(<MiniBoss0Child>)spawn_unit([c class], x, y, ACTIVE_GROUND_LIST, NO) setParent:self];

    if ([super readyToActivate]) {
	boss = self;

	child[MINIBOSS0_GUN_TURRET]     = SPAWN_CHILD(GunTurret);
	child[MINIBOSS0_LASER_TURRET]   = SPAWN_CHILD(LaserTurret);
	child[MINIBOSS0_RAILGUN_TURRET] = SPAWN_CHILD(RailgunTurret);

	engine_sample = [(SebSampleAllegro *)[boss_sebum getSebumByName:"engine"] sample];
	play_sample(engine_sample, 192, 128, 1000, 1);

	return YES;
    }
    else
	return NO;

#undef SPAWN_CHILD
}

- (void) move
{
    int c;

    if (travel_range <= 0.0)
	return;

    y -= speed;
    travel_range -= speed;

    foreach_child (c) {
	if (debri[c])
	    [debri[c] setX:x Y:y];
    }

    if (animate(&animation))
	sprite = [animation.anim getFrame:animation.frame];

    if (ABS(last_track_y - y) > 28) {
	int y_ = y + [sprite height]/2 - 55;
	/* -55 is for track's height. */
	spawn_debris([MiniBoss0Tracks class], x, y_, FLOOR_LAYER);
	last_track_y = y;
    }

    if (travel_range <= 130 && not (flags & FLAG_DYING))
	[current_map setScrollRate:0.0 :YES];
}

- (void) fire
{
    /* Same coordinates as railgun. */
    if (!child[MINIBOSS0_RAILGUN_TURRET])
	[ring_gun fireFromX:x+weapon_displacements[MINIBOSS0_RAILGUN_TURRET].x
		  Y:y+weapon_displacements[MINIBOSS0_RAILGUN_TURRET].y 
		  angle:0.0];
}

- (void) die
{
    int c;

    foreach_child (c) {
	if (debri[c]) {
	    [debri[c] die];
	    debri[c] = nil;
	}
    }

    animation.anim = nil;
    sprite = [boss_sebum getSebumByName:"debri"];
    [super die];
}

- (void) spawnDyingExplosions
{
    if (rnd(health, 0) < -40) {
	spawn_debris([LargeChunkGreen class], x, y, HIGH_LAYER);

	/* Scatter some debri on the ground around us. */
	{
	    int dist = rnd(h, h+50);
	    double theta = deg2rad(rnd(-180, 180));

	    spawn_debris([TankDebri class],
			 x + dist*cos(theta),
			 y + dist*sin(theta),
			 FLOOR_LAYER);
	}
    }

    if (health > -100) {
	w = [sprite width]/2;
	h = [sprite height]/2;
	spawn_debris([BigExplosion class], x + rnd(-w, w), y + rnd(-h, h), MEDIUM_LAYER);
	play_explosion_sample(x);
    }
    else {
	spawn_nuke(x, y, -1);
        [self delete];
    }

    health--;
    flash_tics += 2;
}

- (void) delete
{
    [current_map setScrollRate:DEFAULT_SCROLL_RATE :YES];
    spawn_debris([MiniBoss0BodyDebri class], x, y, LOW_LAYER);

    if (boss == self)
	boss = nil;

    [super delete];
}

- (void) drawMapEditorExtras:(BITMAP *)dest
{
    int xx, yy;

    {				/* Gun Turret */
	xx = weapon_displacements[MINIBOSS0_GUN_TURRET].x;
	yy = weapon_displacements[MINIBOSS0_GUN_TURRET].y;
	[(<SebImage>)[boss_sebum getSebumByName:"gun-turret"]
		     drawTo:dest X:x+xx-offsetX Y:y+yy-offsetY Angle:M_PI_2];
    }

    {				/* Laser Turret */
	xx = weapon_displacements[MINIBOSS0_LASER_TURRET].x;
	yy = weapon_displacements[MINIBOSS0_LASER_TURRET].y;
	[(<SebImage>)[boss_sebum getSebumByName:"pulse-laser"]
		     drawTo:dest X:x+xx-offsetX Y:y+yy-offsetY Angle:M_PI_2];
    }

    {				/* Railgun */
	xx = weapon_displacements[MINIBOSS0_RAILGUN_TURRET].x;
	yy = weapon_displacements[MINIBOSS0_RAILGUN_TURRET].y;
	[(<SebImage>)[boss_sebum getSebumByName:"railgun"]
		     drawTo:dest X:x+xx-offsetX Y:y+yy-offsetY Angle:M_PI_2];
    }

    [super drawMapEditorExtras:dest];
}

	/* Protocol stuff. */
- (void) disownChild:(Unit *)unit
{
#define SPAWN_OVERLAY(c)	spawn_debris([c class], x, y, LOW_LAYER)

    int c;

    assert(unit);
    foreach_child (c) {
	if (child[c] == unit) {
	    child[c] = nil;
	    break;
	}
    }
    assert(c < MINIBOSS0_NUM_CHILDREN && "Attempted to disown non-child-unit from MiniBoss0");

    /* Spawn some debri. */
    if (c == MINIBOSS0_GUN_TURRET)
	debri[c] = SPAWN_OVERLAY(GunTurretOverlay);
    elif (c == MINIBOSS0_LASER_TURRET)
	debri[c] = SPAWN_OVERLAY(LaserTurretOverlay);
    else {
	debri[c] = SPAWN_OVERLAY(RailgunTurretOverlay);
	flags |= FLAG_FIRING_ENABLED;
    }

    if (not child[MINIBOSS0_GUN_TURRET] &&
	not child[MINIBOSS0_LASER_TURRET] &&
	not child[MINIBOSS0_RAILGUN_TURRET])
	[self die];

#undef SPAWN_OVERLAY
}

- (int) collisionLists { return 0; }

- derive_fixedAngle;
- derive_alwaysInfluenceGameplay;
- derive_setXYAlwaysCentred
@end

@implementation MiniBoss0BodyDebri
- init
{
    [super init];
    sprite = [boss_sebum getSebumByName:"debri"];
    return self;
}
@end

/*------------------------------------------------------------*/
/* Abstract MiniBsos Child stuff.			      */
/*------------------------------------------------------------*/

@implementation MiniBoss0AbstractChild
- init
{
    [super init];
    rotatable_unit = YES;
    angle = M_PI_2;
    flags |= FLAG_FIRING_ENABLED;
    return self;
}

- (void) delete
{
    if (parent) {
	[parent disownChild:self];
	parent = nil;
    }

    [super delete];
}

- (void) move
{
    /* Parent not set?  This should never happen. */
    assert(parent && "Parent (MiniBoss0) went missing!");

    if (unit_exists(parent, ACTIVE_GROUND_LIST)) {
	[parent getX:&x Y:&y];
	x += weapon_displacements[child_num].x;
	y += weapon_displacements[child_num].y;
    }
    else {
	/* Parent removed before we were.  This should never happen
	   since the parent is invincible. */
	assert("Parent (MiniBoss0) deleted before child!\n" && NO);
    }
}

- (int) health { return health; }
- (id) setParent:(Unit<OwnsChildren> *)unit { parent = unit; return self; }
- (int) collisionLists { return COLLIDES_WITH_PROJECTILES; }

- (int) receiveDamage:(int)damage type:(enum DAMAGE_TYPE)type
{
    if (type == DAMAGE_TYPE_VULCAN)
	damage++;
    else if (type == DAMAGE_TYPE_LASER)
	damage++;

    return [super receiveDamage:damage type:type];
}

- (void) findDesiredAngle
{
    /* Relocate the target and rotate towards it. */
    Unit *target = find_closest_ally_according_to_rotation(x, y, angle);
    if (target) {
	double x_, y_;
	[target getX:&x_ Y:&y_];
	desired_angle = atan2(y-y_, x_-x);
    }
    else
	desired_angle = deg2rad(90.0);

    limited_rotate(&angle, desired_angle, max_rotation_angle);
    simplify_angle(&angle);
}

- (int) update
{
    [self findDesiredAngle];

    /* Animations. */
    if (animation.anim && animate(&animation))
	sprite = [animation.anim getFrame:animation.frame];

    return [super update];
}

- derive_alwaysInfluenceGameplay;
@end

@implementation MiniBoss0AbstractOverlay
- (id) setX:(double)x_ Y:(double)y_
{
    x = x_ + overlay_dislacements[child_num].x;
    y = y_ + overlay_dislacements[child_num].y;
    return self; 
}

- (void) die { health = 0; }
@end

/*------------------------------------------------------------*/
/* Gun Turret.						      */
/*------------------------------------------------------------*/

@interface GunTurretDebri: TurretDebri
@end

@implementation GunTurret
- init
{
    [super init];

    child_num = MINIBOSS0_GUN_TURRET;
    animation.anim = (SebAnimation *)[boss_sebum getSebumByName:"gun-turret"];
    animation.tics = animation.delay = 18 / 3;
    animation.frame = 0;
    animation.loop = YES;

    sprite = [animation.anim getFrame:0];
    health = GUN_TURRET_HEALTH;
    w = [sprite width];
    h = [sprite height];

    max_rotation_angle = deg2rad(2.1);

    gun[0] = [[[[DualWeapon new]
		   setProjectile:[Fireball class]]
		  setShotDelay:20 WaveDelay:20]
		 setXDisplacement:15 YDisplacement:-4];
    gun[1] = [[[[DualWeapon new]
		   setProjectile:[Fireball class]]
		  setShotDelay:20 WaveDelay:20]
		 setXDisplacement:15 YDisplacement:-4];

    return self;
}

- (void) die
{
    [super die];
    spawn_debris([GunTurretDebri class],
		 x+weapon_displacements[child_num].x,
		 y+weapon_displacements[child_num].y,
		 HIGH_LAYER);
}

- (int) update
{
    if (not (game_flags & FLAG_PLAYERS_ALIVE))
	animation.loop = NO;

    return [super update];
}

- (void) fire
{
    [gun[0] fireFromX:x Y:y angle:angle - deg2rad(2.5)];
    [gun[1] fireFromX:x Y:y angle:angle + deg2rad(2.5)];
}
@end

@implementation GunTurretOverlay
- init
{
    [super init];
    child_num = MINIBOSS0_GUN_TURRET;
    sprite = [boss_sebum getSebumByName:"gun-debri"];
    return self;
}
@end

@implementation GunTurretDebri
- init
{
    [super init];
    health = max_health = 90;
    sprite = [(SebAnimation *)[boss_sebum getSebumByName:"gun-turret"] getFrame:-1];
    return self;
}
@end

/*------------------------------------------------------------*/
/* Laser Turret.					      */
/*------------------------------------------------------------*/

@interface LaserTurretDebri: TurretDebri
@end

@implementation LaserTurret
- init
{
    [super init];

    child_num = MINIBOSS0_LASER_TURRET;
    sprite = [boss_sebum getSebumByName:"pulse-laser"];
    health = PULSE_LASER_HEALTH;
    w = [sprite width];
    h = [sprite height];

    max_rotation_angle = deg2rad(3.0);
    shot_delay = 28;

    return self;
}

- (void) die
{
    [super die];
    spawn_debris([LaserTurretDebri class], 
		 x+weapon_displacements[child_num].x,
		 y+weapon_displacements[child_num].y,
		 HIGH_LAYER);
}

- (void) fire
{
    if (--fire_tics <= 0) {
	fire_tics = shot_delay;
	[spawn_projectile([PulseLaser class], 
			  x + 25*sin(angle+M_PI_2),
			  y + 25*cos(angle+M_PI_2), NO) setAngle:angle];
    }
}
@end

@implementation LaserTurretOverlay
- init
{
    [super init];
    child_num = MINIBOSS0_LASER_TURRET;
    sprite = [boss_sebum getSebumByName:"laser-debri"];
    return self;
}
@end

@implementation LaserTurretDebri
- init
{
    [super init];
    health = max_health = 80;
    sprite = [boss_sebum getSebumByName:"pulse-laser"];
    return self;
}
@end

/*------------------------------------------------------------*/
/* Railgun.						      */
/*------------------------------------------------------------*/

@interface RailgunTurretDebri: TurretDebri
@end

@implementation RailgunTurret
- init
{
    [super init];

    child_num = MINIBOSS0_RAILGUN_TURRET;
    sprite = [boss_sebum getSebumByName:"railgun"];
    health = RAILGUN_HEALTH;
    w = [sprite width];
    h = [sprite height];

    shot_delay = 40;
    return self;
}

- (void) die
{
    [super die];
    spawn_debris([RailgunTurretDebri class], 
		 x+weapon_displacements[child_num].x,
		 y+weapon_displacements[child_num].y,
		 HIGH_LAYER);
}

- (void) fire
{
    if (--fire_tics <= 0) {
	fire_tics = shot_delay;

	[spawn_projectile([Railgun class],
			  x + 25*sin(angle+M_PI_2),
			  y + 25*cos(angle+M_PI_2),
			  NO)
			 setAngle:angle];
    }
}

- (void) findDesiredAngle
{
    if (fire_tics >= shot_delay - 10)
	return;
    else {
	if (fire_tics < 10)
	    max_rotation_angle = deg2rad(1.0);
	else
	    max_rotation_angle = deg2rad(4.0);

	[super findDesiredAngle];
    }
}
@end

@implementation RailgunTurretOverlay
- init
{
    [super init];
    child_num = MINIBOSS0_RAILGUN_TURRET;
    sprite = [boss_sebum getSebumByName:"railgun-debri"];
    return self;
}
@end

@implementation RailgunTurretDebri
- init
{
    [super init];
    health = max_health = 100;
    sprite = [boss_sebum getSebumByName:"railgun"];
    return self;
}
@end

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

@implementation MiniBoss0Tracks
- init
{
    [super init];

    sprite = [boss_sebum getSebumByName:"tracks"];
    w = [sprite width];
    h = [sprite height];
    health = TRACK_HEALTH;

    return self;
}

- (void) draw:(BITMAP *)dest
{
    [sprite drawTo:dest
	    X:x - offsetX - [sprite width]/2
	    Y:y - offsetY - [sprite height]/2
	    Alpha:0xff * health / TRACK_HEALTH];

    health--;
}
@end
