/* boss0.m,
 *
 * This is the boss for the desert level and the first you meet!
 *
 * 20% Damage bonus for vulcan.
 */

#include <allegro.h>
#include <assert.h>
#include <math.h>
#ifndef NO_FBLEND
# include "fblend/include/fblend.h"
#endif
#include "colour-conv.h"
#include "common.h"
#include "debris/explosion.h"
#include "debris/large-chunk.h"
#include "debris/spark.h"
#include "end-level.h"
#include "map.h"
#include "player.h"
#include "projectiles/fireball.h"
#include "projectiles/maser.h"
#include "seborrhea/container-animation.h"
#include "seborrhea/seborrhea-allegro.h" /* XXX */
#include "sound.h"
#include "unit-seeking.h"
#include "units/all-units.h"
#include "units/boss0.h"
#include "units/hunter-seeker.h"


#define foreach_child(c)	for (c = 0; c < BOSS0_NUM_CHILDREN; c++)
#define BASE_LINE		100.0
#define BODY_HEALTH		500
#define WING_HEALTH		50
#define TUSK_HEALTH		200
#define HUGE_LASER_LENGTH	160
#define HUGE_LASER_SPEED	20.0
#define HUGE_LASER_CHARGE_DELAY	20
#define NUM_PATHS		3


static const char *part_names[BOSS0_NUM_PARTS] = {
    "wing-l", "wing-r", "tusk-l", "tusk-r", "body-d"
};

static const char *shadow_names[BOSS0_NUM_PARTS] = {
    "shadow-wl", "shadow-wr", "shadow-tl", "shadow-tr", "shadow"
};

static struct {
    int x, y;
} part_displacements[BOSS0_NUM_CHILDREN] = {
    { -87, -24 }, {  86, -24 }, { -44,  69 }, {  44,  69 }
};


static SebFile *boss_sebum;
static SebSpline *the_path[NUM_PATHS];

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

@interface LeftWing: Boss0Part
{
    int shot_delay, fire_tics;
}
@end

@interface RightWing: LeftWing
@end

@interface LeftTusk: Boss0Part
@end

@interface RightTusk: LeftTusk
@end

@interface Boss0Charging: AnimatedDebriNoLoop
@end

@interface Boss0HugeLaser: Projectile
{
    int damage;
}
@end

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

@implementation Boss0
+ (BOOL) loadPrerequisites
{
    return (LOAD_UNIT_DATA_FOR(HunterSeeker, YES) &&
	    LOAD_PROJECTILE_DATA_FOR(Fireball) &&
	    LOAD_PROJECTILE_DATA_FOR(Maser));
}

+ (BOOL) loadData
{
    set_colour_conversion_keep_alpha();

    boss_sebum = [SebFile new];
    if (not [boss_sebum loadSebumDirectory:"data/boss0"])
	return NO;

    set_color_conversion(COLORCONV_TOTAL);

    {					/* Paths */
	the_path[0] = (SebSpline *)[boss_sebum getSebumByName:"path0"];
	the_path[1] = (SebSpline *)[boss_sebum getSebumByName:"path1"];
	the_path[2] = (SebSpline *)[boss_sebum getSebumByName:"path2"];
    }

    return YES;
}

+ (void) shutdown
{
    int i;

    FREE_SEBFILE(boss_sebum);

    for (i = 0; i < NUM_PATHS; i++)
	the_path[i] = nil;
}

- (void) spawnDyingExplosions
{
    if (health < -50)			/* 2 seconds. */
	spawn_debris([LargeChunk class], x, y, HIGH_LAYER);
    if (health > -150) {		/* 3 seconds. */
	spawn_debris([BigExplosion class],
		     rnd(x-w/2, x+w/2), rnd(y-h/2, y+h/2), HIGH_LAYER);
	play_explosion_sample(x);
    }
    else {
	if (boss == self)
	    boss = nil;

	end_level(5*SECONDS);
        [self delete];
    }

    health--;
    flash_tics += 2;
}

- init
{
    [super init];

    health = BODY_HEALTH;

    sprite = [[SebAnimator new] setAnimation:(SebAnimation *)[boss_sebum getSebumByName:"body"]];
    shadow = [boss_sebum getSebumByName:shadow_names[BOSS0_BODY]];
    w = [sprite width];
    h = 90;
    t = 0;

    turret_angle[0] = turret_angle[1] = -M_PI_2;

    flags |= FLAG_FIRING_ENABLED|FLAG_INVINCIBLE;

    return self;
}

- (void) die
{
    int c;

    /* Tell all of our children to die since the wings are optional
       kills. */
    foreach_child (c)
	if (child[c])
	    [child[c] die];

    /* Change the sprite to a busted up pic.  Remember to free the
       sprite because it is a SebAnimator. */
    [sprite free];
    sprite = [boss_sebum getSebumByName:"body-d"];

    [super die];
}

