using System;
using Server.Items;
using Server.Network;
using Server.Targeting;
using Server.Mobiles;
using Server.Spells.First;
using Server.Spells.Second;
using Server.Spells.Third;
using Server.Spells.Sixth;
using Server.Spells.Seventh;
using Server.Spells.Necromancy;
using Server.Spells.Ninjitsu;
using Server.Spells.Spellweaving;
using System.Collections.Generic;

namespace Server.Spells
{
    public abstract class Spell : ISpell
    {
        private Mobile m_Caster;
        private Item m_Scroll;
        private SpellInfo m_Info;
        private SpellState m_State;
        private DateTime m_StartCastTime;

        public SpellState State { get { return m_State; } set { m_State = value; } }
        public Mobile Caster { get { return m_Caster; } }
        public SpellInfo Info { get { return m_Info; } }
        public string Name { get { return m_Info.Name; } }
        public string Mantra { get { return m_Info.Mantra; } }
        public SpellCircle Circle { get { return m_Info.Circle; } }
        public Type[] Reagents { get { return m_Info.Reagents; } }
        public Item Scroll { get { return m_Scroll; } }
        public DateTime StartCastTime { get { return m_StartCastTime; } }

        private static TimeSpan NextSpellDelay = TimeSpan.FromSeconds(0.75);
        private static TimeSpan AnimateDelay = TimeSpan.FromSeconds(1.5);

        public virtual SkillName CastSkill { get { return SkillName.Magery; } }
        public virtual SkillName DamageSkill { get { return SkillName.EvalInt; } }

        public virtual bool RevealOnCast { get { return true; } }
        public virtual bool ClearHandsOnCast { get { return false; } }
        public virtual bool ShowHandMovement { get { return true; } }

        public virtual bool DelayedDamage { get { return false; } }

        public virtual bool DelayedDamageStacking { get { return true; } }
        //In reality, it's ANY delayed Damage spell Post-AoS that can't stack, but, only 
        //Expo & Magic Arrow have enough delay and a short enough cast time to bring up 
        //the possibility of stacking 'em.  Note that a MA & an Explosion will stack, but
        //of course, two MA's won't.

        private static Dictionary<Type, Dictionary<Mobile, Timer>> m_ContextTable = new Dictionary<Type, Dictionary<Mobile, Timer>>();

        public void StartDelayedDamageContext(Mobile m, Timer t)
        {
            if (DelayedDamageStacking)
                return; //Sanity

            Dictionary<Mobile, Timer> contexts;

            if (!m_ContextTable.TryGetValue(GetType(), out contexts))
            {
                contexts = new Dictionary<Mobile, Timer>();
                m_ContextTable.Add(GetType(), contexts);
            }

            if (contexts.ContainsKey(m))
            {
                contexts[m].Stop();
                contexts.Remove(m);
            }

            contexts.Add(m, t);
        }

        public void RemoveDelayedDamageContext(Mobile m)
        {
            Dictionary<Mobile, Timer> contexts;

            if (!m_ContextTable.TryGetValue(GetType(), out contexts))
                return;

            contexts.Remove(m);
        }

        public Spell(Mobile caster, Item scroll, SpellInfo info)
        {
            m_Caster = caster;
            m_Scroll = scroll;
            m_Info = info;
        }

        public virtual int GetNewAosDamage(int bonus, int dice, int sides, Mobile singleTarget)
        {
            if (singleTarget != null)
            {
                return GetNewAosDamage(bonus, dice, sides, (Caster.Player && singleTarget.Player), GetDamageScalar(singleTarget));
            }
            else
            {
                return GetNewAosDamage(bonus, dice, sides, false);
            }
        }

        public virtual int GetNewAosDamage(int bonus, int dice, int sides, bool playerVsPlayer)
        {
            return GetNewAosDamage(bonus, dice, sides, playerVsPlayer, 1.0);
        }

