/* miniboss1.m,
 *
 * The miniboss for the space level, a wide ship.  Damage bonus for
 * vulcan and toothpaste since its homing ability is nullified.
 *
 * Lots of dirty code because the sprite is normally not rotated, but
 * then in some cases it is so angles are off by 90 degrees...
 */

#include <allegro.h>
#include <math.h>
#include "common.h"
#include "debris/explosion.h"
#include "debris/large-chunk.h"
#include "debris/smoke.h"
#include "map.h"
#include "projectiles/evil-vulcan.h"
#include "projectiles/rocket.h"
#include "rotate.h"
#include "seborrhea/seborrhea-allegro.h"
#include "seborrhea/seborrhea.h"
#include "units/all-units.h"
#include "units/basic-turret.h"
#include "units/miniboss1.h"
#include "units/hunter-seeker.h"


#define BODY_HEALTH	900
#define MOVE_PERIOD	100


static SebFile *boss_sebum;
static SebAnimation *body_anim, *thrust_anim;

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

@interface AbstractVulcan: BasicTurret
@end

@interface LeftVulcan: AbstractVulcan
@end

@interface RightVulcan: AbstractVulcan
@end

@interface MiniBoss1Incoming: Debri
@end

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

@implementation MiniBoss1
+ (BOOL) loadPrerequisites 
{
    return (LOAD_UNIT_DATA_FOR(HunterSeeker, YES) &&
	    LOAD_PROJECTILE_DATA_FOR(EvilVulcan) &&
	    LOAD_PROJECTILE_DATA_FOR(Rocket));
}

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

    body_anim = (SebAnimation *)[boss_sebum getSebumByName:"body"];
    thrust_anim = (SebAnimation *)[boss_sebum getSebumByName:"thrust"];
    return YES;
}

+ (void) shutdown
{
    FREE_SEBFILE(boss_sebum);
    body_anim = nil;
    thrust_anim = nil;
}

- init
{
    [super init];

    health = BODY_HEALTH;

    phase_tics = 2.25*MOVE_PERIOD;
    sign = -1;				/* CW. */

    animation.anim = body_anim;
    animation.tics = animation.delay = 10;
    animation.loop = YES;
    
    sprite = [body_anim getFrame:0];
    shadow = [boss_sebum getSebumByName:"shadow"];
    w = [sprite width] - 30;
    h = 90;

    omega = deg2rad(1.5);
    flags = FLAG_INVINCIBLE;	/* Don't move/shoot yet. */
    chunk_colours = CHUNK_RED;

    activate_y = screen_h;	/* Preactivate */

    return self;
}

- (void) die
{
    if (lgun) { [lgun die]; lgun = nil; }
    if (rgun) { [rgun die]; rgun = nil; }
    [super die];
}

- (BOOL) readyToActivate
{
    if ([super readyToActivate]) {
	/* Show a boss coming in from behind.
	   yy = screen_h*2 + (height of self + incoming pic)/2 + SHADOW. */
	int yy = screen_h*2 + (110 + 55)/2 + SHADOW_DISPLACEMENT;
	spawn_debris([MiniBoss1Incoming class], x, y+yy, SUBTERRANIAN_LAYER);

	angle = 0;
	lgun = spawn_unit([LeftVulcan  class], x, y, ACTIVE_AIR_LIST, NO);
	rgun = spawn_unit([RightVulcan class], x, y, ACTIVE_AIR_LIST, NO);
	return YES;
    }
    else
	return NO;
}

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

    if (flags & FLAG_DYING)
	return;

    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);
}

- (void) enableMovement
{
    /* Special condition: This unit pre-activates to spawn a picture
       of the boss travelling upwards, but we want the unit to remain
       in place until the player reaches it. */
    int sprite_h_2 = [sprite height] / 2;

    if (y + sprite_h_2 >= offsetY) {
	flags &=~FLAG_INVINCIBLE;
        flags |= FLAG_MOVING_ENABLED|FLAG_FIRING_ENABLED;
	boss = self;
    }
}

- (void) moveSwayPhase1
{
    /* Sway backwards. */
    sway_tics++;
    x += 1.5 * cos(deg2rad(360.0 * sway_tics/MOVE_PERIOD));

    if (y > offsetY + 50)
	y -= 1.05;

    /* Don't charge if no one to charge at! */
    if (not (game_flags & FLAG_PLAYERS_ALIVE))
	return;

    if (phase_tics <= 0) {		/* End of phase. */
	boss_phase++;
	phase_tics = 25;
	sway_tics = 0;
	animation.anim = thrust_anim;
	animation.tics = animation.delay;
	animation.frame = 0;
    }
}

