#include "jx_inc_magic_info"

//========================================== Caster Class Search ==========================================//

// Get the main caster class, depending on the spell type
// - oCreature Creature from which to get the main caster class
// - iSpellType JX_SPELLTYPE_* constant
// * Returns a CLASS_TYPE_* constant, or -1 if not found
int JXGetMainCasterClass(object oCreature = OBJECT_SELF, int iSpellType = JX_SPELLTYPE_BOTH);

// Get the creature's class that is associated with the specified spell
// - iSpellId SPELL_* constant
// - oCreature Creature on which the class search is performed
// * Returns a CLASS_TYPE_* constant, or -1 if it is not found
int JXGetClassForSpell(int iSpellId, object oCreature = OBJECT_SELF);

//========================================== Class-related Caster Level ==========================================//

// Get the arcane caster level for a creature based on one of her class
// - oCreature Creature from which to get the arcane caster level
// - iClass CLASS_TYPE_* constant (CLASS_TYPE_INVALID to use the main arcane class)
// * Returns the creature's arcane caster level
int JXGetCreatureArcaneCasterLevel(object oCreature = OBJECT_SELF, int iClass = CLASS_TYPE_INVALID);

// Get the divine caster level for a creature based on one of her class
// - oCreature Creature from which to get the divine caster level
// - iClass CLASS_TYPE_* constant (CLASS_TYPE_INVALID to use the main divine class)
// * Returns the creature's divine caster level
int JXGetCreatureDivineCasterLevel(object oCreature = OBJECT_SELF, int iClass = CLASS_TYPE_INVALID);

// Get the caster level for a creature based on one of her class
// - oCreature Creature from which to get the caster level
// - iClass CLASS_TYPE_* constant (CLASS_TYPE_INVALID to use the main caster class)
// * Returns the creature's caster level
int JXGetCreatureCasterLevel(object oCreature = OBJECT_SELF, int iClass = CLASS_TYPE_INVALID);

// Get the caster level for a creature depending on a spell
// - oCreature Creature from which to get the caster level
// - iClass CLASS_TYPE_* constant (CLASS_TYPE_INVALID to use the main caster class)
// * Returns the creature's caster level
int JXGetCreatureCasterLevelForSpell(int iSpellId, object oCreature = OBJECT_SELF, int iClass = CLASS_TYPE_INVALID);

// Get the save DC for the specified spell cast by a creature
// - iSpellId SPELL_* constant
// - oCreature Creature from which to get the spell save DC
// - iClass CLASS_TYPE_* constant (CLASS_TYPE_INVALID to use the best caster class able to cast the spell)
// * Returns the creature's spell save DC
int JXGetCreatureSpellSaveDC(int iSpellId, object oCreature = OBJECT_SELF, int iClass = CLASS_TYPE_INVALID);


















//========================================== Private functions ==========================================//

// Private function - Get the caster level associated with one of the main classes
int JXPrivateGetComputedCLFromClass(int iClass, int iClassLevel)
{
	if ((iClass == CLASS_TYPE_BARD)
	 || (iClass == CLASS_TYPE_SORCERER)
	 || (iClass == CLASS_TYPE_WIZARD)
	 || (iClass == CLASS_TYPE_WARLOCK)
	 || (iClass == CLASS_TYPE_ASSASSIN)
     || (iClass == CLASS_TYPE_CLERIC)
     || (iClass == CLASS_TYPE_DRUID)
     || (iClass == CLASS_TYPE_BLACKGUARD)
     || (iClass == CLASS_TYPE_SPIRIT_SHAMAN)
     || (iClass == CLASS_TYPE_FAVORED_SOUL))
		return iClassLevel;

    if ((iClass == CLASS_TYPE_PALADIN)
     || (iClass == CLASS_TYPE_RANGER))
		return iClassLevel / 2;

	return 0;
}