        public virtual int GetNewAosDamage(int bonus, int dice, int sides, bool playerVsPlayer, double scalar)
        {
            int damage = Utility.Dice(dice, sides, bonus) * 100;

            if (Spellbook.FindEquippedSpellbook(m_Caster) is WandOfEvolution)
            {
                WandOfEvolution wand = (WandOfEvolution)Spellbook.FindEquippedSpellbook(m_Caster);

                if (wand != null && DateTime.Now > wand.NextSpecial && wand.Effect == WandOfEvolutionEffect.DoubleSpellDamage && wand.Bonus > Utility.Random(100))
                {
                    wand.NextSpecial = DateTime.Now + TimeSpan.FromSeconds(5.0);
                    damage *= 2;
                }
            }

            int damageBonus = 0;

            int inscribeSkill = GetInscribeFixed(m_Caster);
            int inscribeBonus = (inscribeSkill + (1000 * (inscribeSkill / 1000))) / 200;
            damageBonus += inscribeBonus;

            int intBonus = Caster.Int / 10;
            damageBonus += intBonus;

            int sdiBonus = AosAttributes.GetValue(m_Caster, AosAttribute.SpellDamage);

            // SPELLWEAVING: SDI Bonuses
            sdiBonus += ReaperFormSpell.GetSDIBonus(m_Caster);
            sdiBonus += ArcaneEmpowermentSpell.GetSDIBonus(m_Caster);

            // PvP spell damage increase cap of 15% from an item�s magic property
            // 			if ( playerVsPlayer && sdiBonus > 15 )
            // 				sdiBonus = 15;

            // Omezeni SDI
            if (sdiBonus > 300 && m_Caster.AccessLevel == AccessLevel.Player)
            {
                sdiBonus = 300;

                if (m_Caster.FindItemOnLayer(Layer.Shoes) is BaseShoes)
                    sdiBonus += ((BaseShoes)m_Caster.FindItemOnLayer(Layer.Shoes)).SDIBonus;
                else if (m_Caster.FindItemOnLayer(Layer.Shoes) is BaseLevelShoes)
                    sdiBonus += ((BaseLevelShoes)m_Caster.FindItemOnLayer(Layer.Shoes)).SDIBonus;
            }

            damageBonus += sdiBonus;

            damage = AOS.Scale(damage, 100 + damageBonus);

            int evalSkill = GetDamageFixed(m_Caster);
            int evalScale = 30 + ((9 * evalSkill) / 100);

            damage = AOS.Scale(damage, evalScale);

            damage = AOS.Scale(damage, (int)(scalar * 100));

            return damage / 100;
        }

        /*
		public virtual double GetAosDamage( int min, int random, double div )
		{
			double scale = 1.0;

			scale += GetInscribeSkill( m_Caster ) * 0.001;

			if ( Caster.Player )
			{
				scale += Caster.Int * 0.001;
				scale += AosAttributes.GetValue( m_Caster, AosAttribute.SpellDamage ) * 0.01;
			}

			int baseDamage = min + (int)(GetDamageSkill( m_Caster ) / div);

			double damage = Utility.RandomMinMax( baseDamage, baseDamage + random );

			return damage * scale;
		}
		*/

        public virtual bool IsCasting { get { return m_State == SpellState.Casting; } }

        public virtual void OnCasterHurt()
        {
            //Confirm: Monsters and pets cannot be disturbed.
            if (!Caster.Player)
                return;

            if (IsCasting)
            {
                object o = ProtectionSpell.Registry[m_Caster];
                bool disturb = true;

                if (o != null && o is double)
                {
                    if (((double)o) > Utility.RandomDouble() * 100.0)
                        disturb = false;
                }

                if (disturb)
                {
                    if (Spellbook.FindEquippedSpellbook(m_Caster) is WandOfEvolution)
                    {
                        WandOfEvolution wand = (WandOfEvolution)Spellbook.FindEquippedSpellbook(m_Caster);

                        if (wand != null && DateTime.Now > wand.NextSpecial && wand.Effect == WandOfEvolutionEffect.EnergyVortex/* && wand.Bonus > Utility.Random( 100 )*/)
                        {
                            wand.NextSpecial = DateTime.Now + TimeSpan.FromSeconds(5.0);

                            BaseCreature b = new EnergyVortex();
                            //     						b.Combatant = defender;
                            b.ControlSlots = 0;
                            b.Summoned = true;
                            b.SummonMaster = m_Caster;
                            b.Tamable = false;
                            b.MoveToWorld(m_Caster.Location, m_Caster.Map);
                            Effects.PlaySound(m_Caster.Location, m_Caster.Map, 0x212);

                            TimeSpan duration = TimeSpan.FromSeconds(20.0);
                            new UnsummonTimer(m_Caster, b, duration).Start();
                            b.SummonEnd = DateTime.Now + duration;
                        }
                    }

                    Disturb(DisturbType.Hurt, false, true);
                }
            }
        }

        public virtual void OnCasterKilled()
        {
            Disturb(DisturbType.Kill);
        }

        public virtual void OnConnectionChanged()
        {
            FinishSequence();
        }

        public virtual bool OnCasterMoving(Direction d)
        {
            if (IsCasting && BlocksMovement)
            {
                if (m_Caster.Talisman is MerlinsAmulet || Spellbook.FindEquippedSpellbook(m_Caster) is WandOfEvolution)
                {
                    return true;
                }

                m_Caster.SendLocalizedMessage(500111); // You are frozen and can not move.
                return false;
            }

            return true;
        }