- (void) draw:(BITMAP *)dest
{
    [super draw:dest];

    if (game_flags & FLAG_PLAYERS_ALIVE &&
	missile_tics > missile_delay-10) {
	int yy[3] = { 74/2, 22/2, 50/2};
	BITMAP *fire;

	if (missile_barrel == 1)
	    fire = [(SebImageAllegro *)[boss_sebum getSebumByName:"fire0"] bitmap];
	elif (missile_barrel == 2)
	    fire = [(SebImageAllegro *)[boss_sebum getSebumByName:"fire1"] bitmap];
	else
	    fire = [(SebImageAllegro *)[boss_sebum getSebumByName:"fire2"] bitmap];

	draw_sprite(dest, fire, x-fire->w/2-offsetX, y-yy[missile_barrel]-offsetY);
    }

    {				/* Turrets. */
	BITMAP *turret = [(SebImageAllegro *)[boss_sebum getSebumByName:"turret"] bitmap];
	pivot_sprite(dest, turret, x-35-offsetX, y-8-offsetY,
		     turret->w/2, turret->h/2, rad2fix(turret_angle[0]));
	pivot_sprite(dest, turret, x+35-offsetX, y-8-offsetY,
		     turret->w/2, turret->h/2, rad2fix(turret_angle[1]));
    }
}

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

    if (flags & FLAG_DYING)
	return;

    if (child[BOSS0_LEFT_TUSK] || child[BOSS0_RIGHT_TUSK]) {
	if (child[BOSS0_LEFT_TUSK])  ww += [child[BOSS0_LEFT_TUSK]  health];
	if (child[BOSS0_RIGHT_TUSK]) ww += [child[BOSS0_RIGHT_TUSK] health];
	ww = 75 * ww / (2*TUSK_HEALTH);
    }
    else
	ww = 75 * health/BODY_HEALTH;

    blit([(SebImageAllegro *)[base_sebum getSebumByName:"glue/meter-gray"] bitmap],
	 dest, 0, 0, dest->w/2-75, dest->h-10, 150, 8);

    blit([(SebImageAllegro *)[base_sebum getSebumByName:"glue/meter-purple"] bitmap],
	 dest, 75-ww, 1, dest->w/2-ww, dest->h-9, 2*ww, 6);
}

- (BOOL) readyToActivate
{
#define SPAWN_CHILD(c)		[(Boss0Part *)spawn_unit([c class], 0, 1000, ACTIVE_AIR_LIST, NO) setParent:self];

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

	/* Children. */
	child[BOSS0_LEFT_WING]  = SPAWN_CHILD(LeftWing);
	child[BOSS0_RIGHT_WING] = SPAWN_CHILD(RightWing);
	child[BOSS0_LEFT_TUSK]  = SPAWN_CHILD(LeftTusk);
	child[BOSS0_RIGHT_TUSK] = SPAWN_CHILD(RightTusk);

	/* Slightly harder in 2 player mode. */
	if (player[0] && player[1]) {
	    fireball_tics = fireball_delay = 20;
	    laser_tics    = laser_delay    = 70;
	    missile_delay = 30;
	}
	else {
	    fireball_tics = fireball_delay = 25;
	    laser_tics    = laser_delay    = 80;
	    missile_delay = 40;
	}

	missile_tics = 0;

	[(<SebSample>)[boss_sebum getSebumByName:"evil"] playWithVolume:sound_vol/SOUND_VOLMAX];
	return YES;
    }
    else
	return NO;

#undef SPAWN_BOSS_CHILD
}

- (void) launchHunterSeeker:(BOOL)left
{
    int xx[3] = {  70,  66,  62 };
    int yy[3] = { - 6, -18, -30 };
    double x_, y_, theta;

    if (left) {
	x_ = x - xx[missile_barrel];
	theta = deg2rad(135.0);
    }
    else {
	x_ = x + xx[missile_barrel];
	theta = deg2rad(45.0);
    }

    y_ = y + yy[missile_barrel];
    [spawn_unit([HunterSeeker class], x_, y_, ACTIVE_AIR_LIST, YES) setAngle:theta];
}

- (int) update
{
    /* If started charging huge laser, but players died, fire
       anyway. */
    if (not (game_flags & FLAG_PLAYERS_ALIVE) &&
	laser_tics < HUGE_LASER_CHARGE_DELAY)
	[self fire];

    return [super update];
}