// Private function - Get the improved arcane casting levels due to prestige classes
int JXPrivateGetImprovedArcaneCLFromClasses(object oCreature, int iMainClass)
{
	int iCasterLevel;

	// No class can improve the Assassin caster level
	if (iMainClass == CLASS_TYPE_ASSASSIN)
		return 0;

	int iClass, iClassLevel;
	int iLoop;
	for (iLoop = 1; iLoop <= 4; iLoop++)
	{
		iClass = GetClassByPosition(iLoop, oCreature);
		iClassLevel = GetLevelByPosition(iLoop, oCreature);

		// Get the Arcane Scholar of Candlekeep caster level
		if (iClass == CLASS_TYPE_ARCANE_SCHOLAR)
			iCasterLevel += iClassLevel;

		// Get the Arcane Trickster caster level
		if (iClass == CLASS_TYPE_ARCANETRICKSTER)
			iCasterLevel += iClassLevel;

		// Get the Eldritch Knight caster level
		if (iClass == CLASS_TYPE_ELDRITCH_KNIGHT)
			iCasterLevel += iClassLevel - 1;

		// Get the Harper Agent caster level
		if (iClass == CLASS_TYPE_HARPER)
			iCasterLevel += iClassLevel - 1;

		// Get the Palemaster caster level
		if (iClass == CLASS_TYPE_PALEMASTER)
			iCasterLevel += (iClassLevel + 1) / 2;

		// Get the Red Wizard of Thay caster level
		if (iClass == CLASS_TYPE_RED_WIZARD)
			iCasterLevel += iClassLevel;
	}
	
	return iCasterLevel;
}

// Private function - Get the improved divine casting levels du to prestige classes
int JXPrivateGetImprovedDivineCLFromClasses(object oCreature, int iMainClass)
{
	int iCasterLevel = 0;

	// No class can improve the Blackguard caster level
	if (iMainClass == CLASS_TYPE_BLACKGUARD)
		return 0;

	int iClass, iClassLevel;
	int iLoop;
	for (iLoop = 1; iLoop <= 4; iLoop++)
	{
		iClass = GetClassByPosition(iLoop, oCreature);
		iClassLevel = GetLevelByPosition(iLoop, oCreature);

		// Get the Harper Agent caster level
		if (iClass == CLASS_TYPE_HARPER)
			iCasterLevel += iClassLevel - 1;

		// Get the Warpriest caster level
		if (iClass == CLASS_TYPE_WARPRIEST)
			iCasterLevel += (iClassLevel + 1) / 2;

		// Get the Stormlord caster level
		if (iClass == CLASS_TYPE_STORMLORD)
			iCasterLevel += iClassLevel;
	}

	return iCasterLevel;
}

// Private function - Indicate if a class is a main arcane class
int JXPrivateGetIsMainArcaneClass(int iClass)
{
	if ((iClass == CLASS_TYPE_BARD)
	 || (iClass == CLASS_TYPE_SORCERER)
	 || (iClass == CLASS_TYPE_WIZARD)
	 || (iClass == CLASS_TYPE_WARLOCK)
	 || (iClass == CLASS_TYPE_ASSASSIN))
		return TRUE;
	
	return FALSE;
}

// Private function - Indicate if a class is a main divine class
int JXPrivateGetIsMainDivineClass(int iClass)
{
    if ((iClass == CLASS_TYPE_CLERIC)
     || (iClass == CLASS_TYPE_DRUID)
     || (iClass == CLASS_TYPE_PALADIN)
     || (iClass == CLASS_TYPE_RANGER)
     || (iClass == CLASS_TYPE_BLACKGUARD)
     || (iClass == CLASS_TYPE_SPIRIT_SHAMAN)
     || (iClass == CLASS_TYPE_FAVORED_SOUL))
     	return TRUE;
	
	return FALSE;
}