        public virtual bool OnCasterEquiping(Item item)
        {
            if (IsCasting)
                Disturb(DisturbType.EquipRequest);

            return true;
        }

        public virtual bool OnCasterUsingObject(object o)
        {
            if (m_State == SpellState.Sequencing)
                Disturb(DisturbType.UseRequest);

            return true;
        }

        public virtual bool OnCastInTown(Region r)
        {
            return m_Info.AllowTown;
        }

        public virtual bool ConsumeReagents()
        {
            if (m_Scroll != null || !m_Caster.Player)
                return true;

            if (m_Caster.Map == Map.Underworld)
            {
                if (Math.Min(50, AosAttributes.GetValue(m_Caster, AosAttribute.LowerRegCost)) > Utility.Random(100))
                    return true;

                Container pack = m_Caster.Backpack;

                if (pack == null)
                    return false;

                if (pack.ConsumeTotal(m_Info.Reagents, m_Info.Amounts) == -1)
                    return true;

                return false;
            }

            bool isFireRegion = m_Caster.Region.IsPartOf(typeof(Server.Regions.FireRegion));

            if (Math.Min(m_Caster.Region.IsPartOf(typeof(Server.Regions.DungeonRegion)) ? 40 : 75, AosAttributes.GetValue(m_Caster, AosAttribute.LowerRegCost)) > Utility.Random(100) && !(this is CreateFoodSpell) && !isFireRegion)
                return true;

            int tith = ((int)Circle + 1) * 2;

            if (this is CreateFoodSpell)
                tith = 100;

            if (isFireRegion)
            {
                Container pack = m_Caster.Backpack;

                if (pack == null)
                    return false;

                if (pack.ConsumeTotal(m_Info.Reagents, m_Info.Amounts) == -1)
                    return true;

                tith *= 10;
            }

            if (m_Caster.TithingPoints >= tith)
            {
                m_Caster.TithingPoints -= tith;
                return true;
            }

            if (GetType().BaseType == typeof(Spell))
            {
                if (ArcaneGem.ConsumeCharges(m_Caster, 1))
                    return true;
            }

            return false;
        }

        public virtual bool CheckResisted(Mobile target)
        {
            double n = GetResistPercent(target);

            n /= 100.0;

            if (n <= 0.0)
                return false;

            if (n >= 1.0)
                return true;

            int maxSkill = (1 + (int)Circle) * 10;
            maxSkill += (1 + ((int)Circle / 6)) * 25;

            if (target.Skills[SkillName.MagicResist].Value < maxSkill)
                target.CheckSkill(SkillName.MagicResist, 0.0, 120.0);

            return (n >= Utility.RandomDouble());
        }

        public virtual double GetInscribeSkill(Mobile m)
        {
            // There is no chance to gain
            // m.CheckSkill( SkillName.Inscribe, 0.0, 120.0 );

            return m.Skills[SkillName.Inscribe].Value;
        }

        public virtual int GetInscribeFixed(Mobile m)
        {
            // There is no chance to gain
            // m.CheckSkill( SkillName.Inscribe, 0.0, 120.0 );

            return m.Skills[SkillName.Inscribe].Fixed;
        }

        public virtual int GetDamageFixed(Mobile m)
        {
            m.CheckSkill(DamageSkill, 0.0, 120.0);

            return m.Skills[DamageSkill].Fixed;
        }

        public virtual double GetDamageSkill(Mobile m)
        {
            m.CheckSkill(DamageSkill, 0.0, 120.0);

            return m.Skills[DamageSkill].Value;
        }

        public virtual int GetResistFixed(Mobile m)
        {
            int maxSkill = (1 + (int)Circle) * 10;
            maxSkill += (1 + ((int)Circle / 6)) * 25;

            if (m.Skills[SkillName.MagicResist].Value < maxSkill)
                m.CheckSkill(SkillName.MagicResist, 0.0, 120.0);

            return m.Skills[SkillName.MagicResist].Fixed;
        }

        public virtual double GetResistSkill(Mobile m)
        {
            int maxSkill = (1 + (int)Circle) * 10;
            maxSkill += (1 + ((int)Circle / 6)) * 25;

            if (m.Skills[SkillName.MagicResist].Value < maxSkill)
                m.CheckSkill(SkillName.MagicResist, 0.0, 120.0);

            return m.Skills[SkillName.MagicResist].Value;
        }

        public virtual double GetResistPercentForCircle(Mobile target, SpellCircle circle)
        {
            double firstPercent = target.Skills[SkillName.MagicResist].Value / 5.0;
            double secondPercent = target.Skills[SkillName.MagicResist].Value - (((m_Caster.Skills[CastSkill].Value - 20.0) / 5.0) + (1 + (int)circle) * 5.0);

            return (firstPercent > secondPercent ? firstPercent : secondPercent) / 2.0; // Seems should be about half of what stratics says.
        }

