#include "actor.h"
#include "info.h"
#include "p_local.h"
#include "m_random.h"
#include "s_sound.h"
#include "p_enemy.h"
#include "a_hexenglobal.h"
#include "gstrings.h"

static FRandom pr_mstafftrack ("MStaffTrack");
static FRandom pr_bloodscourgedrop ("BloodScourgeDrop");

void A_MStaffTrack (AActor *);
void A_DropBloodscourgePieces (AActor *);
void A_MStaffAttack (AActor *actor);
void A_MStaffPalette (AActor *actor);

static AActor *FrontBlockCheck (AActor *mo, int index);
static divline_t BlockCheckLine;

//==========================================================================

class AMageWeaponPiece : public AFourthWeaponPiece
{
	DECLARE_STATELESS_ACTOR (AMageWeaponPiece, AFourthWeaponPiece)
public:
	void BeginPlay ();
protected:
	bool MatchPlayerClass (AActor *toucher);
	const char *PieceMessage ();
};

IMPLEMENT_ABSTRACT_ACTOR (AMageWeaponPiece)

const char *AMageWeaponPiece::PieceMessage ()
{
	return GStrings("TXT_BLOODSCOURGE_PIECE");
}

bool AMageWeaponPiece::MatchPlayerClass (AActor *toucher)
{
	return !toucher->IsKindOf (RUNTIME_CLASS(AFighterPlayer)) &&
		   !toucher->IsKindOf (RUNTIME_CLASS(AClericPlayer));
}

//==========================================================================

class AMWeaponPiece1 : public AMageWeaponPiece
{
	DECLARE_ACTOR (AMWeaponPiece1, AMageWeaponPiece)
public:
	void BeginPlay ();
};

FState AMWeaponPiece1::States[] =
{
	S_BRIGHT (WMS1, 'A',   -1, NULL					    , NULL),
};

IMPLEMENT_ACTOR (AMWeaponPiece1, Hexen, 21, 37)
	PROP_Flags (MF_SPECIAL)
	PROP_Flags2 (MF2_FLOATBOB)
	PROP_SpawnState (0)
END_DEFAULTS

void AMWeaponPiece1::BeginPlay ()
{
	Super::BeginPlay ();
	PieceValue = WPIECE1<<6;
}

//==========================================================================

class AMWeaponPiece2 : public AMageWeaponPiece
{
	DECLARE_ACTOR (AMWeaponPiece2, AMageWeaponPiece)
public:
	void BeginPlay ();
};

FState AMWeaponPiece2::States[] =
{
	S_BRIGHT (WMS2, 'A',   -1, NULL					    , NULL),
};

IMPLEMENT_ACTOR (AMWeaponPiece2, Hexen, 22, 38)
	PROP_Flags (MF_SPECIAL)
	PROP_Flags2 (MF2_FLOATBOB)
	PROP_SpawnState (0)
END_DEFAULTS

void AMWeaponPiece2::BeginPlay ()
{
	Super::BeginPlay ();
	PieceValue = WPIECE2<<6;
}

//==========================================================================

class AMWeaponPiece3 : public AMageWeaponPiece
{
	DECLARE_ACTOR (AMWeaponPiece3, AMageWeaponPiece)
public:
	void BeginPlay ();
};

FState AMWeaponPiece3::States[] =
{
	S_BRIGHT (WMS3, 'A',   -1, NULL					    , NULL),
};

IMPLEMENT_ACTOR (AMWeaponPiece3, Hexen, 23, 39)
	PROP_Flags (MF_SPECIAL)
	PROP_Flags2 (MF2_FLOATBOB)
	PROP_SpawnState (0)
END_DEFAULTS

void AMWeaponPiece3::BeginPlay ()
{
	Super::BeginPlay ();
	PieceValue = WPIECE3<<6;
}

// An actor that spawns the three pieces of the mage's fourth weapon --------

// This gets spawned if weapon drop is on so that other players can pick up
// this player's weapon.