- (void) fire
{
    /* If a wing exists, fire the laser mounted on top of it.
       Otherwise fire homing missiles. */
    if (child[BOSS0_LEFT_WING])
	[child[BOSS0_LEFT_WING] fire];
    else if (missile_tics <= 0)
	[self launchHunterSeeker:YES];

    if (child[BOSS0_RIGHT_WING])
	[child[BOSS0_RIGHT_WING] fire];
    else if (missile_tics <= 0)
	[self launchHunterSeeker:NO];

    if (not child[BOSS0_LEFT_WING] || not child[BOSS0_RIGHT_WING]) {
	if (--missile_tics < 0) {
	    missile_tics = missile_delay;
	    if (++missile_barrel > 2)
		missile_barrel = 0;
	}
    }

    /* If neither tusks are alive, fire 'uge laser instead. */
    if (not child[BOSS0_LEFT_TUSK] && not child[BOSS0_RIGHT_TUSK] && 
	not the_way) {
	laser_tics--;

	if (laser_tics <= -HUGE_LASER_LENGTH/HUGE_LASER_SPEED) {
	    laser_tics = laser_delay;
	}
	elif (laser_tics < 0) {		/* Just fired.  Recoil. */
	    y += -10.0 - laser_tics*HUGE_LASER_SPEED/HUGE_LASER_LENGTH;
	}
	elif (laser_tics == 0) {	/* Fire! */
	    spawn_projectile([Boss0HugeLaser class], x, y+48, NO);
	}
	elif (laser_tics == HUGE_LASER_CHARGE_DELAY) {
	    if (y < BASE_LINE) {
		/* Don't fire yet - we might get pushed off the
		   screen due to recoil! */
		laser_tics++;
	    }
	    else
		spawn_debris([Boss0Charging class], x, y+35, HIGH_LAYER);
	}
    }

    /* Fire 2 fireballs, directed at the player. */
    if (--fireball_tics <= 0) {
	turret_angle[0] = ANGLE_TO_CLOSEST_ALLY(x-35, y-8);
	[spawn_projectile([Fireball class], x-35, y-8, NO) setAngle:turret_angle[0]];

	turret_angle[1] = ANGLE_TO_CLOSEST_ALLY(x+35, y-8);
	[spawn_projectile([Fireball class], x+35, y-8, NO) setAngle:turret_angle[1]];

	fireball_tics = fireball_delay;
    }
}

- (void) move
{
    /* If either tusk is alive, follow a spline which attempts to ram
       the player. */
    if (the_way) {
	if ([the_way follow:&path :&x :&y :8.0 :screen_w/2 :70] == PATH_ENDED)
	    the_way = nil;
    }

    elif (child[BOSS0_LEFT_TUSK] || child[BOSS0_RIGHT_TUSK]) {
	the_way = the_path[rand() % NUM_PATHS];
	path = 0;
    }

    /* If not charging up or firing huge laser, sweep left and right. */
    elif (laser_tics >= HUGE_LASER_CHARGE_DELAY) {
	/* Move 2/3 period between huge laser shots. */
	t += 240.0 / (laser_delay - HUGE_LASER_CHARGE_DELAY);

	x += 3.0 * cos(deg2rad(t));
	if (y < BASE_LINE)
	    y += 1.5;

	if (t > 360.0)
	    t -= 360.0;
    }
}

- (void) disownChild:(Unit *)unit
{
    int c;

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

    /* If both tusks are dead, unset invincibility. */
    if (not (child[BOSS0_LEFT_TUSK] || child[BOSS0_RIGHT_TUSK]))
	flags &=~FLAG_INVINCIBLE;
}

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

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

- (void) drawMapEditorExtras:(BITMAP *)dest
{
    int c;

    /* Draw all of our parts. */
    foreach_child (c) {
	BITMAP *spr = [(SebImageAllegro *)[boss_sebum getSebumByName:part_names[c]] bitmap];
	draw_sprite(dest, spr, 
		    x + part_displacements[c].x - spr->w/2 - offsetX,
		    y + part_displacements[c].y - spr->h/2 - offsetY);
    }

    [super drawMapEditorExtras:dest];
}

- derive_fixedAngle;
- derive_alwaysInfluenceGameplay;
- derive_setXYAlwaysCentred;
+ derive_airBossCollisionLists;
@end

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

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

    [super delete];
}

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

    if (unit_exists(parent, ACTIVE_AIR_LIST)) {
	[parent getX:&x Y:&y];
	x += part_displacements[part_num].x;
	y += part_displacements[part_num].y;
    }
    else {
	/* Parent removed before we were.  This should never happen
	   because the parent informs us when it dies. */
	assert("Parent (Boss0) deleted before child!\n" && NO);
    }
}

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