        public virtual double GetResistPercent(Mobile target)
        {
            return GetResistPercentForCircle(target, m_Info.Circle);
        }

        public virtual double GetDamageScalar(Mobile target)
        {
            double scalar = 1.0;

            if (target.Map == Map.Underworld)   //EvalInt stuff for AoS is handled elsewhere
            {
                double casterEI = m_Caster.Skills[DamageSkill].Value;
                double targetRS = target.Skills[SkillName.MagicResist].Value;

                /*
				if( Core.AOS )
					targetRS = 0;
				*/

                m_Caster.CheckSkill(DamageSkill, 0.0, 120.0);

                if (casterEI > targetRS)
                    scalar = (1.0 + ((casterEI - targetRS) / 500.0));
                else
                    scalar = (1.0 + ((casterEI - targetRS) / 200.0));

                // magery damage bonus, -25% at 0 skill, +0% at 100 skill, +5% at 120 skill
                scalar += (m_Caster.Skills[CastSkill].Value - 100.0) / 400.0;

                if (!target.Player && !target.Body.IsHuman /*&& !Core.AOS*/)
                    scalar *= 2.0; // Double magery damage to monsters/animals if not AOS
            }

            if (target is BaseCreature)
                ((BaseCreature)target).AlterDamageScalarFrom(m_Caster, ref scalar);

            if (m_Caster is BaseCreature)
                ((BaseCreature)m_Caster).AlterDamageScalarTo(target, ref scalar);

            if (Core.SE)
                scalar *= GetSlayerDamageScalar(target);

            target.Region.SpellDamageScalar(m_Caster, target, ref scalar);

            return scalar;
        }

        public virtual double GetSlayerDamageScalar(Mobile defender)
        {
            Spellbook atkBook = Spellbook.FindEquippedSpellbook(m_Caster);

            double scalar = 1.0;
            if (atkBook != null || m_Caster.Weapon is BaseStaff)
            {
                SlayerEntry atkSlayer = null;
                SlayerEntry atkSlayer2 = null;

                if (atkBook != null)
                {
                    atkSlayer = SlayerGroup.GetEntryByName(atkBook.Slayer);
                    atkSlayer2 = SlayerGroup.GetEntryByName(atkBook.Slayer2);
                }
                else if (m_Caster.Weapon is BaseStaff)
                {
                    BaseWeapon atkWeapon = m_Caster.Weapon as BaseWeapon;

                    atkSlayer = SlayerGroup.GetEntryByName(atkWeapon.Slayer);
                    atkSlayer2 = SlayerGroup.GetEntryByName(atkWeapon.Slayer2);
                }

                if (atkSlayer != null && atkSlayer.Slays(defender) || atkSlayer2 != null && atkSlayer2.Slays(defender))
                {
                    defender.FixedEffect(0x37B9, 10, 5);    //TODO: Confirm this displays on OSIs
                    scalar = 2.0;
                }

                TransformContext context = TransformationSpell.GetContext(defender);

                if (atkBook != null && (atkBook.Slayer == SlayerName.Silver || atkBook.Slayer2 == SlayerName.Silver) && context != null && context.Type != typeof(HorrificBeastSpell))
                    scalar += .25; // Every necromancer transformation other than horrific beast take an additional 25% damage

                if (scalar != 1.0)
                    return scalar;
            }

            ISlayer defISlayer = Spellbook.FindEquippedSpellbook(defender);

            if (defISlayer == null)
                defISlayer = defender.Weapon as ISlayer;

            if (defISlayer != null)
            {
                SlayerEntry defSlayer = SlayerGroup.GetEntryByName(defISlayer.Slayer);
                SlayerEntry defSlayer2 = SlayerGroup.GetEntryByName(defISlayer.Slayer2);

                if (defSlayer != null && defSlayer.Group.OppositionSuperSlays(m_Caster) || defSlayer2 != null && defSlayer2.Group.OppositionSuperSlays(m_Caster))
                    scalar = 2.0;
            }

            return scalar;
        }

        public virtual void DoFizzle()
        {
            m_Caster.LocalOverheadMessage(MessageType.Regular, 0x3B2, 502632); // The spell fizzles.

            if (m_Caster.Player)
            {
                if (Core.AOS)
                    m_Caster.FixedParticles(0x3735, 1, 30, 9503, EffectLayer.Waist);
                else
                    m_Caster.FixedEffect(0x3735, 6, 30);

                m_Caster.PlaySound(0x5C);
            }
        }

        private CastTimer m_CastTimer;
        private AnimTimer m_AnimTimer;