class ABloodscourgeDrop : public AActor
{
	DECLARE_ACTOR (ABloodscourgeDrop, AActor)
};

FState ABloodscourgeDrop::States[] =
{
	S_NORMAL (TNT1, 'A', 1, NULL, &States[1]),
	S_NORMAL (TNT1, 'A', 1, A_DropBloodscourgePieces, NULL)
};

IMPLEMENT_ACTOR (ABloodscourgeDrop, Hexen, -1, 0)
	PROP_SpawnState (0)
END_DEFAULTS

// The Mages's Staff (Bloodscourge) -----------------------------------------

class AMWeapBloodscourge : public AMageWeapon
{
	DECLARE_ACTOR (AMWeapBloodscourge, AMageWeapon)
public:
	void Serialize (FArchive &arc)
	{
		Super::Serialize (arc);
		arc << MStaffCount;
	}
	const char *PickupMessage ()
	{
		return GStrings("TXT_WEAPON_M4");
	}
	PalEntry GetBlend ()
	{
		return PalEntry (MStaffCount * 128 / 3, 151, 110, 0);
	}
	BYTE MStaffCount;
};

FState AMWeapBloodscourge::States[] =
{
	// Dummy state, because the fourth weapon does not appear in a level directly.
	S_NORMAL (TNT1, 'A',   -1, NULL					    , NULL),

#define S_MSTAFFREADY 1
	S_NORMAL (MSTF, 'A',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+1]),
	S_NORMAL (MSTF, 'A',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+2]),
	S_NORMAL (MSTF, 'A',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+3]),
	S_NORMAL (MSTF, 'A',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+4]),
	S_NORMAL (MSTF, 'A',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+5]),
	S_NORMAL (MSTF, 'A',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+6]),
	S_NORMAL (MSTF, 'B',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+7]),
	S_NORMAL (MSTF, 'B',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+8]),
	S_NORMAL (MSTF, 'B',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+9]),
	S_NORMAL (MSTF, 'B',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+10]),
	S_NORMAL (MSTF, 'B',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+11]),
	S_NORMAL (MSTF, 'B',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+12]),
	S_NORMAL (MSTF, 'C',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+13]),
	S_NORMAL (MSTF, 'C',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+14]),
	S_NORMAL (MSTF, 'C',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+15]),
	S_NORMAL (MSTF, 'C',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+16]),
	S_NORMAL (MSTF, 'C',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+17]),
	S_NORMAL (MSTF, 'C',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+18]),
	S_NORMAL (MSTF, 'D',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+19]),
	S_NORMAL (MSTF, 'D',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+20]),
	S_NORMAL (MSTF, 'D',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+21]),
	S_NORMAL (MSTF, 'D',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+22]),
	S_NORMAL (MSTF, 'D',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+23]),
	S_NORMAL (MSTF, 'D',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+24]),
	S_NORMAL (MSTF, 'E',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+25]),
	S_NORMAL (MSTF, 'E',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+26]),
	S_NORMAL (MSTF, 'E',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+27]),
	S_NORMAL (MSTF, 'E',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+28]),
	S_NORMAL (MSTF, 'E',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+29]),
	S_NORMAL (MSTF, 'E',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+30]),
	S_NORMAL (MSTF, 'F',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+31]),
	S_NORMAL (MSTF, 'F',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+32]),
	S_NORMAL (MSTF, 'F',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+33]),
	S_NORMAL (MSTF, 'F',	1, A_WeaponReady		    , &States[S_MSTAFFREADY+34]),
	S_NORMAL (MSTF, 'F',	1, A_WeaponReady		    , &States[S_MSTAFFREADY]),

#define S_MSTAFFDOWN (S_MSTAFFREADY+35)
	S_NORMAL (MSTF, 'A',	1, A_Lower				    , &States[S_MSTAFFDOWN]),

#define S_MSTAFFUP (S_MSTAFFDOWN+1)
	S_NORMAL (MSTF, 'A',	1, A_Raise				    , &States[S_MSTAFFUP]),