// Private function - Get the caster level bonus due to the Practised Spellcaster feature
int JXPrivateGetCLBonusFromPractisedSpellcaster(object oCaster, int iCastingClass, int iCastingLevels)
{
    int iBonus = GetTotalLevels(oCaster, FALSE) - iCastingLevels;
    if (iBonus > 4) iBonus = 4;
    if (iBonus < 0) iBonus = 0;

    if (((iCastingClass == CLASS_TYPE_BARD) && GetHasFeat(FEAT_PRACTICED_SPELLCASTER_BARD, oCaster))
     || ((iCastingClass == CLASS_TYPE_SORCERER) && GetHasFeat(FEAT_PRACTICED_SPELLCASTER_SORCERER, oCaster))
     || ((iCastingClass == CLASS_TYPE_WIZARD) && GetHasFeat(FEAT_PRACTICED_SPELLCASTER_WIZARD, oCaster))
     || ((iCastingClass == CLASS_TYPE_CLERIC) && GetHasFeat(FEAT_PRACTICED_SPELLCASTER_CLERIC, oCaster))
     || ((iCastingClass == CLASS_TYPE_DRUID) && GetHasFeat(FEAT_PRACTICED_SPELLCASTER_DRUID, oCaster))
     || ((iCastingClass == CLASS_TYPE_PALADIN) && GetHasFeat(FEAT_PRACTICED_SPELLCASTER_PALADIN, oCaster))
     || ((iCastingClass == CLASS_TYPE_RANGER) && GetHasFeat(FEAT_PRACTICED_SPELLCASTER_RANGER, oCaster))
     || ((iCastingClass == CLASS_TYPE_SPIRIT_SHAMAN) && GetHasFeat(2003 /*FEAT_PRACTICED_SPELLCASTER_SPIRIT_SHAMAN*/, oCaster))
     || ((iCastingClass == CLASS_TYPE_FAVORED_SOUL) && GetHasFeat(2068 /*FEAT_PRACTICED_SPELLCASTER_FAVOURED_SOUL*/, oCaster))
	 )
		return iBonus;

    return 0;
}

// Private function - Indicate if a spell belongs to the Assassin spellbook
int JXPrivateGetIsAssassinSpell(int iSpellId)
{
	switch (iSpellId)
	{
		case SPELLABILITY_AS_GHOSTLY_VISAGE:
		case SPELLABILITY_AS_DARKNESS:
		case SPELLABILITY_AS_INVISIBILITY:
		case SPELLABILITY_AS_GREATER_INVISIBLITY:
			return TRUE;
	}
	return FALSE;
}

// Private function - Indicate if a spell belongs to the Blackguard spellbook
int JXPrivateGetIsBlackguardSpell(int iSpellId)
{
	switch (iSpellId)
	{
		case SPELLABILITY_BG_BULLS_STRENGTH:
		case SPELLABILITY_BG_CONTAGION:
		case SPELLABILITY_BG_INFLICT_SERIOUS_WOUNDS:
		case SPELLABILITY_BG_INFLICT_CRITICAL_WOUNDS:
		//case SPELLABILITY_BG_CREATEDEAD			// Not a spell : supernatural ability
		//case SPELLABILITY_BG_FIENDISH_SERVANT		// Not a spell : supernatural ability
		case 701: // Summon Fiendish Ally
			return TRUE;
	}

	return FALSE;
}

// Private function - Return the level of an Assassin spell
int JXPrivateGetAssassinSpellLevel(int iSpellId)
{
	switch (iSpellId)
	{
		case SPELLABILITY_AS_GHOSTLY_VISAGE: return 2;
		case SPELLABILITY_AS_DARKNESS: return 2;
		case SPELLABILITY_AS_INVISIBILITY: return 2;
		case SPELLABILITY_AS_GREATER_INVISIBLITY: return 4;
	}
	return 0;
}