        public void Disturb(DisturbType type)
        {
            Disturb(type, true, false);
        }

        public virtual bool CheckDisturb(DisturbType type, bool firstCircle, bool resistable)
        {
            if (resistable && m_Scroll is BaseWand)
                return false;

            return true;
        }

        public void Disturb(DisturbType type, bool firstCircle, bool resistable)
        {
            if (!CheckDisturb(type, firstCircle, resistable))
                return;

            if (m_State == SpellState.Casting)
            {
                if (!firstCircle && Circle == SpellCircle.First && m_Caster.Map == Map.Underworld)
                    return;

                m_State = SpellState.None;
                m_Caster.Spell = null;

                OnDisturb(type, true);

                if (m_CastTimer != null)
                    m_CastTimer.Stop();

                if (m_AnimTimer != null)
                    m_AnimTimer.Stop();

                if (Core.AOS && m_Caster.Player && type == DisturbType.Hurt)
                    DoHurtFizzle();

                m_Caster.NextSpellTime = DateTime.Now + GetDisturbRecovery();
            }
            else if (m_State == SpellState.Sequencing)
            {
                if (!firstCircle && Circle == SpellCircle.First && m_Caster.Map == Map.Underworld)
                    return;

                m_State = SpellState.None;
                m_Caster.Spell = null;

                OnDisturb(type, false);

                Targeting.Target.Cancel(m_Caster);

                if (Core.AOS && m_Caster.Player && type == DisturbType.Hurt)
                    DoHurtFizzle();
            }
        }

        public virtual void DoHurtFizzle()
        {
            m_Caster.FixedEffect(0x3735, 6, 30);
            m_Caster.PlaySound(0x5C);
        }

        public virtual void OnDisturb(DisturbType type, bool message)
        {
            if (message)
                m_Caster.SendLocalizedMessage(500641); // Your concentration is disturbed, thus ruining thy spell.
        }

        public virtual bool CheckCast()
        {
            return true;
        }

        public virtual void SayMantra()
        {
            if (m_Scroll is BaseWand)
                return;

            if (m_Info.Mantra != null && m_Info.Mantra.Length > 0 && m_Caster.Player)
                // 				m_Caster.PublicOverheadMessage( MessageType.Spell, m_Caster.SpeechHue, true, m_Info.Mantra, false );
                m_Caster.LocalOverheadMessage(MessageType.Spell, m_Caster.SpeechHue, false, m_Info.Mantra);
        }

        public virtual bool BlockedByHorrificBeast { get { return true; } }
        public virtual bool BlockedByAnimalForm { get { return true; } }
        public virtual bool BlocksMovement { get { return true; } }

        public virtual bool CheckNextSpellTime { get { return !(m_Scroll is BaseWand); } }