- (void) moveChargePhase1
{
    /* Thust!  We need more thrust! */
    speed = MIN((25-phase_tics) * 0.5, 8.0);
    y += speed;
	    
    /* Smoke for afterburners. */
    spawn_debris([Smoke class], x-70, y-55, MEDIUM_LAYER);
    spawn_debris([Smoke class], x-43, y-55, MEDIUM_LAYER);
    spawn_debris([Smoke class], x+43, y-55, MEDIUM_LAYER);
    spawn_debris([Smoke class], x+70, y-55, MEDIUM_LAYER);

    if (phase_tics <= 0) {		/* End of phase. */
	boss_phase++;
	phase_tics = 1.5*MOVE_PERIOD;
	animation.anim = body_anim;
	animation.tics = animation.delay;
	animation.frame = 0;
	rotatable_unit = YES;
    }
}

- (void) moveSwayPhase2
{
    /* Sway backwards. */
    sway_tics++;
    angle += sign * deg2rad(15.0) / (2.0*MOVE_PERIOD);
    x += 1.5 * cos(deg2rad(360.0 * sway_tics/MOVE_PERIOD)) * cos(angle);
    y -= 1.5 * sin(deg2rad(360.0 * sway_tics/MOVE_PERIOD)) + 0.8;

    if (phase_tics <= 0) {		/* End of phase. */
	boss_phase++;
	phase_tics = 20;
	sway_tics = 0;
	animation.anim = thrust_anim;
	animation.tics = animation.delay;
	animation.frame = 0;
    }
}

- (void) moveChargePhase2
{
    /* Thrust. */
    speed = MIN((20-phase_tics) * 0.5, 8.0);
    x += speed * sin(angle);		/* sin is correct. */
    y += speed * cos(angle);		/* cos is correct. */

    /* Smoke for after burners.  Not mathematically correct, but
       prolly good enough. */
    spawn_debris([Smoke class], x-70, y-55, MEDIUM_LAYER);
    spawn_debris([Smoke class], x-43, y-55, MEDIUM_LAYER);
    spawn_debris([Smoke class], x+43, y-55, MEDIUM_LAYER);
    spawn_debris([Smoke class], x+70, y-55, MEDIUM_LAYER);

    if (phase_tics <= 0) {		/* End of phase. */
	boss_phase++;
	phase_tics = 9999999;		/* infinitely long. */
	animation.anim = body_anim;
	animation.tics = animation.delay;
	animation.frame = 0;
	rotatable_unit = YES;
	flags &=~FLAG_FIRING_ENABLED;
    }
}

- (void) moveRetreatPhase
{
    x += speed * sin(angle);		/* sin is correct. */
    y += speed * cos(angle);		/* cos is correct. */

    if (boss_phase == MINIBOSS1_RETREAT1) {
	speed *= 0.95;
	angle += sign * deg2rad(3.5);

	if (ABS(angle) > deg2rad(160.0)) {
	    speed = 3.6;
	    boss_phase++;
	    omega = sign * deg2rad(3.5);
	}
    }
    elif (boss_phase == MINIBOSS1_RETREAT2) {
	if (flags & FLAG_DYING) {	/* Die. */
	    speed = 0.0;

	    /* Head towards middle of screen. */
	    y -= 3.0;
	    x -= sign*1.5;

	    omega += sign * deg2rad(0.1);
	    angle += omega;
	    health--;
	}
	else {				/* Continue. */
	    angle += sign * deg2rad(0.3);
	    if (y < offsetY+50)
		boss_phase++;
	}
    }
    elif (boss_phase == MINIBOSS1_RETREAT3) {
	speed = 2.5;
	angle += sign * deg2rad(3.5);

	if (ABS(angle) > deg2rad(360.0)) {
	    angle = 0.0;
	    speed = 0.0;
	    boss_phase++;
	    rotatable_unit = NO;
	}
    }
    else {
	y -= 1.05;

	if (ABS(x - screen_w/2.0) < 1.5) {
	    x = screen_w/2.0;
	    boss_phase = MINIBOSS1_SWAY1;
	    flags |= FLAG_FIRING_ENABLED;

	    if (sign < 0) {
		phase_tics = 1.75*MOVE_PERIOD;
		sign = 1;
	    }
	    else {
		phase_tics = 2.25*MOVE_PERIOD;
		sign = -1;
	    }
	}
	elif (x < screen_w/2.0)
	    x += 1.5;
	else
	    x -= 1.5;
    }
}

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

    return [super update];
}

- (void) move
{
    /* Phase 1: Collect underwear. */
    if (boss_phase == MINIBOSS1_SWAY1)
	[self moveSwayPhase1];
    elif (boss_phase == MINIBOSS1_CHARGE1)
	[self moveChargePhase1];

    /* Phase 2: */
    elif (boss_phase == MINIBOSS1_SWAY2)
	[self moveSwayPhase2];
    elif (boss_phase == MINIBOSS1_CHARGE2)
	[self moveChargePhase2];

    /* Phase 3: Profit! */
    else
	[self moveRetreatPhase];

    phase_tics--;

    /* Place the children in the right place. */
    if (lgun) [[lgun setX:x Y:y] setAngle:angle];
    if (rgun) [[rgun setX:x Y:y] setAngle:angle];
}