#define S_MSTAFFATK (S_MSTAFFUP+1)
	S_NORMAL2 (MSTF, 'G',	4, NULL					    , &States[S_MSTAFFATK+1], 0, 40),
	S_BRIGHT2 (MSTF, 'H',	4, A_MStaffAttack		    , &States[S_MSTAFFATK+2], 0, 48),
	S_BRIGHT2 (MSTF, 'H',	2, A_MStaffPalette		    , &States[S_MSTAFFATK+3], 0, 48),
	S_NORMAL2 (MSTF, 'I',	2, A_MStaffPalette		    , &States[S_MSTAFFATK+4], 0, 48),
	S_NORMAL2 (MSTF, 'I',	2, A_MStaffPalette		    , &States[S_MSTAFFATK+5], 0, 48),
	S_NORMAL2 (MSTF, 'I',	1, NULL					    , &States[S_MSTAFFATK+6], 0, 40),
	S_NORMAL2 (MSTF, 'J',	5, NULL					    , &States[S_MSTAFFREADY], 0, 36)
};

IMPLEMENT_ACTOR (AMWeapBloodscourge, Hexen, -1, 0)
	PROP_Flags (MF_SPECIAL)
	PROP_SpawnState (0)

	PROP_Weapon_SelectionOrder (3100)
	PROP_Weapon_Flags (WIF_PRIMARY_USES_BOTH | WIF_EXTREME_DEATH)
	PROP_Weapon_AmmoUse1 (15)
	PROP_Weapon_AmmoUse2 (15)
	PROP_Weapon_UpState (S_MSTAFFUP)
	PROP_Weapon_DownState (S_MSTAFFDOWN)
	PROP_Weapon_ReadyState (S_MSTAFFREADY)
	PROP_Weapon_AtkState (S_MSTAFFATK)
	PROP_Weapon_HoldAtkState (S_MSTAFFATK)
	PROP_Weapon_Kickback (150)
	PROP_Weapon_YAdjust (20)
	PROP_Weapon_MoveCombatDist (20000000)
	PROP_Weapon_AmmoType1 ("Mana1")
	PROP_Weapon_AmmoType2 ("Mana2")
	PROP_Weapon_ProjectileType ("MageStaffFX2")
END_DEFAULTS

// Mage Staff FX2 (Bloodscourge) --------------------------------------------

void A_BeAdditive (AActor *self)
{
	self->RenderStyle = STYLE_Add;
}

class AMageStaffFX2 : public AActor
{
	DECLARE_ACTOR (AMageStaffFX2, AActor)
public:
	void GetExplodeParms (int &damage, int &dist, bool &hurtSource);
	int SpecialMissileHit (AActor *victim);
	bool IsOkayToAttack (AActor *link);
	bool SpecialBlastHandling (AActor *source, fixed_t strength);
};

FState AMageStaffFX2::States[] =
{
#define S_MSTAFF_FX2_1 0
	S_BRIGHT (MSP2, 'A',	2, A_MStaffTrack		    , &States[S_MSTAFF_FX2_1+1]),
	S_BRIGHT (MSP2, 'B',	2, A_MStaffTrack		    , &States[S_MSTAFF_FX2_1+2]),
	S_BRIGHT (MSP2, 'C',	2, A_MStaffTrack		    , &States[S_MSTAFF_FX2_1+3]),
	S_BRIGHT (MSP2, 'D',	2, A_MStaffTrack		    , &States[S_MSTAFF_FX2_1]),

#define S_MSTAFF_FX2_X1 (S_MSTAFF_FX2_1+4)
	S_BRIGHT (MSP2, 'E',	4, A_BeAdditive			    , &States[S_MSTAFF_FX2_X1+1]),
	S_BRIGHT (MSP2, 'F',	5, A_Explode			    , &States[S_MSTAFF_FX2_X1+2]),
	S_BRIGHT (MSP2, 'G',	5, NULL					    , &States[S_MSTAFF_FX2_X1+3]),
	S_BRIGHT (MSP2, 'H',	5, NULL					    , &States[S_MSTAFF_FX2_X1+4]),
	S_BRIGHT (MSP2, 'I',	4, NULL					    , NULL),
};