// Private function - Return the level of a Blackguard spell
int JXPrivateGetBlackguardSpellLevel(int iSpellId)
{
	switch (iSpellId)
	{
		case SPELLABILITY_BG_BULLS_STRENGTH: return 2;
		case SPELLABILITY_BG_CONTAGION: return 3;
		case SPELLABILITY_BG_INFLICT_SERIOUS_WOUNDS: return 3;
		case SPELLABILITY_BG_INFLICT_CRITICAL_WOUNDS: return 4;
		//case SPELLABILITY_BG_CREATEDEAD			// Not a spell : supernatural ability
		//case SPELLABILITY_BG_FIENDISH_SERVANT		// Not a spell : supernatural ability
		case 701: return 5; // Summon Fiendish Ally
	}

	return 0;
}

// Private function - Get the save DC bonus for the specified spell
int JXPrivateGetSpellDCBonusFromSpell(int iSpellId, object oCreature)
{
	int iSpellSaveDC = 0;

	int iSpellSchool = JXGetSpellSchool(iSpellId);
	switch (iSpellSchool)
	{
		case SPELL_SCHOOL_EVOCATION:
			if (GetHasFeat(FEAT_EPIC_SPELL_FOCUS_EVOCATION, oCreature))			{ iSpellSaveDC += 6; break; }
			if (GetHasFeat(FEAT_GREATER_SPELL_FOCUS_EVOCATION, oCreature))		{ iSpellSaveDC += 4; break; }
			if (GetHasFeat(FEAT_SPELL_FOCUS_EVOCATION, oCreature))				{ iSpellSaveDC += 2; break; }
		case SPELL_SCHOOL_TRANSMUTATION:
			if (GetHasFeat(FEAT_EPIC_SPELL_FOCUS_TRANSMUTATION, oCreature))		{ iSpellSaveDC += 6; break; }
			if (GetHasFeat(FEAT_GREATER_SPELL_FOCUS_TRANSMUTATION, oCreature))	{ iSpellSaveDC += 4; break; }
			if (GetHasFeat(FEAT_SPELL_FOCUS_TRANSMUTATION, oCreature))			{ iSpellSaveDC += 2; break; }
		case SPELL_SCHOOL_NECROMANCY:
			if (GetHasFeat(FEAT_EPIC_SPELL_FOCUS_NECROMANCY, oCreature))		{ iSpellSaveDC += 6; break; }
			if (GetHasFeat(FEAT_GREATER_SPELL_FOCUS_NECROMANCY, oCreature))		{ iSpellSaveDC += 4; break; }
			if (GetHasFeat(FEAT_SPELL_FOCUS_NECROMANCY, oCreature))				{ iSpellSaveDC += 2; break; }
		case SPELL_SCHOOL_ILLUSION:
			if (GetHasFeat(FEAT_EPIC_SPELL_FOCUS_ILLUSION, oCreature))			{ iSpellSaveDC += 6; break; }
			if (GetHasFeat(FEAT_GREATER_SPELL_FOCUS_ILLUSION, oCreature))		{ iSpellSaveDC += 4; break; }
			if (GetHasFeat(FEAT_SPELL_FOCUS_ILLUSION, oCreature))				{ iSpellSaveDC += 2; break; }
		case SPELL_SCHOOL_ABJURATION:
			if (GetHasFeat(FEAT_EPIC_SPELL_FOCUS_ABJURATION, oCreature))		{ iSpellSaveDC += 6; break; }
			if (GetHasFeat(FEAT_GREATER_SPELL_FOCUS_ABJURATION, oCreature))		{ iSpellSaveDC += 4; break; }
			if (GetHasFeat(FEAT_SPELL_FOCUS_ABJURATION, oCreature))				{ iSpellSaveDC += 2; break; }
		case SPELL_SCHOOL_CONJURATION:
			if (GetHasFeat(FEAT_EPIC_SPELL_FOCUS_CONJURATION, oCreature))		{ iSpellSaveDC += 6; break; }
			if (GetHasFeat(FEAT_GREATER_SPELL_FOCUS_CONJURATION, oCreature))	{ iSpellSaveDC += 4; break; }
			if (GetHasFeat(FEAT_SPELL_FOCUS_CONJURATION, oCreature))			{ iSpellSaveDC += 2; break; }
		case SPELL_SCHOOL_DIVINATION:
			if (GetHasFeat(FEAT_EPIC_SPELL_FOCUS_DIVINATION, oCreature))		{ iSpellSaveDC += 6; break; }
			if (GetHasFeat(FEAT_GREATER_SPELL_FOCUS_DIVINIATION, oCreature))		{ iSpellSaveDC += 4; break; }
			if (GetHasFeat(FEAT_SPELL_FOCUS_DIVINATION, oCreature))				{ iSpellSaveDC += 2; break; }
		case SPELL_SCHOOL_ENCHANTMENT:
			if (GetHasFeat(FEAT_EPIC_SPELL_FOCUS_ENCHANTMENT, oCreature))		{ iSpellSaveDC += 6; break; }
			if (GetHasFeat(FEAT_GREATER_SPELL_FOCUS_ENCHANTMENT, oCreature))	{ iSpellSaveDC += 4; break; }
			if (GetHasFeat(FEAT_SPELL_FOCUS_ENCHANTMENT, oCreature))			{ iSpellSaveDC += 2; break; }
  }

	return iSpellSaveDC;
}



