#include "actor.h"
#include "m_random.h"
#include "a_action.h"
#include "p_local.h"
#include "p_enemy.h"
#include "s_sound.h"
#include "a_strifeglobal.h"
#include "f_finale.h"

static FRandom pr_prog ("Programmer");

void A_SentinelBob (AActor *);
void A_TossGib (AActor *);
void A_Countdown (AActor *);

void A_ProgrammerMelee (AActor *);
void A_SpotLightning (AActor *);
void A_SpawnProgrammerBase (AActor *);
void A_ProgrammerDeath (AActor *);

// Programmer ---------------------------------------------------------------

class AProgrammer : public AActor
{
	DECLARE_ACTOR (AProgrammer, AActor)
public:
	void NoBlockingSet ();
};

FState AProgrammer::States[] =
{
#define S_PROG_STND 0
	S_NORMAL (PRGR, 'A',   5, A_Look,				&States[S_PROG_STND+1]),
	S_NORMAL (PRGR, 'A',   1, A_SentinelBob,		&States[S_PROG_STND]),

#define S_PROG_CHASE (S_PROG_STND+2)
	S_NORMAL (PRGR, 'A', 160, A_SentinelBob,		&States[S_PROG_CHASE+1]),
	S_NORMAL (PRGR, 'B',   5, A_SentinelBob,		&States[S_PROG_CHASE+2]),
	S_NORMAL (PRGR, 'C',   5, A_SentinelBob,		&States[S_PROG_CHASE+3]),
	S_NORMAL (PRGR, 'D',   5, A_SentinelBob,		&States[S_PROG_CHASE+4]),
	S_NORMAL (PRGR, 'E',   2, A_SentinelBob,		&States[S_PROG_CHASE+5]),
	S_NORMAL (PRGR, 'F',   2, A_SentinelBob,		&States[S_PROG_CHASE+6]),
	S_NORMAL (PRGR, 'E',   3, A_Chase,				&States[S_PROG_CHASE+7]),
	S_NORMAL (PRGR, 'F',   3, A_Chase,				&States[S_PROG_CHASE+4]),

#define S_PROG_MELEE (S_PROG_CHASE+8)
	S_NORMAL (PRGR, 'E',   2, A_SentinelBob,		&States[S_PROG_MELEE+1]),
	S_NORMAL (PRGR, 'F',   3, A_SentinelBob,		&States[S_PROG_MELEE+2]),
	S_NORMAL (PRGR, 'E',   3, A_FaceTarget,			&States[S_PROG_MELEE+4]),
	S_NORMAL (PRGR, 'F',   4, A_ProgrammerMelee,	&States[S_PROG_CHASE+4]),

#define S_PROG_MISSILE (S_PROG_MELEE+4)
	S_NORMAL (PRGR, 'G',   5, A_FaceTarget,			&States[S_PROG_MISSILE+1]),
	S_NORMAL (PRGR, 'H',   5, A_SentinelBob,		&States[S_PROG_MISSILE+2]),
	S_BRIGHT (PRGR, 'I',   5, A_FaceTarget,			&States[S_PROG_MISSILE+3]),
	S_BRIGHT (PRGR, 'J',   5, A_SpotLightning,		&States[S_PROG_CHASE+4]),

#define S_PROG_PAIN (S_PROG_MISSILE+4)
	S_NORMAL (PRGR, 'K',   5, A_Pain,				&States[S_PROG_PAIN+1]),
	S_NORMAL (PRGR, 'L',   5, A_SentinelBob,		&States[S_PROG_CHASE+4]),

#define S_PROG_DIE (S_PROG_PAIN+2)
	S_BRIGHT (PRGR, 'L',   7, A_TossGib,			&States[S_PROG_DIE+1]),
	S_BRIGHT (PRGR, 'M',   7, A_Scream,				&States[S_PROG_DIE+2]),
	S_BRIGHT (PRGR, 'N',   7, A_TossGib,			&States[S_PROG_DIE+3]),
	S_BRIGHT (PRGR, 'O',   7, A_NoBlocking,			&States[S_PROG_DIE+4]),
	S_BRIGHT (PRGR, 'P',   7, A_TossGib,			&States[S_PROG_DIE+5]),
	S_BRIGHT (PRGR, 'Q',   7, A_SpawnProgrammerBase,&States[S_PROG_DIE+6]),
	S_BRIGHT (PRGR, 'R',   7, NULL,					&States[S_PROG_DIE+7]),
	S_BRIGHT (PRGR, 'S',   6, NULL,					&States[S_PROG_DIE+8]),
	S_BRIGHT (PRGR, 'T',   5, NULL,					&States[S_PROG_DIE+9]),
	S_BRIGHT (PRGR, 'U',   5, NULL,					&States[S_PROG_DIE+10]),
	S_BRIGHT (PRGR, 'V',   5, NULL,					&States[S_PROG_DIE+11]),
	S_BRIGHT (PRGR, 'W',   5, NULL,					&States[S_PROG_DIE+12]),
	S_BRIGHT (PRGR, 'X',  32, NULL,					&States[S_PROG_DIE+13]),
	S_BRIGHT (PRGR, 'X',  -1, A_ProgrammerDeath,	NULL)
};