IMPLEMENT_ACTOR (AMageStaffFX2, Hexen, -1, 0)
	PROP_SpeedFixed (17)
	PROP_HeightFixed (8)
	PROP_Damage (4)
	PROP_DamageType (MOD_FIRE)
	PROP_Flags (MF_NOBLOCKMAP|MF_NOGRAVITY|MF_DROPOFF|MF_MISSILE)
	PROP_Flags2 (MF2_NOTELEPORT|MF2_IMPACT|MF2_PCROSS|MF2_SEEKERMISSILE)

	PROP_SpawnState (S_MSTAFF_FX2_1)
	PROP_DeathState (S_MSTAFF_FX2_X1)

	PROP_DeathSound ("MageStaffExplode")
END_DEFAULTS

void AMageStaffFX2::GetExplodeParms (int &damage, int &dist, bool &hurtSource)
{
	damage = 80;
	dist = 192;
	hurtSource = false;
}

int AMageStaffFX2::SpecialMissileHit (AActor *victim)
{
	if (victim != target &&
		!victim->player &&
		!(victim->flags2 & MF2_BOSS))
	{
		P_DamageMobj (victim, this, target, 10, MOD_FIRE);
		return 1;	// Keep going
	}
	return -1;
}

bool AMageStaffFX2::IsOkayToAttack (AActor *link)
{
	if (((link->flags3&MF3_ISMONSTER) || link->player)
		&& !(link->flags2&MF2_DORMANT))
	{
		if (!(link->flags&MF_SHOOTABLE))
		{
			return false;
		}
		if (multiplayer && !deathmatch && link->player && target->player)
		{
			return false;
		}
		if (link == target)
		{
			return false;
		}
		else if (P_CheckSight (this, link))
		{
			AActor *master = target;
			angle = R_PointToAngle2 (master->x, master->y,
							link->x, link->y) - master->angle;
			angle >>= 24;
			if (angle>226 || angle<30)
			{
				return true;
			}
		}
	}
	return false;
}

bool AMageStaffFX2::SpecialBlastHandling (AActor *source, fixed_t strength)
{
	// Reflect to originator
	tracer = target;	
	target = source;
	return true;
}

//============================================================================

//============================================================================
//
// MStaffSpawn2 - for use by mage class boss
//
//============================================================================

void MStaffSpawn2 (AActor *actor, angle_t angle)
{
	AActor *mo;

	mo = P_SpawnMissileAngleZ (actor, actor->z+40*FRACUNIT,
		RUNTIME_CLASS(AMageStaffFX2), angle, 0);
	if (mo)
	{
		mo->target = actor;
		mo->tracer = P_BlockmapSearch (mo, 10, FrontBlockCheck);
	}
}

//============================================================================
//
// A_MStaffAttack2 - for use by mage class boss
//
//============================================================================

void A_MStaffAttack2 (AActor *actor)
{
	angle_t angle;
	angle = actor->angle;
	MStaffSpawn2 (actor, angle);
	MStaffSpawn2 (actor, angle-ANGLE_1*5);
	MStaffSpawn2 (actor, angle+ANGLE_1*5);
	S_Sound (actor, CHAN_WEAPON, "MageStaffFire", 1, ATTN_NORM);
}

//============================================================================
//
// MStaffSpawn
//
//============================================================================

void MStaffSpawn (AActor *pmo, angle_t angle)
{
	AActor *mo;

	mo = P_SpawnPlayerMissile (pmo, pmo->x, pmo->y, pmo->z+8*FRACUNIT,
		RUNTIME_CLASS(AMageStaffFX2), angle);
	if (mo)
	{
		mo->target = pmo;
		mo->tracer = linetarget;
	}
}