- (int) receiveDamage:(int)damage type:(enum DAMAGE_TYPE)type
{
    if (type == DAMAGE_TYPE_UNIT)
	return 0;
    if (type == DAMAGE_TYPE_VULCAN)
	damage++;

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

- derive_alwaysInfluenceGameplay;
+ derive_airBossCollisionLists;
@end

/*------------------------------------------------------------*/
/* Wings.						      */
/*------------------------------------------------------------*/

@implementation LeftWing
- init
{
    [super init];

    health = WING_HEALTH;
    part_num = BOSS0_LEFT_WING;

    sprite = [boss_sebum getSebumByName:part_names[part_num]];
    shadow = [boss_sebum getSebumByName:shadow_names[part_num]];
    w = [sprite width];
    h = [sprite height];

    fire_tics = shot_delay = 50;

    return self;
}

- (void) fire
{
    if (--fire_tics <= 0) {
	spawn_projectile([Maser class], x-17, y+40, NO);
	fire_tics = shot_delay;
    }
}

- (void) spawnDyingExplosions
{
    if (health < -75)		/* 1.5 seconds. */
	spawn_debris([LargeChunkRed class], x, y, HIGH_LAYER);
    if (health > -100)		/* 2 seconds. */
	spawn_debris([BigExplosion class],
		     rnd(x-w/4, x+w/4), rnd(y-h/4, y+h/4), HIGH_LAYER);
    else
        [self delete];

    health--;
    flash_tics += 2;
}
@end


@implementation RightWing
- init
{
    [super init];
    part_num = BOSS0_RIGHT_WING;
    sprite = [boss_sebum getSebumByName:part_names[part_num]];
    shadow = [boss_sebum getSebumByName:shadow_names[part_num]];
    return self;
}

- (void) fire
{
    if (--fire_tics <= 0) {
	spawn_projectile([Maser class], x+17, y+40, NO);
	fire_tics = shot_delay;
    }
}
@end

/*------------------------------------------------------------*/
/* Tusks.						      */
/*------------------------------------------------------------*/

@implementation LeftTusk
- init
{
    [super init];

    part_num = BOSS0_LEFT_TUSK;

    health = TUSK_HEALTH;
    sprite = [boss_sebum getSebumByName:part_names[part_num]];
    shadow = [boss_sebum getSebumByName:shadow_names[part_num]];
    w = [sprite width];
    h = [sprite height];

    return self;
}

- (void) spawnDyingExplosions
{
    if (health > -50)		/* 1 second. */
	spawn_debris([Explosion class],
		     rnd(x-w/4, x+w/4), rnd(y-h/4, y+h/4), HIGH_LAYER);
    else
        [self delete];

    health--;
    flash_tics += 2;
}
@end

@implementation RightTusk
- init
{
    [super init];
    part_num = BOSS0_RIGHT_TUSK;
    sprite = [boss_sebum getSebumByName:part_names[part_num]];
    shadow = [boss_sebum getSebumByName:shadow_names[part_num]];
    return self;
}
@end

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

@implementation Boss0Charging
- init
{
    [super init];

    speed = 0.0;
    sprite = [[SebAnimator new] setAnimation:(SebAnimation *)[boss_sebum getSebumByName:"charge"]];

    return self;
}
@end

@implementation Boss0HugeLaser
- init
{
    [super init];
    w = 40;
    h = 5;
    speed = HUGE_LASER_SPEED;
    damage = 999;			/* Insta-kill non-players. */
    return self;
}

- (void) die
{
    /* Don't die. */
    spawn_debris([Spark class], x, y, HIGH_LAYER);
}

- (void) draw:(BITMAP *)dest
{
    int x_ = x-offsetX;
    int y_ = y-offsetY;
    int c1 = makecol(0x3c, 0x55, 0x7c);
    int c2 = makecol(0x75, 0xa7, 0xf4);

    if (h < HUGE_LASER_LENGTH)
	y_ -= h/2;

#ifndef NO_FBLEND
    fblend_rect_add(dest, x_-w/2, y_, w/4, h, c1, 0xa0);
    fblend_rect_add(dest, x_-w/4, y_, w/2, h, c2, 0xc0);
    fblend_rect_add(dest, x_+w/4, y_, w/4, h, c1, 0xa0);
#else					/* Normal Allegro routines. */
    drawing_mode(DRAW_MODE_TRANS, NULL, 0, 0);
    set_add_blender(0xff, 0xff, 0xff, 0xa0);
    rectfill(dest, x_-w/2, y_, x_-w/4, y_+h-1, c1);
    rectfill(dest, x_+w/4, y_, x_+w/2, y_+h-1, c1);
    set_add_blender(0xff, 0xff, 0xff, 0xc0);
    rectfill(dest, x_-w/4, y_, x_+w/4, y_+h-1, c2);
    drawing_mode(DRAW_MODE_SOLID, NULL, 0, 0);
#endif					/* End: NO_FBLEND. */
}

- (int) update
{
    if (h < HUGE_LASER_LENGTH) {
	h += speed;
	y += speed/2;
	return THING_NORMAL;
    }
    else
	return [super update];
}

/* This is a dangerous laser, kill both ally and enemy. */
- (int) damage { return damage; }
- (int) collisionLists { return ALLY_LIST|ACTIVE_AIR_LIST; }
@end