IMPLEMENT_ACTOR (AProgrammer, Strife, 71, 0)
	PROP_StrifeType (95)
	PROP_SpawnHealth (1100)
	PROP_SpawnState (S_PROG_STND)
	PROP_SeeState (S_PROG_CHASE)
	PROP_PainState (S_PROG_PAIN)
	PROP_PainChance (50)
	PROP_MeleeState (S_PROG_MELEE)
	PROP_MissileState (S_PROG_MISSILE)
	PROP_DeathState (S_PROG_DIE)
	PROP_SpeedFixed (26)
	PROP_RadiusFixed (45)
	PROP_HeightFixed (60)
	PROP_Mass (800)
	PROP_Damage (4)
	PROP_Flags (MF_SOLID|MF_SHOOTABLE|MF_NOGRAVITY|MF_FLOAT|
				MF_NOBLOOD|MF_COUNTKILL|MF_NOTDMATCH)
	PROP_Flags2 (MF2_PASSMOBJ|MF2_PUSHWALL|MF2_MCROSS)
	PROP_Flags4 (MF4_LOOKALLAROUND|MF4_FIRERESIST|MF4_NOICEDEATH|MF4_NOTARGETSWITCH)
	PROP_MinMissileChance (150)
	PROP_AttackSound ("programmer/attack")
	PROP_PainSound ("programmer/pain")
	PROP_DeathSound ("programmer/death")
	PROP_ActiveSound ("programmer/active")
END_DEFAULTS

//============================================================================
//
// AProgrammer :: NoBlockingSet
//
//============================================================================

void AProgrammer::NoBlockingSet ()
{
	P_DropItem (this, "Sigil1", -1, 256);
}

// The Programmer's base for when he dies -----------------------------------

class AProgrammerBase : public AActor
{
	DECLARE_ACTOR (AProgrammerBase, AActor)
public:
	void GetExplodeParms (int &damage, int &dist, bool &hurtSource);
};

FState AProgrammerBase::States[] =
{
	S_BRIGHT (BASE, 'A',  5, A_Explode,		&States[1]),
	S_BRIGHT (BASE, 'B',  5, NULL,			&States[2]),
	S_BRIGHT (BASE, 'C',  5, NULL,			&States[3]),
	S_BRIGHT (BASE, 'D',  5, NULL,			&States[4]),
	S_NORMAL (BASE, 'E',  5, NULL,			&States[5]),
	S_NORMAL (BASE, 'F',  5, NULL,			&States[6]),
	S_NORMAL (BASE, 'G',  5, NULL,			&States[7]),
	S_NORMAL (BASE, 'H', -1, NULL,			NULL)
};