//========================================== Caster Class Search ==========================================//

// Get the main caster class, depending on the spell type
// - oCreature Creature from which to get the main caster class
// - iSpellType JX_SPELLTYPE_* constant
// * Returns a CLASS_TYPE_* constant, or -1 if not found
int JXGetMainCasterClass(object oCreature = OBJECT_SELF, int iSpellType = JX_SPELLTYPE_BOTH)
{
	struct script_param_list paramList;
	paramList = JXScriptAddParameterObject(paramList, oCreature);
	paramList = JXScriptAddParameterInt(paramList, iSpellType);

	JXScriptCallFork(JX_SPFMWK_FORKSCRIPT, JX_FORK_MAINCASTERCLASS, paramList);

	return JXScriptGetResponseInt();
}

// Get the creature's class that is associated with the specified spell
// - iSpellId SPELL_* constant
// - oCreature Creature on which the class search is performed
// * Returns a CLASS_TYPE_* constant, or -1 if it is not found
int JXGetClassForSpell(int iSpellId, object oCreature = OBJECT_SELF)
{
	int iBestClass = CLASS_TYPE_INVALID;
	int iBestCasterLevel = -1;

	// Loop on the creature's classes to know which one may cast the spell
	int iClass, iCasterLevel;
	int iLoop;
	for (iLoop = 1; iLoop <= 4; iLoop++)
	{
		iClass = GetClassByPosition(iLoop, oCreature);

		// Special case : Assassins don't have spellbooks, but have unique spells
		if (iClass == CLASS_TYPE_ASSASSIN)
		{
			if (JXPrivateGetIsAssassinSpell(iSpellId))
				return iClass;
		}
		// Special case : Blackguards don't have spellbooks, but have unique spells
		else if (iClass == CLASS_TYPE_BLACKGUARD)
		{
			if (JXPrivateGetIsBlackguardSpell(iSpellId))
				return iClass;
		}
		// Classic cases : Caster classes with spellbooks
		else if (JXPrivateGetIsMainArcaneClass(iClass)
		      || JXPrivateGetIsMainDivineClass(iClass))
		{
			// Spell defined for the class
			if (JXGetBaseSpellLevel(iSpellId, iClass) != -1)
			{
				// Keep the class if it has the best caster level
				iCasterLevel = JXGetCreatureCasterLevel(oCreature, iClass);
		  		if (iCasterLevel > iBestCasterLevel)
		  		{
		  			iBestClass = iClass;
		  			iBestCasterLevel = iCasterLevel;
		  		}
			}
		}
	}

	return iBestClass;
}



//========================================== Class-related Caster Level ==========================================//