- (void) fire
{
    fire_tics--;
    hunter_tics--;

    /* Left/right wing's rocket(s). */
    if (fire_tics <= 0) {
	fire_tics = 50;
	[spawn_projectile([Rocket class], x-60, y, NO) setAngle:deg2rad(-100.0)];
    }
    if (fire_tics == 10)
	[spawn_projectile([Rocket class], x+60, y, NO) setAngle:deg2rad(-80.0)];

    /* Left/right wing's hunter seekers. */
    if (hunter_tics <= 0) {
	hunter_tics = 80;
	[spawn_unit([HunterSeeker class], x-w/2+10, y, ACTIVE_AIR_LIST, YES) setAngle:deg2rad(-135.0)];
    }
    if (hunter_tics == 30)
	[spawn_unit([HunterSeeker class], x+w/2-10, y, ACTIVE_AIR_LIST, YES) setAngle:deg2rad(-45.0)];
}

- (void) spawnDyingExplosions
{
    if (rnd(health, 0) < -20)
	spawn_debris([LargeChunkRed class], x, y, HIGH_LAYER);
    if (health > -2.5*SECONDS) {
	double xx, yy;
	rotate(rnd(-w, w)/2, rnd(-h, h)/2, angle, &xx, &yy);
	spawn_debris([BigExplosion class], x-xx, y+yy, HIGH_LAYER);

	play_explosion_sample(x);
    }
    else {
	int i;
	double xx, yy;

	for (i = 0; i < 20; i++) {
	    rotate(rnd(-w, w)/2, rnd(-h, h)/2, angle, &xx, &yy);
	    spawn_debris([BigExplosion class], x-xx, y+yy, HIGH_LAYER);
	    spawn_debris([LargeChunkRed class], x, y, HIGH_LAYER);
	}

	if (boss == self)
	    boss = nil;

        [self delete];
    }

    flash_tics += 2;
}

- (BOOL) collidesWith:(Thing<DetectsCollision> *)object
{
    if (rotatable_unit)
	return [super collidesWithRotatedUnit:object];
    else
	return [super collidesWith:object];
}

- (int) receiveDamage:(int)damage type:(enum DAMAGE_TYPE)type
{
    if (type == DAMAGE_TYPE_UNIT)
	return 0;
    if (type == DAMAGE_TYPE_VULCAN ||
	type == DAMAGE_TYPE_TOOTHPASTE) {
	/* 50/25% bonus damage for vulcan/toothpaste.*/
	bonus_damage += damage;
	if (bonus_damage > 4) {
	    bonus_damage -= 4;
	    damage++;
	}
    }

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

- (void) drawMapEditorExtras:(BITMAP *)dest
{
    {					/* Vulcan turrets. */
	BITMAP *spr = [(SebImageAllegro *)[boss_sebum getSebumByName:"turret"] bitmap];
	pivot_sprite(dest, spr, x-42-offsetX, y+4-offsetY, 10, 10, rad2fix(-M_PI_2));
	pivot_sprite(dest, spr, x+43-offsetX, y+4-offsetY, 10, 10, rad2fix(-M_PI_2));
    }

    [super drawMapEditorExtras:dest];
}

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

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

@implementation AbstractVulcan
- init
{
    [super init];

    sprite = nil;
    turret = [boss_sebum getSebumByName:"turret"];

    gun = [[[[PulseWeapon new]
		setProjectile:[EvilVulcan class]]
	       setShotDelay:8 WaveDelay:50]
	      setShotsPerWave:4];

    flags |= FLAG_INVINCIBLE;
    chunk_colours = CHUNK_RED;

    return self;
}

- (int) collisionLists { return 0; }
- derive_alwaysInfluenceGameplay;
@end

@implementation LeftVulcan
- (id) setAngle:(double)theta
{
    double xx, yy;
    rotate(-42, -4, theta, &xx, &yy);
    x += xx;
    y -= yy;
    return self;
}
@end

@implementation RightVulcan
- (id) setAngle:(double)theta
{
    double xx, yy;
    rotate( 43, -4, theta, &xx, &yy);
    x += xx;
    y -= yy;
    return self;
}
@end

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

@implementation MiniBoss1Incoming
- init
{
    [super init];

    sprite = [boss_sebum getSebumByName:"body-incoming"];
    w = [sprite width];
    h = [sprite height];

    angle = deg2rad(90.0);
    speed = 2.2;

    return self;
}
@end