IMPLEMENT_ACTOR (AProgrammerBase, Strife, -1, 0)
	PROP_StrifeType (96)
	PROP_SpawnState (0)
	PROP_Flags (MF_NOBLOCKMAP|MF_NOCLIP|MF_NOBLOOD)
END_DEFAULTS

//============================================================================
//
// AProgrammerBase :: GetExplodeParms
//
//============================================================================

void AProgrammerBase::GetExplodeParms (int &damage, int &dist, bool &hurtSource)
{
	damage = dist = 32;
}

// The Programmer level ending thing ----------------------------------------
// [RH] I took some liberties to make this "cooler" than it was in Strife.

class AProgLevelEnder : public AInventory
{
	DECLARE_STATELESS_ACTOR (AProgLevelEnder, AInventory)
public:
	void Tick ();
	PalEntry GetBlend ();
};

IMPLEMENT_STATELESS_ACTOR (AProgLevelEnder, Strife, -1, 0)
	PROP_Inventory_FlagsSet (IF_UNDROPPABLE)
END_DEFAULTS

//============================================================================
//
// AProgLevelEnder :: Tick
//
// Fade to black, end the level, then unfade.
//
//============================================================================

void AProgLevelEnder::Tick ()
{
	if (special2 == 0)
	{ // fade out over .66 second
		special1 += 255 / (TICRATE*2/3);
		if (++special1 >= 255)
		{
			special1 = 255;
			special2 = 1;
			G_ExitLevel (0, false);
		}
	}
	else
	{ // fade in over two seconds
		special1 -= 255 / (TICRATE*2);
		if (special1 <= 0)
		{
			Destroy ();
		}
	}
}

//============================================================================
//
// AProgLevelEnder :: GetBlend
//
//============================================================================

PalEntry AProgLevelEnder::GetBlend ()
{
	return PalEntry (special1, 0, 0, 0);
}

//============================================================================
//
// A_ProgrammerMelee
//
//============================================================================

void A_ProgrammerMelee (AActor *self)
{
	int damage;

	if (self->target == NULL)
		return;

	A_FaceTarget (self);

	if (!self->CheckMeleeRange ())
		return;

	S_Sound (self, CHAN_WEAPON, "programmer/clank", 1, ATTN_NORM);

	damage = ((pr_prog() % 10) + 1) * 6;
	P_DamageMobj (self->target, self, self, damage, MOD_HIT);
	P_TraceBleed (damage, self->target, self);
}

//============================================================================
//
// A_SpotLightning
//
//============================================================================

void A_SpotLightning (AActor *self)
{
	AActor *spot;

	if (self->target == NULL)
		return;

	spot = Spawn<ASpectralLightningSpot> (self->target->x, self->target->y, ONFLOORZ);
	if (spot != NULL)
	{
		spot->threshold = 25;
		spot->target = self;
		spot->health = -2;
		spot->tracer = self->target;
	}
}

//============================================================================
//
// A_SpawnProgrammerBase
//
//============================================================================

void A_SpawnProgrammerBase (AActor *self)
{
	AActor *foo = Spawn<AProgrammerBase> (self->x, self->y, self->z + 24*FRACUNIT);
	if (foo != NULL)
	{
		foo->angle = self->angle + ANGLE_180 + (pr_prog.Random2() << 22);
		foo->momx = FixedMul (foo->Speed, finecosine[foo->angle >> ANGLETOFINESHIFT]);
		foo->momy = FixedMul (foo->Speed, finesine[foo->angle >> ANGLETOFINESHIFT]);
		foo->momz = pr_prog() << 9;
	}
}

//============================================================================
//
// A_ProgrammerDeath
//
//============================================================================

void A_ProgrammerDeath (AActor *self)
{
	if (!CheckBossDeath (self))
		return;

	for (int i = 0; i < MAXPLAYERS; ++i)
	{
		if (playeringame[i] && players[i].health > 0)
		{
			players[i].mo->GiveInventoryType (RUNTIME_CLASS(AProgLevelEnder));
			break;
		}
	}
	P_StartScript (self, NULL, 250, NULL, 0, 0, 0, 0, false, false);
}