        public bool Cast()
        {
            m_StartCastTime = DateTime.Now;

            if (Core.AOS && m_Caster.Spell is Spell && ((Spell)m_Caster.Spell).State == SpellState.Sequencing)
                ((Spell)m_Caster.Spell).Disturb(DisturbType.NewCast);

            if (!m_Caster.CheckAlive())
            {
                return false;
            }
            else if (m_Caster.Spell != null && m_Caster.Spell.IsCasting)
            {
                m_Caster.SendLocalizedMessage(502642); // You are already casting a spell.
            }
            else if (BlockedByHorrificBeast && TransformationSpell.UnderTransformation(m_Caster, typeof(HorrificBeastSpell)) || (BlockedByAnimalForm && AnimalForm.UnderTransformation(m_Caster)))
            {
                m_Caster.SendLocalizedMessage(1061091); // You cannot cast that spell in this form.
            }
            else if (!(m_Scroll is BaseWand) && (m_Caster.Paralyzed || m_Caster.Frozen))
            {
                m_Caster.SendLocalizedMessage(502643); // You can not cast a spell while frozen.
            }
            else if (CheckNextSpellTime && DateTime.Now < m_Caster.NextSpellTime)
            {
                m_Caster.SendLocalizedMessage(502644); // You have not yet recovered from casting a spell.
            }
            else if (m_Caster.Mana >= ScaleMana(GetMana()))
            {
                if (m_Caster.Spell == null && m_Caster.CheckSpellCast(this) && CheckCast() && m_Caster.Region.OnBeginSpellCast(m_Caster, this))
                {
                    m_State = SpellState.Casting;
                    m_Caster.Spell = this;

                    if (RevealOnCast)
                        m_Caster.RevealingAction();

                    SayMantra();

                    TimeSpan castDelay = this.GetCastDelay();

                    if (ShowHandMovement && m_Caster.Body.IsHuman)
                    {
                        int count = (int)Math.Ceiling(castDelay.TotalSeconds / AnimateDelay.TotalSeconds);

                        if (count != 0)
                        {
                            m_AnimTimer = new AnimTimer(this, count);
                            m_AnimTimer.Start();
                        }

                        if (m_Info.LeftHandEffect > 0)
                            Caster.FixedParticles(0, 10, 5, m_Info.LeftHandEffect, EffectLayer.LeftHand);

                        if (m_Info.RightHandEffect > 0)
                            Caster.FixedParticles(0, 10, 5, m_Info.RightHandEffect, EffectLayer.RightHand);
                    }

                    if (ClearHandsOnCast)
                        m_Caster.ClearHands();

                    m_CastTimer = new CastTimer(this, castDelay);
                    m_CastTimer.Start();

                    OnBeginCast();

                    return true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                m_Caster.LocalOverheadMessage(MessageType.Regular, 0x22, 502625); // Insufficient mana
            }

            return false;
        }

        public abstract void OnCast();

        public virtual void OnBeginCast()
        {
        }

        private const double ChanceOffset = 20.0, ChanceLength = 100.0 / 7.0;

        public virtual void GetCastSkills(out double min, out double max)
        {
            int circle = (int)m_Info.Circle;

            if (m_Scroll != null)
                circle -= 2;

            double avg = ChanceLength * circle;

            min = avg - ChanceOffset;
            max = avg + ChanceOffset;
        }

        public virtual bool CheckFizzle()
        {
            if (m_Scroll is BaseWand)
                return true;

            if (Server.Mobiles.KhalAnkur.IsUnderInfluence(Caster))
            {
                Caster.SendMessage("Jsi pod vlivem kouzla Silence");
                return false;
            }

            double minSkill, maxSkill;

            GetCastSkills(out minSkill, out maxSkill);

            return Caster.CheckSkill(CastSkill, minSkill, maxSkill);
        }

        private static int[] m_ManaTable = new int[] { 4, 6, 9, 11, 14, 20, 40, 50 };

        public virtual int GetMana()
        {
            if (m_Scroll is BaseWand)
                return 0;

            if (Spellbook.FindEquippedSpellbook(m_Caster) is WandOfEvolution)
            {
                WandOfEvolution wand = (WandOfEvolution)Spellbook.FindEquippedSpellbook(m_Caster);

                if (wand != null && DateTime.Now > wand.NextSpecial && wand.Effect == WandOfEvolutionEffect.NoManaCost && wand.Bonus > Utility.Random(100))
                {
                    wand.NextSpecial = DateTime.Now + TimeSpan.FromSeconds(5.0);
                    return 0;
                }
            }

            return m_ManaTable[(int)Circle];
        }

        public virtual int ScaleMana(int mana)
        {
            double scalar = 1.0;

            if (!Necromancy.MindRotSpell.GetMindRotScalar(Caster, ref scalar))
                scalar = 1.0;

            // Lower Mana Cost = 40%
            int lmc = AosAttributes.GetValue(m_Caster, AosAttribute.LowerManaCost);
            if (lmc > 40)
                lmc = 40;

            scalar -= (double)lmc / 100;

            return (int)(mana * scalar);
        }

        public virtual TimeSpan GetDisturbRecovery()
        {
            if (m_Caster.Map != Map.Underworld)
                return TimeSpan.Zero;

            double delay = 1.0 - Math.Sqrt((DateTime.Now - m_StartCastTime).TotalSeconds / GetCastDelay().TotalSeconds);

            if (delay < 0.2)
                delay = 0.2;

            return TimeSpan.FromSeconds(delay);
        }

        public virtual int CastRecoveryBase { get { return 8; } }
        public virtual int CastRecoveryCircleScalar { get { return 1; } }
        public virtual int CastRecoveryFastScalar { get { return 1; } }
        public virtual int CastRecoveryPerSecond { get { return 4; } }
        public virtual int CastRecoveryMinimum { get { return 0; } }

        public virtual TimeSpan GetCastRecovery()
        {
            int fcr = 0;
            int fcrDelay = 0;
            int delay = 0;

            if (m_Caster.Map == Map.Underworld)
            {
                fcr = Math.Min(AosAttributes.GetValue(m_Caster, AosAttribute.CastRecovery), 6);
                int circleDelay = CastRecoveryCircleScalar * (1 + (int)Circle); // Note: Circle is 0-based so we must offset
                fcrDelay = -(CastRecoveryFastScalar * fcr);
                delay = CastRecoveryBase + circleDelay + fcrDelay;

                if (delay < 1)
                    delay = 1;

                return TimeSpan.FromSeconds((double)delay / CastRecoveryPerSecond);//NextSpellDelay;
            }

            fcr = (AosAttributes.GetValue(m_Caster, AosAttribute.CastRecovery) / 2);

            // SPELLWEAVING: Thunderstorm malus to Faster Cast Recovery
            fcr -= ThunderstormSpell.GetCastRecoveryMalus(m_Caster);

            fcrDelay = -(CastRecoveryFastScalar * fcr);

            delay = CastRecoveryBase/* + circleDelay*/ + fcrDelay;

            if (delay < CastRecoveryMinimum)
            {
                //             if ( this is MirrorImage || this is ArcaneSummon || this is WallOfStoneSpell || this is ParalyzeFieldSpell || this is EnergyFieldSpell || this is TeleportSpell )
                //                delay = Math.Max( delay, 0 );
                //             else
                delay = CastRecoveryMinimum;
            }

            double maxSpeed = 0.4;

            if (m_Caster is PlayerMobile)
            {
                PlayerMobile.TalentEntry te = ((PlayerMobile)m_Caster).GetTalent(PlayerMobile.TalentType.RychlaMagie);

                if (te != null && te.Enabled && te.Charges >= 3)
                {
                    te.Charges -= 3;

                    if (te.Charges < 3)
                        te.Enabled = false;

                    maxSpeed = 0.15;
                }
            }

            return TimeSpan.FromSeconds(Math.Max((double)delay / CastDelayPerSecond, maxSpeed));
        }

        public virtual int CastDelayBase { get { return 3; } }
        public virtual int CastDelayCircleScalar { get { return 1; } }
        public virtual int CastDelayFastScalar { get { return 1; } }
        public virtual int CastDelayPerSecond { get { return 4; } }
        public virtual int CastDelayMinimum { get { return 1; } }

        public virtual TimeSpan GetCastDelay()
        {
            if (m_Scroll is BaseWand)
                return TimeSpan.Zero;

            if (m_Caster.Map == Map.Underworld)
                return TimeSpan.FromSeconds(0.5 + (0.25 * (int)Circle));

            // Faster casting cap of 2 (if not using the protection spell) 
            // Faster casting cap of 0 (if using the protection spell) 
            // Paladin spells are subject to a faster casting cap of 4 
            // Paladins with magery of 70.0 or above are subject to a faster casting cap of 2 
            int fcMax = 6; //default 2

            if (CastSkill == SkillName.Chivalry && m_Caster.Skills[SkillName.Magery].Value < 70.0)
                fcMax = 8; //default 4

            int fc = AosAttributes.GetValue(m_Caster, AosAttribute.CastSpeed);

            if (fc > fcMax)
                fc = fcMax;

            if (ProtectionSpell.Registry.Contains(m_Caster))
                fc -= 2;

            // SPELLWEAVING: Essence of Wind malus to Faster Casting
            fc -= EssenceOfWindSpell.GetCastMalus(m_Caster);

            // Circle is 0-based but we must not offset, first circle spells are cast at base delay.
            int circleDelay = CastDelayCircleScalar * (int)Circle;

            int fcDelay = -(CastDelayFastScalar * fc);

            int delay = CastDelayBase + circleDelay + fcDelay;

            if (delay < CastDelayMinimum)
                delay = CastDelayMinimum;

            return TimeSpan.FromSeconds((double)delay / CastDelayPerSecond);
        }

        public virtual void FinishSequence()
        {
            m_State = SpellState.None;

            if (m_Caster.Spell == this)
                m_Caster.Spell = null;
        }

        public virtual int ComputeKarmaAward()
        {
            return 0;
        }

        public virtual bool CheckSequence()
        {
            int mana = ScaleMana(GetMana());

            if (m_Caster.Deleted || !m_Caster.Alive || m_Caster.Spell != this || m_State != SpellState.Sequencing)
            {
                DoFizzle();
            }
            else if (m_Scroll != null && !(m_Scroll is Runebook) && (m_Scroll.Amount <= 0 || m_Scroll.Deleted || m_Scroll.RootParent != m_Caster || (m_Scroll is BaseWand && (((BaseWand)m_Scroll).Charges <= 0 || m_Scroll.Parent != m_Caster))))
            {
                DoFizzle();
            }
            else if (!ConsumeReagents())
            {
                if (m_Caster.Map != Map.Underworld)
                    m_Caster.LocalOverheadMessage(MessageType.Regular, 0x22, 1060173, (((int)Circle + 1) * 2).ToString()); // You must have at least ~1_TITHE_REQUIREMENT~ Tithing Points to use this ability,
                else
                    m_Caster.LocalOverheadMessage(MessageType.Regular, 0x22, 502630); // More reagents are needed for this spell.
            }
            else if (m_Caster.Mana < mana)
            {
                m_Caster.LocalOverheadMessage(MessageType.Regular, 0x22, 502625); // Insufficient mana for this spell.
            }
            else if (Core.AOS && (m_Caster.Frozen || m_Caster.Paralyzed))
            {
                m_Caster.SendLocalizedMessage(502646); // You cannot cast a spell while frozen.
                DoFizzle();
            }
            else if (CheckFizzle())
            {
                m_Caster.Mana -= mana;

                if (m_Scroll is SpellScroll)
                    m_Scroll.Consume();
                else if (m_Scroll is BaseWand)
                    ((BaseWand)m_Scroll).ConsumeCharge(m_Caster);

                if (m_Scroll is BaseWand)
                {
                    bool m = m_Scroll.Movable;

                    m_Scroll.Movable = false;

                    if (ClearHandsOnCast)
                        m_Caster.ClearHands();

                    m_Scroll.Movable = m;
                }
                else
                {
                    if (ClearHandsOnCast)
                        m_Caster.ClearHands();
                }

                int karma = ComputeKarmaAward();

                if (karma != 0)
                    Misc.Titles.AwardKarma(Caster, karma, true);

                if (TransformationSpell.UnderTransformation(m_Caster, typeof(VampiricEmbraceSpell)))
                {
                    bool garlic = false;

                    for (int i = 0; !garlic && i < m_Info.Reagents.Length; ++i)
                        garlic = (m_Info.Reagents[i] == Reagent.Garlic);

                    if (garlic)
                    {
                        m_Caster.SendLocalizedMessage(1061651); // The garlic burns you!
                        AOS.Damage(m_Caster, Utility.RandomMinMax(17, 23), 100, 0, 0, 0, 0);
                    }
                }

                return true;
            }
            else
            {
                DoFizzle();
            }

            return false;
        }

        public bool CheckBSequence(Mobile target)
        {
            return CheckBSequence(target, false);
        }

        public bool CheckBSequence(Mobile target, bool allowDead)
        {
            if (!target.Alive && !allowDead)
            {
                m_Caster.SendLocalizedMessage(501857); // This spell won't work on that!
                return false;
            }
            else if (Caster.CanBeBeneficial(target, true, allowDead) && CheckSequence())
            {
                Caster.DoBeneficial(target);
                return true;
            }
            else
            {
                return false;
            }
        }

        public bool CheckHSequence(Mobile target)
        {
            if (!target.Alive)
            {
                m_Caster.SendLocalizedMessage(501857); // This spell won't work on that!
                return false;
            }
            else if (Caster.CanBeHarmful(target) && CheckSequence())
            {
                Caster.DoHarmful(target);
                return true;
            }
            else
            {
                return false;
            }
        }

        private class AnimTimer : Timer
        {
            private Spell m_Spell;

            public AnimTimer(Spell spell, int count) : base(TimeSpan.Zero, AnimateDelay, count)
            {
                m_Spell = spell;

                Priority = TimerPriority.FiftyMS;
            }

            protected override void OnTick()
            {
                if (m_Spell.State != SpellState.Casting || m_Spell.m_Caster.Spell != m_Spell)
                {
                    Stop();
                    return;
                }

                if (!m_Spell.Caster.Mounted && m_Spell.Caster.Body.IsHuman && m_Spell.m_Info.Action >= 0)
                    m_Spell.Caster.Animate(m_Spell.m_Info.Action, 7, 1, true, false, 0);

                if (!Running)
                    m_Spell.m_AnimTimer = null;
            }
        }

        private class CastTimer : Timer
        {
            private Spell m_Spell;

            public CastTimer(Spell spell, TimeSpan castDelay) : base(castDelay)
            {
                m_Spell = spell;

                Priority = TimerPriority.TwentyFiveMS;
            }

            protected override void OnTick()
            {
                if (m_Spell.m_State == SpellState.Casting && m_Spell.m_Caster.Spell == m_Spell)
                {
                    m_Spell.m_State = SpellState.Sequencing;
                    m_Spell.m_CastTimer = null;
                    m_Spell.m_Caster.OnSpellCast(m_Spell);
                    m_Spell.m_Caster.Region.OnSpellCast(m_Spell.m_Caster, m_Spell);
                    m_Spell.m_Caster.NextSpellTime = DateTime.Now + m_Spell.GetCastRecovery();// Spell.NextSpellDelay;

                    Target originalTarget = m_Spell.m_Caster.Target;

                    m_Spell.OnCast();

                    if (m_Spell.m_Caster.Player && m_Spell.m_Caster.Target != originalTarget && m_Spell.Caster.Target != null)
                        m_Spell.m_Caster.Target.BeginTimeout(m_Spell.m_Caster, TimeSpan.FromSeconds(30.0));

                    m_Spell.m_CastTimer = null;
                }
            }
        }
    }
}