//============================================================================
//
// A_MStaffAttack
//
//============================================================================

void A_MStaffAttack (AActor *actor)
{
	angle_t angle;
	player_t *player;

	if (NULL == (player = actor->player))
	{
		return;
	}

	AMWeapBloodscourge *weapon = static_cast<AMWeapBloodscourge *> (actor->player->ReadyWeapon);
	if (weapon != NULL)
	{
		if (!weapon->DepleteAmmo (weapon->bAltFire))
			return;
	}
	angle = actor->angle;
	
	// [RH] Let's try and actually track what the player aimed at
	P_AimLineAttack (actor, angle, PLAYERMISSILERANGE, ANGLE_1*32);
	if (linetarget == NULL)
	{
		BlockCheckLine.x = actor->x;
		BlockCheckLine.y = actor->y;
		BlockCheckLine.dx = -finesine[angle >> ANGLETOFINESHIFT];
		BlockCheckLine.dy = -finecosine[angle >> ANGLETOFINESHIFT];
		linetarget = P_BlockmapSearch (actor, 10, FrontBlockCheck);
	}
	MStaffSpawn (actor, angle);
	MStaffSpawn (actor, angle-ANGLE_1*5);
	MStaffSpawn (actor, angle+ANGLE_1*5);
	S_Sound (actor, CHAN_WEAPON, "MageStaffFire", 1, ATTN_NORM);
	weapon->MStaffCount = 3;
}

//============================================================================
//
// A_MStaffPalette
//
//============================================================================

void A_MStaffPalette (AActor *actor)
{
	if (actor->player != NULL)
	{
		AMWeapBloodscourge *weapon = static_cast<AMWeapBloodscourge *> (actor->player->ReadyWeapon);
		if (weapon != NULL && weapon->MStaffCount != 0)
		{
			weapon->MStaffCount--;
		}
	}
}

//============================================================================
//
// A_MStaffTrack
//
//============================================================================

void A_MStaffTrack (AActor *actor)
{
	if ((actor->tracer == 0) && (pr_mstafftrack()<50))
	{
		actor->tracer = P_RoughMonsterSearch (actor, 10);
	}
	P_SeekerMissile (actor, ANGLE_1*2, ANGLE_1*10);
}

//============================================================================
//
// A_DropBloodscourgePieces
//
//============================================================================

void A_DropBloodscourgePieces (AActor *actor)
{
	static const TypeInfo *pieces[3] =
	{
		RUNTIME_CLASS(AMWeaponPiece1),
		RUNTIME_CLASS(AMWeaponPiece2),
		RUNTIME_CLASS(AMWeaponPiece3)
	};

	for (int i = 0, j = 0, fineang = 0; i < 3; ++i)
	{
		AActor *piece = Spawn (pieces[j], actor->x, actor->y, actor->z);
		if (piece != NULL)
		{
			piece->momx = actor->momx + finecosine[fineang];
			piece->momy = actor->momy + finesine[fineang];
			piece->momz = actor->momz;
			piece->flags |= MF_DROPPED;
			fineang += FINEANGLES/3;
			j = (j == 0) ? (pr_bloodscourgedrop() & 1) + 1 : 3-j;
		}
	}
}

void AMageWeaponPiece::BeginPlay ()
{
	Super::BeginPlay ();
	FourthWeaponClass = RUNTIME_CLASS(AMWeapBloodscourge);
}

//============================================================================
//
// FrontBlockCheck
//
// [RH] Like RoughBlockCheck, but it won't return anything behind a line.
//
//============================================================================

static AActor *FrontBlockCheck (AActor *mo, int index)
{
	FBlockNode *link;

	for (link = blocklinks[index]; link != NULL; link = link->NextActor)
	{
		if (link->Me != mo)
		{
			if (P_PointOnDivlineSide (link->Me->x, link->Me->y, &BlockCheckLine) == 0 &&
				mo->IsOkayToAttack (link->Me))
			{
				return link->Me;
			}
		}
	}
	return NULL;
}