// Get the arcane caster level for a creature based on one of her class
// - oCreature Creature from which to get the arcane caster level
// - iClass CLASS_TYPE_* constant (CLASS_TYPE_INVALID to use the main arcane class)
// * Returns the creature's arcane caster level
int JXGetCreatureArcaneCasterLevel(object oCreature = OBJECT_SELF, int iClass = CLASS_TYPE_INVALID)
{
	struct script_param_list paramList;
	paramList = JXScriptAddParameterObject(paramList, oCreature);
	paramList = JXScriptAddParameterInt(paramList, iClass);

	JXScriptCallFork(JX_SPFMWK_FORKSCRIPT, JX_FORK_CREATUREARCANECASTERLEVEL, paramList);

	return JXScriptGetResponseInt();
}

// Get the divine caster level for a creature based on one of her class
// - oCreature Creature from which to get the divine caster level
// - iClass CLASS_TYPE_* constant (CLASS_TYPE_INVALID to use the main divine class)
// * Returns the creature's divine caster level
int JXGetCreatureDivineCasterLevel(object oCreature = OBJECT_SELF, int iClass = CLASS_TYPE_INVALID)
{
	struct script_param_list paramList;
	paramList = JXScriptAddParameterObject(paramList, oCreature);
	paramList = JXScriptAddParameterInt(paramList, iClass);

	JXScriptCallFork(JX_SPFMWK_FORKSCRIPT, JX_FORK_CREATUREDIVINECASTERLEVEL, paramList);

	return JXScriptGetResponseInt();
}

// Get the caster level for a creature based on one of her class
// - oCreature Creature from which to get the caster level
// - iClass CLASS_TYPE_* constant (CLASS_TYPE_INVALID to use the main caster class)
// * Returns the creature's caster level
int JXGetCreatureCasterLevel(object oCreature = OBJECT_SELF, int iClass = CLASS_TYPE_INVALID)
{
	struct script_param_list paramList;
	paramList = JXScriptAddParameterObject(paramList, oCreature);
	paramList = JXScriptAddParameterInt(paramList, iClass);

	JXScriptCallFork(JX_SPFMWK_FORKSCRIPT, JX_FORK_CREATURECASTERLEVEL, paramList);

	return JXScriptGetResponseInt();
}

// Get the caster level for a creature depending on a spell
// - oCreature Creature from which to get the caster level
// - iClass CLASS_TYPE_* constant (CLASS_TYPE_INVALID to use the main caster class)
// * Returns the creature's caster level
int JXGetCreatureCasterLevelForSpell(int iSpellId, object oCreature = OBJECT_SELF, int iClass = CLASS_TYPE_INVALID)
{
	struct script_param_list paramList;
	paramList = JXScriptAddParameterInt(paramList, iSpellId);
	paramList = JXScriptAddParameterObject(paramList, oCreature);
	paramList = JXScriptAddParameterInt(paramList, iClass);

	JXScriptCallFork(JX_SPFMWK_FORKSCRIPT, JX_FORK_CREATURECASTERLEVELSPELL, paramList);

	return JXScriptGetResponseInt();
}


//========================================== Class-related Spell Save DC ==========================================//

// Get the save DC for the specified spell cast by a creature
// - iSpellId SPELL_* constant
// - oCreature Creature from which to get the spell save DC
// - iClass CLASS_TYPE_* constant (CLASS_TYPE_INVALID to use the best caster class able to cast the spell)
// * Returns the creature's spell save DC
int JXGetCreatureSpellSaveDC(int iSpellId, object oCreature = OBJECT_SELF, int iClass = CLASS_TYPE_INVALID)
{
	struct script_param_list paramList;
	paramList = JXScriptAddParameterInt(paramList, iSpellId);
	paramList = JXScriptAddParameterObject(paramList, oCreature);
	paramList = JXScriptAddParameterInt(paramList, iClass);

	JXScriptCallFork(JX_SPFMWK_FORKSCRIPT, JX_FORK_CREATURESPELLSAVEDC, paramList);

	return JXScriptGetResponseInt();
}