/*
 * Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

using Framework.Constants;
using Framework.Dynamic;
using Game.DataStorage;
using Game.Entities;
using Game.Maps;
using Game.Network.Packets;
using Game.Scripting;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;

namespace Game.Spells
{
    public enum AuraObjectType
    {
        Unit,
        DynObj
    }
    public enum AuraRemoveMode
    {
        None = 0,
        Default = 1,       // scripted remove, remove by stack with aura with different ids and sc aura remove
        Cancel,
        EnemySpell,       // dispel and absorb aura destroy
        Expire,            // aura duration has ended
        ByDeath
    }
    public enum AuraFlags
    {
        None = 0x00,
        NoCaster = 0x01,
        Positive = 0x02,
        Duration = 0x04,
        Scalable = 0x08,
        Negative = 0x10,
        Unk20 = 0x20
    }

    public class AuraApplication
    {
        Unit _target;
        Aura _base;
        AuraRemoveMode _removeMode;                  // Store info for know remove aura reason
        byte _slot;                                   // Aura slot on unit
        AuraFlags _flags;                                  // Aura info flag
        uint _effectsToApply;                         // Used only at spell hit to determine which effect should be applied
        bool _needClientUpdate;
        uint _effectMask;

        public AuraApplication(Unit target, Unit caster, Aura aura, uint effMask)
        {
            _target = target;
            _base = aura;
            _removeMode = AuraRemoveMode.None;
            _slot = SpellConst.MaxAuras;
            _flags = AuraFlags.None;
            _effectsToApply = effMask;
            _needClientUpdate = false;

            Contract.Assert(GetTarget() != null && GetBase() != null);

            // Try find slot for aura
            byte slot = 0;
            // Lookup for auras already applied from spell
            foreach (AuraApplication visibleAura in GetTarget().GetVisibleAuras())
            {
                if (slot < visibleAura.GetSlot())
                    break;

                ++slot;
            }

            // Register Visible Aura
            if (slot < SpellConst.MaxAuras)
            {
                _slot = slot;
                GetTarget().SetVisibleAura(this);
                _needClientUpdate = true;
                Log.outDebug(LogFilter.Spells, "Aura: {0} Effect: {1} put to unit visible auras slot: {2}", GetBase().GetId(), GetEffectMask(), slot);
            }
            else
                Log.outError(LogFilter.Spells, "Aura: {0} Effect: {1} could not find empty unit visible slot", GetBase().GetId(), GetEffectMask());


            _InitFlags(caster, effMask);
        }

        public void _Remove()
        {
            // update for out of range group members
            if (GetSlot() < SpellConst.MaxAuras)
            {
                GetTarget().RemoveVisibleAura(this);
                ClientUpdate(true);
            }
        }

        void _InitFlags(Unit caster, uint effMask)
        {
            // mark as selfcasted if needed
            _flags |= (GetBase().GetCasterGUID() == GetTarget().GetGUID()) ? AuraFlags.NoCaster : AuraFlags.None;

            // aura is casted by self or an enemy
            // one negative effect and we know aura is negative
            if (IsSelfcasted() || caster == null || !caster.IsFriendlyTo(GetTarget()))
            {
                bool negativeFound = false;
                foreach (SpellEffectInfo effect in GetBase().GetSpellEffectInfos())
                {
                    if (effect != null && (Convert.ToBoolean((1 << (int)effect.EffectIndex) & effMask) && !GetBase().GetSpellInfo().IsPositiveEffect(effect.EffectIndex)))
                    {
                        negativeFound = true;
                        break;
                    }
                }
                _flags |= negativeFound ? AuraFlags.Negative : AuraFlags.Positive;
            }
            // aura is casted by friend
            // one positive effect and we know aura is positive
            else
            {
                bool positiveFound = false;
                foreach (SpellEffectInfo effect in GetBase().GetSpellEffectInfos())
                {
                    if (effect != null && (Convert.ToBoolean((1 << (int)effect.EffectIndex) & effMask) && GetBase().GetSpellInfo().IsPositiveEffect(effect.EffectIndex)))
                    {
                        positiveFound = true;
                        break;
                    }
                }
                _flags |= positiveFound ? AuraFlags.Positive : AuraFlags.Negative;
            }

            if (GetBase().GetSpellInfo().HasAttribute(SpellAttr8.AuraSendAmount) ||
                GetBase().HasEffectType(AuraType.ModMaxCharges) ||
                GetBase().HasEffectType(AuraType.ChargeRecoveryMod) ||
                GetBase().HasEffectType(AuraType.ChargeRecoveryMultiplier))
                _flags |= AuraFlags.Scalable;
        }

        public void _HandleEffect(uint effIndex, bool apply)
        {
            AuraEffect aurEff = GetBase().GetEffect(effIndex);
            if (aurEff == null)
            {
                Log.outError(LogFilter.Spells, "Aura {0} has no effect at effectIndex {1} but _HandleEffect was called", GetBase().GetSpellInfo().Id, effIndex);
                return;
            }
            Contract.Assert(aurEff != null);
            Contract.Assert(HasEffect(effIndex) == (!apply));
            Contract.Assert(Convert.ToBoolean((1 << (int)effIndex) & _effectsToApply));
            Log.outDebug(LogFilter.Spells, "AuraApplication._HandleEffect: {0}, apply: {1}: amount: {2}", aurEff.GetAuraType(), apply, aurEff.GetAmount());

            if (apply)
            {
                Contract.Assert(!Convert.ToBoolean(_effectMask & (1 << (int)effIndex)));
                _effectMask |= (uint)(1 << (int)effIndex);
                aurEff.HandleEffect(this, AuraEffectHandleModes.Real, true);
            }
            else
            {
                Contract.Assert(Convert.ToBoolean(_effectMask & (1 << (int)effIndex)));
                _effectMask &= ~(uint)(1 << (int)effIndex);
                aurEff.HandleEffect(this, AuraEffectHandleModes.Real, false);
            }
            SetNeedClientUpdate();
        }

        public void SetNeedClientUpdate()
        {
            if (_needClientUpdate || GetRemoveMode() != AuraRemoveMode.None)
                return;

            _needClientUpdate = true;
            _target.SetVisibleAuraUpdate(this);
        }

        public void BuildUpdatePacket(ref AuraInfo auraInfo, bool remove)
        {
            Contract.Assert(_target.HasVisibleAura(this) != remove);

            auraInfo.Slot = GetSlot();
            if (remove)
                return;

            auraInfo.AuraData.HasValue = true;

            Aura aura = GetBase();

            AuraDataInfo auraData = auraInfo.AuraData.Value;
            auraData.CastID = aura.GetCastGUID();
            auraData.SpellID = (int)aura.GetId();
            auraData.SpellXSpellVisualID = (int)aura.GetSpellXSpellVisualId();
            auraData.Flags = GetFlags();
            if (aura.GetMaxDuration() > 0 && !aura.GetSpellInfo().HasAttribute(SpellAttr5.HideDuration))
                auraData.Flags |= AuraFlags.Duration;

            auraData.ActiveFlags = GetEffectMask();
            if (!aura.GetSpellInfo().HasAttribute(SpellAttr11.ScalesWithItemLevel))
                auraData.CastLevel = aura.GetCasterLevel();
            else
                auraData.CastLevel = (ushort)aura.GetCastItemLevel();

            // send stack amount for aura which could be stacked (never 0 - causes incorrect display) or charges
            // stack amount has priority over charges (checked on retail with spell 50262)
            auraData.Applications = aura.GetSpellInfo().StackAmount != 0 ? aura.GetStackAmount() : aura.GetCharges();
            if (!auraData.Flags.HasAnyFlag(AuraFlags.NoCaster))
                auraData.CastUnit.Set(aura.GetCasterGUID());

            if (auraData.Flags.HasAnyFlag(AuraFlags.Duration))
            {
                auraData.Duration.Set(aura.GetMaxDuration());
                auraData.Remaining.Set(aura.GetDuration());
            }

            if (auraData.Flags.HasAnyFlag(AuraFlags.Scalable))
            {
                auraData.Points = new float[GetBase().GetAuraEffects().Length];
                foreach (AuraEffect effect in GetBase().GetAuraEffects())
                    if (effect != null && HasEffect(effect.GetEffIndex()))       // Not all of aura's effects have to be applied on every target
                        auraData.Points[effect.GetEffIndex()] = effect.GetAmount();
            }
        }

        public void ClientUpdate(bool remove = false)
        {
            _needClientUpdate = false;

            AuraUpdate update = new AuraUpdate();
            update.UpdateAll = false;
            update.UnitGUID = GetTarget().GetGUID();

            AuraInfo auraInfo = new AuraInfo();
            BuildUpdatePacket(ref auraInfo, remove);
            update.Auras.Add(auraInfo);

            _target.SendMessageToSet(update, true);
        }

        public Unit GetTarget() { return _target; }
        public Aura GetBase() { return _base; }

        public byte GetSlot() { return _slot; }
        public AuraFlags GetFlags() { return _flags; }
        public uint GetEffectMask() { return _effectMask; }
        public bool HasEffect(uint effect)
        {
            Contract.Assert(effect < SpellConst.MaxEffects);
            return Convert.ToBoolean(_effectMask & (1 << (int)effect));
        }
        public bool IsPositive() { return _flags.HasAnyFlag(AuraFlags.Positive); }
        bool IsSelfcasted() { return !_flags.HasAnyFlag(AuraFlags.NoCaster); }
        public uint GetEffectsToApply() { return _effectsToApply; }

        public void SetRemoveMode(AuraRemoveMode mode) { _removeMode = mode; }
        public AuraRemoveMode GetRemoveMode() { return _removeMode; }
        public bool HasRemoveMode() { return _removeMode != 0; }

        public bool IsNeedClientUpdate() { return _needClientUpdate; }
    }

    public class Aura
    {
        const int UPDATE_TARGET_MAP_INTERVAL = 500;

        public Aura(SpellInfo spellproto, ObjectGuid castId, WorldObject owner, Unit caster, Item castItem, ObjectGuid casterGUID, int castItemLevel)
        {
            m_spellInfo = spellproto;
            m_castGuid = castId;
            m_casterGuid = !casterGUID.IsEmpty() ? casterGUID : caster.GetGUID();
            m_castItemGuid = castItem != null ? castItem.GetGUID() : ObjectGuid.Empty;
            m_castItemLevel = castItemLevel;
            m_spellXSpellVisualId = caster ? caster.GetCastSpellXSpellVisualId(spellproto) : spellproto.GetSpellXSpellVisualId();
            m_applyTime = Time.UnixTime;
            m_owner = owner;
            m_timeCla = 0;
            m_updateTargetMapInterval = 0;
            m_casterLevel = caster != null ? caster.getLevel() : m_spellInfo.SpellLevel;
            m_procCharges = 0;
            m_stackAmount = 1;
            m_isRemoved = false;
            m_isSingleTarget = false;
            m_isUsingCharges = false;
            m_lastProcAttemptTime = (DateTime.Now - TimeSpan.FromSeconds(10));
            m_lastProcSuccessTime = (DateTime.Now - TimeSpan.FromSeconds(120));

            var powers = Global.DB2Mgr.GetSpellPowers(GetId(), caster ? caster.GetMap().GetDifficultyID() : Difficulty.None);
            foreach (var power in powers)
                if (power.ManaCostPerSecond != 0 || power.ManaCostPercentagePerSecond > 0.0f)
                    m_periodicCosts.Add(power);

            if (!m_periodicCosts.Empty())
                m_timeCla = 1 * Time.InMilliseconds;

            m_maxDuration = CalcMaxDuration(caster);
            m_duration = m_maxDuration;
            m_procCharges = CalcMaxCharges(caster);
            m_isUsingCharges = m_procCharges != 0;
            // m_casterLevel = cast item level/caster level, caster level should be saved to db, confirmed with sniffs
        }

        public T GetScript<T>(string scriptName) where T : AuraScript
        {
            return (T)GetScriptByName(scriptName);
        }

        public AuraScript GetScriptByName(string scriptName)
        {
            foreach (var auraScript in m_loadedScripts)
                if (auraScript._GetScriptName().Equals(scriptName))
                    return auraScript;
            return null;
        }

        public void _InitEffects(uint effMask, Unit caster, int[] baseAmount)
        {
            // shouldn't be in constructor - functions in AuraEffect.AuraEffect use polymorphism
            _spellEffectInfos = m_spellInfo.GetEffectsForDifficulty(GetOwner().GetMap().GetDifficultyID());

            _effects = new AuraEffect[GetSpellEffectInfos().Length];

            foreach (SpellEffectInfo effect in GetSpellEffectInfos())
            {
                if (effect != null && Convert.ToBoolean(effMask & (1 << (int)effect.EffectIndex)))
                    _effects[effect.EffectIndex] = new AuraEffect(this, effect.EffectIndex, baseAmount != null ? baseAmount[effect.EffectIndex] : (int?)null, caster);
            }
        }

        public Unit GetCaster()
        {
            if (m_owner.GetGUID() == m_casterGuid)
                return GetUnitOwner();
            AuraApplication aurApp = GetApplicationOfTarget(m_casterGuid);
            if (aurApp != null)
                return aurApp.GetTarget();

            return Global.ObjAccessor.GetUnit(m_owner, m_casterGuid);
        }

        public AuraObjectType GetAuraType()
        {
            return (m_owner.GetTypeId() == TypeId.DynamicObject) ? AuraObjectType.DynObj : AuraObjectType.Unit;
        }

        public virtual void _ApplyForTarget(Unit target, Unit caster, AuraApplication auraApp)
        {
            Contract.Assert(target != null);
            Contract.Assert(auraApp != null);
            // aura mustn't be already applied on target
            //Contract.Assert(!IsAppliedOnTarget(target.GetGUID()) && "Aura._ApplyForTarget: aura musn't be already applied on target");

            m_applications[target.GetGUID()] = auraApp;

            // set infinity cooldown state for spells
            if (caster != null && caster.IsTypeId(TypeId.Player))
            {
                if (m_spellInfo.HasAttribute(SpellAttr0.DisabledWhileActive))
                {
                    Item castItem = !m_castItemGuid.IsEmpty() ? caster.ToPlayer().GetItemByGuid(m_castItemGuid) : null;
                    caster.GetSpellHistory().StartCooldown(m_spellInfo, castItem != null ? castItem.GetEntry() : 0, null, true);
                }
            }
        }
        public virtual void _UnapplyForTarget(Unit target, Unit caster, AuraApplication auraApp)
        {
            Contract.Assert(target != null);
            Contract.Assert(auraApp.HasRemoveMode());
            Contract.Assert(auraApp != null);

            var app = m_applications.LookupByKey(target.GetGUID());

            /// @todo Figure out why this happens
            if (app == null)
            {
                Log.outError(LogFilter.Spells, "Aura._UnapplyForTarget, target: {0}, caster: {1}, spell: {2} was not found in owners application map!",
                target.GetGUID().ToString(), caster ? caster.GetGUID().ToString() : "", auraApp.GetBase().GetSpellInfo().Id);
                Contract.Assert(false);
            }

            // aura has to be already applied
            Contract.Assert(app == auraApp);
            m_applications.Remove(target.GetGUID());

            m_removedApplications.Add(auraApp);

            // reset cooldown state for spells
            if (caster != null && GetSpellInfo().IsCooldownStartedOnEvent())
                // note: item based cooldowns and cooldown spell mods with charges ignored (unknown existed cases)
                caster.GetSpellHistory().SendCooldownEvent(GetSpellInfo());
        }

        // removes aura from all targets
        // and marks aura as removed
        public void _Remove(AuraRemoveMode removeMode)
        {
            Contract.Assert(!m_isRemoved);
            m_isRemoved = true;
            foreach (var pair in m_applications.ToList())
            {
                AuraApplication aurApp = pair.Value;
                Unit target = aurApp.GetTarget();
                target._UnapplyAura(aurApp, removeMode);
            }

            if (m_dropEvent != null)
            {
                m_dropEvent.ScheduleAbort();
                m_dropEvent = null;
            }
        }

        void UpdateTargetMap(Unit caster, bool apply = true)
        {
            if (IsRemoved())
                return;

            m_updateTargetMapInterval = UPDATE_TARGET_MAP_INTERVAL;

            // fill up to date target list
            //       target, effMask
            Dictionary<Unit, uint> targets = new Dictionary<Unit, uint>();

            FillTargetMap(ref targets, caster);

            List<Unit> targetsToRemove = new List<Unit>();

            // mark all auras as ready to remove
            foreach (var app in m_applications)
            {
                var existing = targets.FirstOrDefault(p => p.Key == app.Value.GetTarget());
                // not found in current area - remove the aura
                if (existing.Key == null)
                    targetsToRemove.Add(app.Value.GetTarget());
                else
                {
                    // needs readding - remove now, will be applied in next update cycle
                    // (dbcs do not have auras which apply on same type of targets but have different radius, so this is not really needed)
                    if (app.Value.GetEffectMask() != existing.Value || !CanBeAppliedOn(existing.Key))
                        targetsToRemove.Add(app.Value.GetTarget());
                    // nothing todo - aura already applied
                    // remove from auras to register list
                    targets.Remove(existing.Key);
                }
            }
            // register auras for units
            foreach (var unit in targets.Keys.ToList())
            {
                // aura mustn't be already applied on target
                AuraApplication aurApp = GetApplicationOfTarget(unit.GetGUID());
                if (aurApp != null)
                {
                    // the core created 2 different units with same guid
                    // this is a major failue, which i can't fix right now
                    // let's remove one unit from aura list
                    // this may cause area aura "bouncing" between 2 units after each update
                    // but because we know the reason of a crash we can remove the assertion for now
                    if (aurApp.GetTarget() != unit)
                    {
                        // remove from auras to register list
                        targets.Remove(unit);
                        continue;
                    }
                    else
                    {
                        // ok, we have one unit twice in target map (impossible, but...)
                        Contract.Assert(false);
                    }
                }

                var value = targets[unit];

                bool addUnit = true;
                // check target immunities
                for (byte effIndex = 0; effIndex < SpellConst.MaxEffects; ++effIndex)
                {
                    if (unit.IsImmunedToSpellEffect(GetSpellInfo(), effIndex))
                        value &= (byte)~(1 << effIndex);
                }
                if (value == 0 || unit.IsImmunedToSpell(GetSpellInfo())
                    || !CanBeAppliedOn(unit))
                    addUnit = false;

                if (addUnit && !unit.IsHighestExclusiveAura(this, true))
                    addUnit = false;

                if (addUnit)
                {
                    // persistent area aura does not hit flying targets
                    if (GetAuraType() == AuraObjectType.DynObj)
                    {
                        if (unit.IsInFlight())
                            addUnit = false;
                    }
                    // unit auras can not stack with each other
                    else // (GetAuraType() == UNIT_AURA_TYPE)
                    {
                        // Allow to remove by stack when aura is going to be applied on owner
                        if (unit != m_owner)
                        {
                            // check if not stacking aura already on target
                            // this one prevents unwanted usefull buff loss because of stacking and prevents overriding auras periodicaly by 2 near area aura owners
                            foreach (var iter in unit.GetAppliedAuras())
                            {
                                Aura aura = iter.Value.GetBase();
                                if (!CanStackWith(aura))
                                {
                                    addUnit = false;
                                    break;
                                }
                            }
                        }
                    }
                }
                if (!addUnit)
                    targets.Remove(unit);
                else
                {
                    // owner has to be in world, or effect has to be applied to self
                    if (!m_owner.IsSelfOrInSameMap(unit))
                    {
                        /// @todo There is a crash caused by shadowfiend load addon
                        Log.outFatal(LogFilter.Spells, "Aura {0}: Owner {1} (map {2}) is not in the same map as target {3} (map {4}).", GetSpellInfo().Id,
                            m_owner.GetName(), m_owner.IsInWorld ? (int)m_owner.GetMap().GetId() : -1,
                            unit.GetName(), unit.IsInWorld ? (int)unit.GetMap().GetId() : -1);
                    }
                    unit._CreateAuraApplication(this, value);
                }
            }

            // remove auras from units no longer needing them
            foreach (var unit in targetsToRemove)
            {
                AuraApplication aurApp = GetApplicationOfTarget(unit.GetGUID());
                if (aurApp != null)
                    unit._UnapplyAura(aurApp, AuraRemoveMode.Default);
            }

            if (!apply)
                return;

            // apply aura effects for units
            foreach (var pair in targets)
            {
                AuraApplication aurApp = GetApplicationOfTarget(pair.Key.GetGUID());
                if (aurApp != null)
                {
                    // owner has to be in world, or effect has to be applied to self
                    Contract.Assert((!m_owner.IsInWorld && m_owner == pair.Key) || m_owner.IsInMap(pair.Key));
                    pair.Key._ApplyAura(aurApp, pair.Value);
                }
            }
        }

        // targets have to be registered and not have effect applied yet to use this function
        public void _ApplyEffectForTargets(uint effIndex)
        {
            // prepare list of aura targets
            List<Unit> targetList = new List<Unit>();
            foreach (var app in m_applications.Values)
            {
                if (Convert.ToBoolean(app.GetEffectsToApply() & (1 << (int)effIndex)) && !app.HasEffect(effIndex))
                    targetList.Add(app.GetTarget());
            }

            // apply effect to targets
            foreach (var unit in targetList)
            {
                if (GetApplicationOfTarget(unit.GetGUID()) != null)
                {
                    // owner has to be in world, or effect has to be applied to self
                    Contract.Assert((!GetOwner().IsInWorld && GetOwner() == unit) || GetOwner().IsInMap(unit));
                    unit._ApplyAuraEffect(this, effIndex);
                }
            }
        }

        public void UpdateOwner(uint diff, WorldObject owner)
        {
            Contract.Assert(owner == m_owner);

            Unit caster = GetCaster();
            // Apply spellmods for channeled auras
            // used for example when triggered spell of spell:10 is modded
            Spell modSpell = null;
            Player modOwner = null;
            if (caster != null)
            {
                modOwner = caster.GetSpellModOwner();
                if (modOwner != null)
                {
                    modSpell = modOwner.FindCurrentSpellBySpellId(GetId());
                    if (modSpell != null)
                        modOwner.SetSpellModTakingSpell(modSpell, true);
                }
            }

            Update(diff, caster);

            if (m_updateTargetMapInterval <= diff)
                UpdateTargetMap(caster);
            else
                m_updateTargetMapInterval -= (int)diff;

            // update aura effects
            foreach (AuraEffect effect in GetAuraEffects())
                if (effect != null)
                    effect.Update(diff, caster);

            // remove spellmods after effects update
            if (modSpell != null)
                modOwner.SetSpellModTakingSpell(modSpell, false);

            _DeleteRemovedApplications();
        }

        void Update(uint diff, Unit caster)
        {
            if (m_duration > 0)
            {
                m_duration -= (int)diff;
                if (m_duration < 0)
                    m_duration = 0;

                // handle manaPerSecond/manaPerSecondPerLevel
                if (m_timeCla != 0)
                {
                    if (m_timeCla > diff)
                        m_timeCla -= (int)diff;
                    else if (caster != null)
                    {
                        if (!m_periodicCosts.Empty())
                        {
                            m_timeCla += (int)(1000 - diff);

                            foreach (SpellPowerRecord power in m_periodicCosts)
                            {
                                if (power.RequiredAura != 0 && !caster.HasAura(power.RequiredAura))
                                    continue;

                                int manaPerSecond = (int)power.ManaCostPerSecond;
                                if (power.PowerType != PowerType.Health)
                                    manaPerSecond += MathFunctions.CalculatePct(caster.GetMaxPower(power.PowerType), power.ManaCostPercentagePerSecond);
                                else
                                    manaPerSecond += (int)MathFunctions.CalculatePct(caster.GetMaxHealth(), power.ManaCostPercentagePerSecond);

                                if (manaPerSecond != 0)
                                {
                                    if (power.PowerType == PowerType.Health)
                                    {
                                        if ((int)caster.GetHealth() > manaPerSecond)
                                            caster.ModifyHealth(-manaPerSecond);
                                        else
                                            Remove();
                                    }
                                    else if (caster.GetPower(power.PowerType) >= manaPerSecond)
                                        caster.ModifyPower(power.PowerType, -manaPerSecond);
                                    else
                                        Remove();
                                }
                            }
                        }
                    }
                }
            }
        }

        int CalcMaxDuration(Unit caster)
        {
            Player modOwner = null;
            int maxDuration;

            if (caster != null)
            {
                modOwner = caster.GetSpellModOwner();
                maxDuration = caster.CalcSpellDuration(m_spellInfo);
            }
            else
                maxDuration = m_spellInfo.GetDuration();

            if (IsPassive() && m_spellInfo.DurationEntry == null)
                maxDuration = -1;

            // IsPermanent() checks max duration (which we are supposed to calculate here)
            if (maxDuration != -1 && modOwner != null)
                modOwner.ApplySpellMod(GetId(), SpellModOp.Duration, ref maxDuration);
            return maxDuration;
        }

        public void SetDuration(int duration, bool withMods = false)
        {
            if (withMods)
            {
                Unit caster = GetCaster();
                if (caster)
                {
                    Player modOwner = caster.GetSpellModOwner();
                    if (modOwner)
                        modOwner.ApplySpellMod(GetId(), SpellModOp.Duration, ref duration);
                }
            }

            m_duration = duration;
            SetNeedClientUpdateForTargets();
        }

        public void RefreshDuration(bool withMods = false)
        {
            Unit caster = GetCaster();
            if (withMods && caster)
            {
                int duration = m_spellInfo.GetMaxDuration();
                // Calculate duration of periodics affected by haste.
                if (caster.HasAuraTypeWithAffectMask(AuraType.PeriodicHaste, m_spellInfo) || m_spellInfo.HasAttribute(SpellAttr5.HasteAffectDuration))
                    duration = (int)(duration * caster.GetFloatValue(UnitFields.ModCastSpeed));

                SetMaxDuration(duration);
                SetDuration(duration);
            }
            else
                SetDuration(GetMaxDuration());

            if (!m_periodicCosts.Empty())
                m_timeCla = 1 * Time.InMilliseconds;
        }

        void RefreshTimers()
        {
            m_maxDuration = CalcMaxDuration();
            bool resetPeriodic = true;
            if (m_spellInfo.HasAttribute(SpellAttr8.DontResetPeriodicTimer))
            {
                int minPeriod = m_maxDuration;
                for (byte i = 0; i < SpellConst.MaxEffects; ++i)
                {
                    AuraEffect eff = GetEffect(i);
                    if (eff != null)
                    {
                        int period = eff.GetPeriod();
                        if (period != 0)
                            minPeriod = Math.Min(period, minPeriod);
                    }
                }

                // If only one tick remaining, roll it over into new duration
                if (GetDuration() <= minPeriod)
                {
                    m_maxDuration += GetDuration();
                    resetPeriodic = false;
                }
            }

            RefreshDuration();
            Unit caster = GetCaster();
            for (byte i = 0; i < SpellConst.MaxEffects; ++i)
                if (HasEffect(i))
                    GetEffect(i).CalculatePeriodic(caster, resetPeriodic, false);
        }

        public void SetCharges(int charges)
        {
            if (m_procCharges == charges)
                return;
            m_procCharges = (byte)charges;
            m_isUsingCharges = m_procCharges != 0;
            SetNeedClientUpdateForTargets();
        }

        public bool ModCharges(int num, AuraRemoveMode removeMode = AuraRemoveMode.Default)
        {
            if (IsUsingCharges())
            {
                int charges = m_procCharges + num;
                int maxCharges = CalcMaxCharges();

                // limit charges (only on charges increase, charges may be changed manually)
                if ((num > 0) && (charges > maxCharges))
                    charges = maxCharges;
                // we're out of charges, remove
                else if (charges <= 0)
                {
                    Remove(removeMode);
                    return true;
                }

                SetCharges((byte)charges);
            }
            return false;
        }

        public void ModChargesDelayed(int num, AuraRemoveMode removeMode = AuraRemoveMode.Default)
        {
            m_dropEvent = null;
            ModCharges(num, removeMode);
        }

        public void DropChargeDelayed(uint delay, AuraRemoveMode removeMode = AuraRemoveMode.Default)
        {
            // aura is already during delayed charge drop
            if (m_dropEvent != null)
                return;

            // only units have events
            Unit owner = m_owner.ToUnit();
            if (!owner)
                return;

            m_dropEvent = new ChargeDropEvent(this, removeMode);
            owner.m_Events.AddEvent(m_dropEvent, owner.m_Events.CalculateTime(delay));
        }

        public void SetStackAmount(byte stackAmount)
        {
            m_stackAmount = stackAmount;
            Unit caster = GetCaster();

            List<AuraApplication> applications = GetApplicationList();
            foreach (var appt in applications)
                if (!appt.HasRemoveMode())
                    HandleAuraSpecificMods(appt, caster, false, true);

            foreach (AuraEffect effect in GetAuraEffects())
                if (effect != null)
                    effect.ChangeAmount(effect.CalculateAmount(caster), false, true);

            foreach (var app in applications)
            {
                if (!app.HasRemoveMode())
                {
                    HandleAuraSpecificMods(app, caster, true, true);
                    HandleAuraSpecificPeriodics(app, caster);
                }
            }

            SetNeedClientUpdateForTargets();
        }

        public bool ModStackAmount(int num, AuraRemoveMode removeMode = AuraRemoveMode.Default)
        {
            int stackAmount = m_stackAmount + num;

            // limit the stack amount (only on stack increase, stack amount may be changed manually)
            if ((num > 0) && (stackAmount > (int)m_spellInfo.StackAmount))
            {
                // not stackable aura - set stack amount to 1
                if (m_spellInfo.StackAmount == 0)
                    stackAmount = 1;
                else
                    stackAmount = (int)m_spellInfo.StackAmount;
            }
            // we're out of stacks, remove
            else if (stackAmount <= 0)
            {
                Remove(removeMode);
                return true;
            }

            bool refresh = stackAmount >= GetStackAmount();

            // Update stack amount
            SetStackAmount((byte)stackAmount);

            if (refresh)
            {
                RefreshSpellMods();
                RefreshTimers();

                // reset charges
                SetCharges(CalcMaxCharges());
                // FIXME: not a best way to synchronize charges, but works
                for (byte i = 0; i < SpellConst.MaxEffects; ++i)
                {
                    AuraEffect aurEff = GetEffect(i);
                    if (aurEff != null)
                        if (aurEff.GetAuraType() == AuraType.AddFlatModifier || aurEff.GetAuraType() == AuraType.AddPctModifier)
                        {
                            SpellModifier mod = aurEff.GetSpellModifier();
                            if (mod != null)
                                mod.charges = GetCharges();
                        }
                }
            }
            SetNeedClientUpdateForTargets();
            return false;
        }

        void RefreshSpellMods()
        {
            foreach (var app in m_applications.Values)
            {
                Player player = app.GetTarget().ToPlayer();
                if (player != null)
                    player.RestoreAllSpellMods(0, this);
            }
        }

        public bool HasMoreThanOneEffectForType(AuraType auraType)
        {
            uint count = 0;
            foreach (SpellEffectInfo effect in GetSpellEffectInfos())
            {
                if (effect != null && HasEffect(effect.EffectIndex) && effect.ApplyAuraName == auraType)
                    ++count;
            }

            return count > 1;
        }

        public bool IsArea()
        {
            foreach (SpellEffectInfo effect in GetSpellEffectInfos())
            {
                if (effect != null && HasEffect(effect.EffectIndex) && effect.IsAreaAuraEffect())
                    return true;
            }
            return false;
        }

        public bool IsPassive()
        {
            return m_spellInfo.IsPassive();
        }

        public bool IsDeathPersistent()
        {
            return GetSpellInfo().IsDeathPersistent();
        }

        public bool CanBeSaved()
        {
            if (IsPassive())
                return false;

            if (GetCasterGUID() != GetOwner().GetGUID())
                if (GetSpellInfo().IsSingleTarget())
                    return false;

            // Can't be saved - aura handler relies on calculated amount and changes it
            if (HasEffectType(AuraType.ConvertRune))
                return false;

            // No point in saving this, since the stable dialog can't be open on aura load anyway.
            if (HasEffectType(AuraType.OpenStable))
                return false;

            // Can't save vehicle auras, it requires both caster & target to be in world
            if (HasEffectType(AuraType.ControlVehicle))
                return false;

            // Incanter's Absorbtion - considering the minimal duration and problems with aura stacking
            // we skip saving this aura
            // Also for some reason other auras put as MultiSlot crash core on keeping them after restart,
            // so put here only these for which you are sure they get removed
            switch (GetId())
            {
                case 44413: // Incanter's Absorption
                case 40075: // Fel Flak Fire
                case 55849: // Power Spark
                    return false;
            }

            // When a druid logins, he doesnt have either eclipse power, nor the marker auras, nor the eclipse buffs. Dont save them.
            if (GetId() == 67483 || GetId() == 67484 || GetId() == 48517 || GetId() == 48518)
                return false;

            // don't save auras removed by proc system
            if (IsUsingCharges() && GetCharges() == 0)
                return false;

            return true;
        }

        public bool IsSingleTargetWith(Aura aura)
        {
            // Same spell?
            if (GetSpellInfo().IsRankOf(aura.GetSpellInfo()))
                return true;

            SpellSpecificType spec = GetSpellInfo().GetSpellSpecific();
            // spell with single target specific types
            switch (spec)
            {
                case SpellSpecificType.Judgement:
                case SpellSpecificType.MagePolymorph:
                    if (aura.GetSpellInfo().GetSpellSpecific() == spec)
                        return true;
                    break;
                default:
                    break;
            }

            if (HasEffectType(AuraType.ControlVehicle) && aura.HasEffectType(AuraType.ControlVehicle))
                return true;

            return false;
        }

        public void UnregisterSingleTarget()
        {
            Contract.Assert(m_isSingleTarget);
            Unit caster = GetCaster();
            Contract.Assert(caster != null);
            caster.GetSingleCastAuras().Remove(this);
            SetIsSingleTarget(false);
        }

        public int CalcDispelChance(Unit auraTarget, bool offensive)
        {
            // we assume that aura dispel chance is 100% on start
            // need formula for level difference based chance
            int resistChance = 0;

            // Apply dispel mod from aura caster
            Unit caster = GetCaster();
            if (caster != null)
            {
                Player modOwner = caster.GetSpellModOwner();
                if (modOwner != null)
                    modOwner.ApplySpellMod(GetId(), SpellModOp.ResistDispelChance, ref resistChance);
            }

            // Dispel resistance from target SPELL_AURA_MOD_DISPEL_RESIST
            // Only affects offensive dispels
            if (offensive && auraTarget != null)
                resistChance += auraTarget.GetTotalAuraModifier(AuraType.ModDispelResist);

            resistChance = resistChance < 0 ? 0 : resistChance;
            resistChance = resistChance > 100 ? 100 : resistChance;
            return 100 - resistChance;
        }

        public AuraKey GenerateKey(out uint recalculateMask)
        {
            AuraKey key = new AuraKey(GetCasterGUID(), GetCastItemGUID(), GetId(), 0);
            recalculateMask = 0;
            for (int i = 0; i < _effects.Length; ++i)
            {
                AuraEffect effect = _effects[i];
                if (effect != null)
                {
                    key.EffectMask |= 1u << i;
                    if (effect.CanBeRecalculated())
                        recalculateMask |= 1u << i;
                }
            }

            return key;
        }

        public void SetLoadedState(int maxduration, int duration, int charges, byte stackamount, uint recalculateMask, int[] amount)
        {
            m_maxDuration = maxduration;
            m_duration = duration;
            m_procCharges = (byte)charges;
            m_isUsingCharges = m_procCharges != 0;
            m_stackAmount = stackamount;
            Unit caster = GetCaster();
            foreach (AuraEffect effect in GetAuraEffects())
            {
                if (effect == null)
                    continue;

                effect.SetAmount(amount[effect.GetEffIndex()]);
                effect.SetCanBeRecalculated(Convert.ToBoolean(recalculateMask & (1 << effect.GetEffIndex())));
                effect.CalculatePeriodic(caster, false, true);
                effect.CalculateSpellMod();
                effect.RecalculateAmount(caster);
            }
        }

        public bool HasEffectType(AuraType type)
        {
            foreach (var eff in GetAuraEffects())
                if (eff != null && HasEffect(eff.GetEffIndex()) && eff.GetAuraType() == type)
                    return true;

            return false;
        }

        public void RecalculateAmountOfEffects()
        {
            Contract.Assert(!IsRemoved());
            Unit caster = GetCaster();
            foreach (AuraEffect effect in GetAuraEffects())
                if (effect != null && !IsRemoved())
                    effect.RecalculateAmount(caster);
        }

        public void HandleAllEffects(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply)
        {
            Contract.Assert(!IsRemoved());
            foreach (AuraEffect effect in GetAuraEffects())
                if (effect != null && !IsRemoved())
                    effect.HandleEffect(aurApp, mode, apply);
        }

        public List<AuraApplication> GetApplicationList()
        {
            var applicationList = new List<AuraApplication>();
            foreach (var app in m_applications.Values)
            {
                if (app.GetEffectMask() != 0)
                    applicationList.Add(app);
            }

            return applicationList;
        }

        public void SetNeedClientUpdateForTargets()
        {
            foreach (var app in m_applications.Values)
                app.SetNeedClientUpdate();
        }

        // trigger effects on real aura apply/remove
        public void HandleAuraSpecificMods(AuraApplication aurApp, Unit caster, bool apply, bool onReapply)
        {
            Unit target = aurApp.GetTarget();
            AuraRemoveMode removeMode = aurApp.GetRemoveMode();
            // handle spell_area table
            var saBounds = Global.SpellMgr.GetSpellAreaForAuraMapBounds(GetId());
            if (saBounds != null)
            {
                uint zone, area;
                target.GetZoneAndAreaId(out zone, out area);

                foreach (var bound in saBounds)
                {
                    // some auras remove at aura remove
                    if (!bound.IsFitToRequirements((Player)target, zone, area))
                        target.RemoveAurasDueToSpell(bound.spellId);
                    // some auras applied at aura apply
                    else if (bound.autocast)
                    {
                        if (!target.HasAura(bound.spellId))
                            target.CastSpell(target, bound.spellId, true);
                    }
                }
            }

            // handle spell_linked_spell table
            if (!onReapply)
            {
                // apply linked auras
                if (apply)
                {
                    var spellTriggered = Global.SpellMgr.GetSpellLinked((int)GetId() + (int)SpellLinkedType.Aura);
                    if (spellTriggered != null)
                    {
                        foreach (var spell in spellTriggered)
                        {
                            if (spell < 0)
                                target.ApplySpellImmune(GetId(), SpellImmunity.Id, (uint)-spell, true);
                            else if (caster != null)
                                caster.AddAura((uint)spell, target);
                        }
                    }
                }
                else
                {
                    // remove linked auras
                    var spellTriggered = Global.SpellMgr.GetSpellLinked(-(int)GetId());
                    if (spellTriggered != null)
                    {
                        foreach (var spell in spellTriggered)
                        {
                            if (spell < 0)
                                target.RemoveAurasDueToSpell((uint)-spell);
                            else if (removeMode != AuraRemoveMode.ByDeath)
                                target.CastSpell(target, (uint)spell, true, null, null, GetCasterGUID());
                        }
                    }
                    spellTriggered = Global.SpellMgr.GetSpellLinked((int)GetId() + (int)SpellLinkedType.Aura);
                    if (spellTriggered != null)
                    {
                        foreach (var id in spellTriggered)
                        {
                            if (id < 0)
                                target.ApplySpellImmune(GetId(), SpellImmunity.Id, (uint)-id, false);
                            else
                                target.RemoveAura((uint)id, GetCasterGUID(), 0, removeMode);
                        }
                    }
                }
            }
            else if (apply)
            {
                // modify stack amount of linked auras
                var spellTriggered = Global.SpellMgr.GetSpellLinked((int)GetId() + (int)SpellLinkedType.Aura);
                if (spellTriggered != null)
                {
                    foreach (var id in spellTriggered)
                    {
                        if (id > 0)
                        {
                            Aura triggeredAura = target.GetAura((uint)id, GetCasterGUID());
                            if (triggeredAura != null)
                                triggeredAura.ModStackAmount(GetStackAmount() - triggeredAura.GetStackAmount());
                        }
                    }
                }
            }

            // mods at aura apply
            if (apply)
            {
                switch (GetSpellInfo().SpellFamilyName)
                {
                    case SpellFamilyNames.Generic:
                        switch (GetId())
                        {
                            case 32474: // Buffeting Winds of Susurrus
                                if (target.IsTypeId(TypeId.Player))
                                    target.ToPlayer().ActivateTaxiPathTo(506, GetId());
                                break;
                            case 33572: // Gronn Lord's Grasp, becomes stoned
                                if (GetStackAmount() >= 5 && !target.HasAura(33652))
                                    target.CastSpell(target, 33652, true);
                                break;
                            case 50836: //Petrifying Grip, becomes stoned
                                if (GetStackAmount() >= 5 && !target.HasAura(50812))
                                    target.CastSpell(target, 50812, true);
                                break;
                            case 60970: // Heroic Fury (remove Intercept cooldown)
                                if (target.IsTypeId(TypeId.Player))
                                    target.GetSpellHistory().ResetCooldown(20252, true);
                                break;
                        }
                        break;
                    case SpellFamilyNames.Druid:
                        if (caster == null)
                            break;
                        // Rejuvenation
                        if (GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x10u) && GetEffect(0) != null)
                        {
                            // Druid T8 Restoration 4P Bonus
                            if (caster.HasAura(64760))
                            {
                                int heal = GetEffect(0).GetAmount();
                                caster.CastCustomSpell(target, 64801, heal, 0, 0, true, null, GetEffect(0));
                            }
                        }
                        break;
                    case SpellFamilyNames.Rogue:
                        // Sprint (skip non player casted spells by category)
                        if (GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x40u) && GetSpellInfo().GetCategory() == 44)
                            // in official maybe there is only one icon?
                            if (target.HasAura(58039)) // Glyph of Blurred Speed
                                target.CastSpell(target, 61922, true); // Sprint (waterwalk)
                        break;
                }
            }
            // mods at aura remove
            else
            {
                switch (GetSpellInfo().SpellFamilyName)
                {
                    case SpellFamilyNames.Generic:
                        switch (GetId())
                        {
                            case 61987: // Avenging Wrath
                                // Remove the immunity shield marker on Avenging Wrath removal if Forbearance is not present
                                if (target.HasAura(61988) && !target.HasAura(25771))
                                    target.RemoveAura(61988);
                                break;
                            case 72368: // Shared Suffering
                            case 72369:
                                if (caster != null)
                                {
                                    AuraEffect aurEff = GetEffect(0);
                                    if (aurEff != null)
                                    {
                                        int remainingDamage = (int)(aurEff.GetAmount() * (aurEff.GetTotalTicks() - aurEff.GetTickNumber()));
                                        if (remainingDamage > 0)
                                            caster.CastCustomSpell(caster, 72373, 0, remainingDamage, 0, true);
                                    }
                                }
                                break;
                        }
                        break;
                    case SpellFamilyNames.Mage:
                        switch (GetId())
                        {
                            case 66: // Invisibility
                                if (removeMode != AuraRemoveMode.Expire)
                                    break;
                                target.CastSpell(target, 32612, true, null, GetEffect(1));
                                target.CombatStop();
                                break;
                            default:
                                break;
                        }
                        break;
                    case SpellFamilyNames.Priest:
                        if (caster == null)
                            break;
                        // Power word: shield
                        if (removeMode == AuraRemoveMode.EnemySpell && GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x00000001u))
                        {
                            // Rapture
                            Aura aura = caster.GetAuraOfRankedSpell(47535);
                            if (aura != null)
                            {
                                // check cooldown
                                if (caster.IsTypeId(TypeId.Player))
                                {
                                    if (caster.GetSpellHistory().HasCooldown(aura.GetId()))
                                    {
                                        // This additional check is needed to add a minimal delay before cooldown in in effect
                                        // to allow all bubbles broken by a single damage source proc mana return
                                        if (caster.GetSpellHistory().GetRemainingCooldown(aura.GetSpellInfo()) <= 11 * Time.InMilliseconds)
                                            break;
                                    }
                                    else    // and add if needed
                                        caster.GetSpellHistory().AddCooldown(aura.GetId(), 0, TimeSpan.FromSeconds(12));
                                }

                                // effect on caster
                                AuraEffect aurEff = aura.GetEffect(0);
                                if (aurEff != null)
                                {
                                    float multiplier = aurEff.GetAmount();
                                    int basepoints0 = MathFunctions.CalculatePct(caster.GetMaxPower(PowerType.Mana), multiplier);
                                    caster.CastCustomSpell(caster, 47755, basepoints0, 0, 0, true);
                                }
                            }
                        }
                        break;
                    case SpellFamilyNames.Rogue:
                        // Remove Vanish on stealth remove
                        if (GetId() == 1784)
                            target.RemoveAurasWithFamily(SpellFamilyNames.Rogue, new FlagArray128(0x0000800, 0, 0), target.GetGUID());
                        break;
                    case SpellFamilyNames.Paladin:
                        // Remove the immunity shield marker on Forbearance removal if AW marker is not present
                        if (GetId() == 25771 && target.HasAura(61988) && !target.HasAura(61987))
                            target.RemoveAura(61988);
                        break;
                    case SpellFamilyNames.Hunter:
                        // Glyph of Freezing Trap
                        if (GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x00000008u))
                            if (caster != null && caster.HasAura(56845))
                                target.CastSpell(target, 61394, true);
                        break;
                }
            }

            // mods at aura apply or remove
            switch (GetSpellInfo().SpellFamilyName)
            {
                case SpellFamilyNames.Rogue:
                    // Stealth
                    if (GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x00400000u))
                    {
                        // Master of subtlety
                        AuraEffect aurEff = target.GetAuraEffect(31223, 0);
                        if (aurEff != null)
                        {
                            if (!apply)
                                target.CastSpell(target, 31666, true);
                            else
                            {
                                int basepoints0 = aurEff.GetAmount();
                                target.CastCustomSpell(target, 31665, basepoints0, 0, 0, true);
                            }
                        }
                        break;
                    }
                    break;
                case SpellFamilyNames.Hunter:
                    switch (GetId())
                    {
                        case 19574: // Bestial Wrath
                            // The Beast Within cast on owner if talent present
                            Unit owner = target.GetOwner();
                            if (owner != null)
                            {
                                // Search talent
                                if (owner.HasAura(34692))
                                {
                                    if (apply)
                                        owner.CastSpell(owner, 34471, true, null, GetEffect(0));
                                    else
                                        owner.RemoveAurasDueToSpell(34471);
                                }
                            }
                            break;
                    }
                    break;
                case SpellFamilyNames.Paladin:
                    switch (GetId())
                    {
                        case 31821:
                            // Aura Mastery Triggered Spell Handler
                            // If apply Concentration Aura . trigger . apply Aura Mastery Immunity
                            // If remove Concentration Aura . trigger . remove Aura Mastery Immunity
                            // If remove Aura Mastery . trigger . remove Aura Mastery Immunity
                            // Do effects only on aura owner
                            if (GetCasterGUID() != target.GetGUID())
                                break;

                            if (apply)
                            {
                                if ((GetSpellInfo().Id == 31821 && target.HasAura(19746, GetCasterGUID())) || (GetSpellInfo().Id == 19746 && target.HasAura(31821)))
                                    target.CastSpell(target, 64364, true);
                            }
                            else
                                target.RemoveAurasDueToSpell(64364, GetCasterGUID());
                            break;
                        case 31842: // Divine Favor
                            // Item - Paladin T10 Holy 2P Bonus
                            if (target.HasAura(70755))
                            {
                                if (apply)
                                    target.CastSpell(target, 71166, true);
                                else
                                    target.RemoveAurasDueToSpell(71166);
                            }
                            break;
                    }
                    break;
                case SpellFamilyNames.Warlock:
                    // Drain Soul - If the target is at or below 25% health, Drain Soul causes four times the normal damage
                    if (GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x00004000u))
                    {
                        if (caster == null)
                            break;
                        if (apply)
                        {
                            if (target != caster && !target.HealthAbovePct(25))
                                caster.CastSpell(caster, 100001, true);
                        }
                        else
                        {
                            if (target != caster)
                                caster.RemoveAurasDueToSpell(GetId());
                            else
                                caster.RemoveAurasDueToSpell(100001);
                        }
                    }
                    break;
            }
        }

        public void HandleAuraSpecificPeriodics(AuraApplication aurApp, Unit caster)
        {
            Unit target = aurApp.GetTarget();

            if (!caster || aurApp.HasRemoveMode())
                return;

            foreach (AuraEffect effect in GetAuraEffects())
            {
                if (effect == null || effect.IsAreaAuraEffect() || effect.IsEffect(SpellEffectName.PersistentAreaAura))
                    continue;

                switch (effect.GetSpellEffectInfo().ApplyAuraName)
                {
                    case AuraType.PeriodicDamage:
                    case AuraType.PeriodicDamagePercent:
                    case AuraType.PeriodicLeech:
                        {
                            // ignore non positive values (can be result apply spellmods to aura damage
                            uint damage = (uint)Math.Max(effect.GetAmount(), 0);

                            // Script Hook For HandlePeriodicDamageAurasTick -- Allow scripts to change the Damage pre class mitigation calculations
                            Global.ScriptMgr.ModifyPeriodicDamageAurasTick(target, caster, ref damage);

                            effect.SetDonePct(caster.SpellDamagePctDone(target, m_spellInfo, DamageEffectType.DOT)); // Calculate done percentage first!
                            effect.SetDamage((int)(caster.SpellDamageBonusDone(target, m_spellInfo, damage, DamageEffectType.DOT, effect.GetSpellEffectInfo(), GetStackAmount()) * effect.GetDonePct()));
                            effect.SetCritChance(caster.GetUnitSpellCriticalChance(target, m_spellInfo, m_spellInfo.GetSchoolMask()));
                            break;
                        }
                    case AuraType.PeriodicHeal:
                    case AuraType.ObsModHealth:
                        {
                            // ignore non positive values (can be result apply spellmods to aura damage
                            uint damage = (uint)Math.Max(effect.GetAmount(), 0);

                            // Script Hook For HandlePeriodicDamageAurasTick -- Allow scripts to change the Damage pre class mitigation calculations
                            Global.ScriptMgr.ModifyPeriodicDamageAurasTick(target, caster, ref damage);

                            effect.SetDonePct(caster.SpellHealingPctDone(target, m_spellInfo)); // Calculate done percentage first!
                            effect.SetDamage((int)(caster.SpellHealingBonusDone(target, m_spellInfo, damage, DamageEffectType.DOT, effect.GetSpellEffectInfo(), GetStackAmount()) * effect.GetDonePct()));
                            effect.SetCritChance(caster.GetUnitSpellCriticalChance(target, m_spellInfo, m_spellInfo.GetSchoolMask()));
                            break;
                        }
                    default:
                        break;
                }
            }
        }

        bool CanBeAppliedOn(Unit target)
        {
            // unit not in world or during remove from world
            if (!target.IsInWorld || target.IsDuringRemoveFromWorld())
            {
                // area auras mustn't be applied
                if (GetOwner() != target)
                    return false;
                // not selfcasted single target auras mustn't be applied
                if (GetCasterGUID() != GetOwner().GetGUID() && GetSpellInfo().IsSingleTarget())
                    return false;
                return true;
            }
            else
                return CheckAreaTarget(target);
        }

        bool CheckAreaTarget(Unit target)
        {
            return CallScriptCheckAreaTargetHandlers(target);
        }

        public bool CanStackWith(Aura existingAura)
        {
            // Can stack with self
            if (this == existingAura)
                return true;

            // Dynobj auras always stack
            if (GetAuraType() == AuraObjectType.DynObj || existingAura.GetAuraType() == AuraObjectType.DynObj)
                return true;

            SpellInfo existingSpellInfo = existingAura.GetSpellInfo();
            bool sameCaster = GetCasterGUID() == existingAura.GetCasterGUID();

            // passive auras don't stack with another rank of the spell cast by same caster
            if (IsPassive() && sameCaster && (m_spellInfo.IsDifferentRankOf(existingSpellInfo) || (m_spellInfo.Id == existingSpellInfo.Id && m_castItemGuid.IsEmpty())))
                return false;

            foreach (SpellEffectInfo effect in existingAura.GetSpellEffectInfos())
            {
                // prevent remove triggering aura by triggered aura
                if (effect != null && effect.TriggerSpell == GetId())
                    return true;
            }

            foreach (SpellEffectInfo effect in GetSpellEffectInfos())
            {
                // prevent remove triggered aura by triggering aura refresh
                if (effect != null && effect.TriggerSpell == existingAura.GetId())
                    return true;
            }

            // check spell specific stack rules
            if (m_spellInfo.IsAuraExclusiveBySpecificWith(existingSpellInfo)
                || (sameCaster && m_spellInfo.IsAuraExclusiveBySpecificPerCasterWith(existingSpellInfo)))
                return false;

            // check spell group stack rules
            switch (Global.SpellMgr.CheckSpellGroupStackRules(m_spellInfo, existingSpellInfo))
            {
                case SpellGroupStackRule.Exclusive:
                case SpellGroupStackRule.ExclusiveHighest: // if it reaches this point, existing aura is lower/equal
                    return false;
                case SpellGroupStackRule.ExclusiveFromSameCaster:
                    if (sameCaster)
                        return false;
                    break;
                case SpellGroupStackRule.Default:
                case SpellGroupStackRule.ExclusiveSameEffect:
                default:
                    break;
            }

            if (m_spellInfo.SpellFamilyName != existingSpellInfo.SpellFamilyName)
                return true;

            if (!sameCaster)
            {
                // Channeled auras can stack if not forbidden by db or aura type
                if (existingAura.GetSpellInfo().IsChanneled())
                    return true;

                if (m_spellInfo.HasAttribute(SpellAttr3.StackForDiffCasters))
                    return true;

                // check same periodic auras
                for (byte i = 0; i < SpellConst.MaxEffects; i++)
                {
                    SpellEffectInfo effect = GetSpellEffectInfo(i);
                    if (effect == null)
                        continue;

                    switch (effect.ApplyAuraName)
                    {
                        // DOT or HOT from different casters will stack
                        case AuraType.PeriodicDamage:
                        case AuraType.PeriodicDummy:
                        case AuraType.PeriodicHeal:
                        case AuraType.PeriodicTriggerSpell:
                        case AuraType.PeriodicEnergize:
                        case AuraType.PeriodicManaLeech:
                        case AuraType.PeriodicLeech:
                        case AuraType.PowerBurn:
                        case AuraType.ObsModPower:
                        case AuraType.ObsModHealth:
                        case AuraType.PeriodicTriggerSpellWithValue:
                            SpellEffectInfo existingEffect = GetSpellEffectInfo(i);
                            // periodic auras which target areas are not allowed to stack this way (replenishment for example)
                            if (effect.IsTargetingArea() || (existingEffect != null && existingEffect.IsTargetingArea()))
                                break;
                            return true;
                        default:
                            break;
                    }
                }
            }

            if (HasEffectType(AuraType.ControlVehicle) && existingAura.HasEffectType(AuraType.ControlVehicle))
            {
                Vehicle veh = null;
                if (GetOwner().ToUnit())
                    veh = GetOwner().ToUnit().GetVehicleKit();

                if (!veh)           // We should probably just let it stack. Vehicle system will prevent undefined behaviour later
                    return true;

                if (veh.GetAvailableSeatCount() == 0)
                    return false;   // No empty seat available

                return true; // Empty seat available (skip rest)
            }

            if (HasEffectType(AuraType.ShowConfirmationPrompt) || HasEffectType(AuraType.ShowConfirmationPromptWithDifficulty))
                if (existingAura.HasEffectType(AuraType.ShowConfirmationPrompt) || existingAura.HasEffectType(AuraType.ShowConfirmationPromptWithDifficulty))
                    return false;

            // spell of same spell rank chain
            if (m_spellInfo.IsRankOf(existingSpellInfo))
            {
                // don't allow passive area auras to stack
                if (m_spellInfo.IsMultiSlotAura() && !IsArea())
                    return true;
                if (!GetCastItemGUID().IsEmpty() && !existingAura.GetCastItemGUID().IsEmpty())
                    if (GetCastItemGUID() != existingAura.GetCastItemGUID() && m_spellInfo.HasAttribute(SpellCustomAttributes.EnchantProc))
                        return true;
                // same spell with same caster should not stack
                return false;
            }

            return true;
        }

        public bool IsProcOnCooldown(DateTime now)
        {
            return m_procCooldown > now;
        }

        public void AddProcCooldown(DateTime cooldownEnd)
        {
            m_procCooldown = cooldownEnd;
        }

        void PrepareProcToTrigger(AuraApplication aurApp, ProcEventInfo eventInfo, DateTime now)
        {
            bool prepare = CallScriptPrepareProcHandlers(aurApp, eventInfo);
            if (!prepare)
                return;

            // take one charge, aura expiration will be handled in Aura.TriggerProcOnEvent (if needed)
            if (IsUsingCharges())
            {
                --m_procCharges;
                SetNeedClientUpdateForTargets();
            }

            SpellProcEntry procEntry = Global.SpellMgr.GetSpellProcEntry(GetId());

            Contract.Assert(procEntry != null);

            // cooldowns should be added to the whole aura (see 51698 area aura)
            AddProcCooldown(now + TimeSpan.FromMilliseconds(procEntry.Cooldown));
        }

        bool IsProcTriggeredOnEvent(AuraApplication aurApp, ProcEventInfo eventInfo, DateTime now)
        {
            SpellProcEntry procEntry = Global.SpellMgr.GetSpellProcEntry(GetId());
            // only auras with spell proc entry can trigger proc
            if (procEntry == null)
                return false;

            // check if we have charges to proc with
            if (IsUsingCharges() && GetCharges() == 0)
                return false;

            // check proc cooldown
            if (IsProcOnCooldown(now))
                return false;

            /// @todo
            // something about triggered spells triggering, and add extra attack effect

            // do checks against db data
            if (!Global.SpellMgr.CanSpellTriggerProcOnEvent(procEntry, eventInfo))
                return false;

            // do checks using conditions table
            if (!Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.SpellProc, GetId(), eventInfo.GetActor(), eventInfo.GetActionTarget()))
                return false;

            // AuraScript Hook
            bool check = CallScriptCheckProcHandlers(aurApp, eventInfo);
            if (!check)
                return false;

            /// @todo
            // do allow additional requirements for procs
            // this is needed because this is the last moment in which you can prevent aura charge drop on proc
            // and possibly a way to prevent default checks (if there're going to be any)

            // Check if current equipment meets aura requirements
            // do that only for passive spells
            /// @todo this needs to be unified for all kinds of auras
            Unit target = aurApp.GetTarget();
            if (IsPassive() && target.IsTypeId(TypeId.Player))
            {
                if (GetSpellInfo().EquippedItemClass == ItemClass.Weapon)
                {
                    if (target.ToPlayer().IsInFeralForm())
                        return false;

                    if (eventInfo.GetDamageInfo() != null)
                    {
                        WeaponAttackType attType = eventInfo.GetDamageInfo().GetAttackType();
                        Item item = null;
                        if (attType == WeaponAttackType.BaseAttack || attType == WeaponAttackType.RangedAttack)
                            item = target.ToPlayer().GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand);
                        else if (attType == WeaponAttackType.OffAttack)
                            item = target.ToPlayer().GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand);

                        if (item == null || item.IsBroken() || item.GetTemplate().GetClass() != ItemClass.Weapon || !Convert.ToBoolean((1 << (int)item.GetTemplate().GetSubClass()) & GetSpellInfo().EquippedItemSubClassMask))
                            return false;
                    }
                }
                else if (GetSpellInfo().EquippedItemClass == ItemClass.Armor)
                {
                    // Check if player is wearing shield
                    Item item = target.ToPlayer().GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand);
                    if (item == null || item.IsBroken() || item.GetTemplate().GetClass() != ItemClass.Armor || !Convert.ToBoolean((1 << (int)item.GetTemplate().GetSubClass()) & GetSpellInfo().EquippedItemSubClassMask))
                        return false;
                }
            }

            return RandomHelper.randChance(CalcProcChance(procEntry, eventInfo));
        }

        float CalcProcChance(SpellProcEntry procEntry, ProcEventInfo eventInfo)
        {
            float chance = procEntry.Chance;
            // calculate chances depending on unit with caster's data
            // so talents modifying chances and judgements will have properly calculated proc chance
            Unit caster = GetCaster();
            if (caster != null)
            {
                // calculate ppm chance if present and we're using weapon
                if (eventInfo.GetDamageInfo() != null && procEntry.ProcsPerMinute != 0)
                {
                    uint WeaponSpeed = caster.GetBaseAttackTime(eventInfo.GetDamageInfo().GetAttackType());
                    chance = caster.GetPPMProcChance(WeaponSpeed, procEntry.ProcsPerMinute, GetSpellInfo());
                }
                // apply chance modifer aura, applies also to ppm chance (see improved judgement of light spell)
                Player modOwner = caster.GetSpellModOwner();
                if (modOwner != null)
                    modOwner.ApplySpellMod(GetId(), SpellModOp.ChanceOfSuccess, ref chance);
            }
            return chance;
        }

        void TriggerProcOnEvent(AuraApplication aurApp, ProcEventInfo eventInfo)
        {
            CallScriptProcHandlers(aurApp, eventInfo);

            for (byte i = 0; i < SpellConst.MaxEffects; ++i)
            {
                if (aurApp.HasEffect(i))
                    // OnEffectProc / AfterEffectProc hooks handled in AuraEffect.HandleProc()
                    GetEffect(i).HandleProc(aurApp, eventInfo);
            }

            CallScriptAfterProcHandlers(aurApp, eventInfo);

            // Remove aura if we've used last charge to proc
            if (IsUsingCharges() && GetCharges() == 0)
                Remove();
        }

        public float CalcPPMProcChance(Unit actor)
        {
            // Formula see http://us.battle.net/wow/en/forum/topic/8197741003#1
            float ppm = m_spellInfo.CalcProcPPM(actor, m_castItemLevel);
            float averageProcInterval = 60.0f / ppm;

            var currentTime = DateTime.Now;
            float secondsSinceLastAttempt = Math.Min((float)(currentTime - m_lastProcAttemptTime).TotalSeconds, 10.0f);
            float secondsSinceLastProc = Math.Min((float)(currentTime - m_lastProcSuccessTime).TotalSeconds, 1000.0f);

            float chance = Math.Max(1.0f, 1.0f + ((secondsSinceLastProc / averageProcInterval - 1.5f) * 3.0f)) * ppm * secondsSinceLastAttempt / 60.0f;
            MathFunctions.RoundToInterval(ref chance, 0.0f, 1.0f);
            return chance * 100.0f;
        }

        void _DeleteRemovedApplications()
        {
            m_removedApplications.Clear();
        }

        public void LoadScripts()
        {
            m_loadedScripts = Global.ScriptMgr.CreateAuraScripts(m_spellInfo.Id, this);
            foreach (var script in m_loadedScripts.ToList())
            {
                Log.outDebug(LogFilter.Spells, "Aura.LoadScripts: Script `{0}` for aura `{1}` is loaded now", script._GetScriptName(), m_spellInfo.Id);
                script.Register();
            }
        }

        public virtual void Remove(AuraRemoveMode removeMode = AuraRemoveMode.Default) { }        
        #region CallScripts

    bool CallScriptCheckAreaTargetHandlers(Unit target)
        {
            bool result = true;
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.CheckAreaTarget);

                foreach (var hook in auraScript.DoCheckAreaTarget)
                    result &= hook.Call(target);

                auraScript._FinishScriptCall();
            }
            return result;
        }

        public void CallScriptDispel(DispelInfo dispelInfo)
        {
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.Dispel);

                foreach (var hook in auraScript.OnDispel)
                    hook.Call(dispelInfo);

                auraScript._FinishScriptCall();
            }
        }

        public void CallScriptAfterDispel(DispelInfo dispelInfo)
        {
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.AfterDispel);

                foreach (var hook in auraScript.AfterDispel)
                    hook.Call(dispelInfo);

                auraScript._FinishScriptCall();
            }
        }

        public bool CallScriptEffectApplyHandlers(AuraEffect aurEff, AuraApplication aurApp, AuraEffectHandleModes mode)
        {
            bool preventDefault = false;
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.EffectApply, aurApp);

                foreach (var eff in auraScript.OnEffectApply)
                    if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
                        eff.Call(aurEff, mode);

                if (!preventDefault)
                    preventDefault = auraScript._IsDefaultActionPrevented();

                auraScript._FinishScriptCall();
            }

            return preventDefault;
        }

        public bool CallScriptEffectRemoveHandlers(AuraEffect aurEff, AuraApplication aurApp, AuraEffectHandleModes mode)
        {
            bool preventDefault = false;
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.EffectRemove, aurApp);

                foreach (var eff in auraScript.OnEffectRemove)
                    if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
                        eff.Call(aurEff, mode);

                if (!preventDefault)
                    preventDefault = auraScript._IsDefaultActionPrevented();

                auraScript._FinishScriptCall();
            }
            return preventDefault;
        }

        public void CallScriptAfterEffectApplyHandlers(AuraEffect aurEff, AuraApplication aurApp, AuraEffectHandleModes mode)
        {
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.EffectAfterApply, aurApp);

                foreach (var eff in auraScript.AfterEffectApply)
                    if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
                        eff.Call(aurEff, mode);

                auraScript._FinishScriptCall();
            }
        }

        public void CallScriptAfterEffectRemoveHandlers(AuraEffect aurEff, AuraApplication aurApp, AuraEffectHandleModes mode)
        {
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.EffectAfterRemove, aurApp);

                foreach (var eff in auraScript.AfterEffectRemove)
                    if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
                        eff.Call(aurEff, mode);

                auraScript._FinishScriptCall();
            }
        }

        public bool CallScriptEffectPeriodicHandlers(AuraEffect aurEff, AuraApplication aurApp)
        {
            bool preventDefault = false;
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.EffectPeriodic, aurApp);

                foreach (var eff in auraScript.OnEffectPeriodic)
                    if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
                        eff.Call(aurEff);

                if (!preventDefault)
                    preventDefault = auraScript._IsDefaultActionPrevented();

                auraScript._FinishScriptCall();
            }

            return preventDefault;
        }

        public void CallScriptEffectUpdatePeriodicHandlers(AuraEffect aurEff)
        {
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.EffectUpdatePeriodic);

                foreach (var eff in auraScript.OnEffectUpdatePeriodic)
                    if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
                        eff.Call(aurEff);

                auraScript._FinishScriptCall();
            }
        }

        public void CallScriptEffectCalcAmountHandlers(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated)
        {
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.EffectCalcAmount);
                foreach (var eff in auraScript.DoEffectCalcAmount)
                {
                    if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
                        eff.Call(aurEff, ref amount, ref canBeRecalculated);
                }

                auraScript._FinishScriptCall();
            }
        }

        public void CallScriptEffectCalcPeriodicHandlers(AuraEffect aurEff, ref bool isPeriodic, ref int amplitude)
        {
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.EffectCalcPeriodic);

                foreach (var eff in auraScript.DoEffectCalcPeriodic)
                    if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
                        eff.Call(aurEff, ref isPeriodic, ref amplitude);

                auraScript._FinishScriptCall();
            }
        }

        public void CallScriptEffectCalcSpellModHandlers(AuraEffect aurEff, ref SpellModifier spellMod)
        {
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.EffectCalcSpellmod);

                foreach (var eff in auraScript.DoEffectCalcSpellMod)
                    if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
                        eff.Call(aurEff, ref spellMod);

                auraScript._FinishScriptCall();
            }
        }

        public void CallScriptEffectAbsorbHandlers(AuraEffect aurEff, AuraApplication aurApp, DamageInfo dmgInfo, ref uint absorbAmount, ref bool defaultPrevented)
        {
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.EffectAbsorb, aurApp);

                foreach (var eff in auraScript.OnEffectAbsorb)
                    if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
                        eff.Call(aurEff, dmgInfo, ref absorbAmount);

                if (!defaultPrevented)
                    defaultPrevented = auraScript._IsDefaultActionPrevented();

                auraScript._FinishScriptCall();
            }
        }

        public void CallScriptEffectAfterAbsorbHandlers(AuraEffect aurEff, AuraApplication aurApp, DamageInfo dmgInfo, ref uint absorbAmount)
        {
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.EffectAfterAbsorb, aurApp);

                foreach (var eff in auraScript.AfterEffectAbsorb)
                    if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
                        eff.Call(aurEff, dmgInfo, ref absorbAmount);

                auraScript._FinishScriptCall();
            }
        }

        public void CallScriptEffectManaShieldHandlers(AuraEffect aurEff, AuraApplication aurApp, DamageInfo dmgInfo, ref uint absorbAmount, bool defaultPrevented)
        {
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.EffectManaShield, aurApp);

                foreach (var eff in auraScript.OnEffectManaShield)
                    if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
                        eff.Call(aurEff, dmgInfo, ref absorbAmount);

                auraScript._FinishScriptCall();
            }
        }

        public void CallScriptEffectAfterManaShieldHandlers(AuraEffect aurEff, AuraApplication aurApp, DamageInfo dmgInfo, ref uint absorbAmount)
        {
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.EffectAfterManaShield, aurApp);

                foreach (var eff in auraScript.AfterEffectManaShield)
                    if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
                        eff.Call(aurEff, dmgInfo, ref absorbAmount);

                auraScript._FinishScriptCall();
            }
        }

        public void CallScriptEffectSplitHandlers(AuraEffect aurEff, AuraApplication aurApp, DamageInfo dmgInfo, uint splitAmount)
        {
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.EffectSplit, aurApp);

                foreach (var eff in auraScript.OnEffectSplit)
                    if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
                        eff.Call(aurEff, dmgInfo, splitAmount);

                auraScript._FinishScriptCall();
            }
        }

        public bool CallScriptCheckProcHandlers(AuraApplication aurApp, ProcEventInfo eventInfo)
        {
            bool result = true;
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.CheckProc, aurApp);

                foreach (var hook in auraScript.DoCheckProc)
                    result &= hook.Call(eventInfo);

                auraScript._FinishScriptCall();
            }

            return result;
        }

        public bool CallScriptPrepareProcHandlers(AuraApplication aurApp, ProcEventInfo eventInfo)
        {
            bool prepare = true;
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.PrepareProc, aurApp);

                foreach (var eff in auraScript.DoPrepareProc)
                    eff.Call(eventInfo);

                if (prepare)
                    prepare = !auraScript._IsDefaultActionPrevented();

                auraScript._FinishScriptCall();
            }

            return prepare;
        }

        public bool CallScriptProcHandlers(AuraApplication aurApp, ProcEventInfo eventInfo)
        {
            bool handled = false;
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.Proc, aurApp);

                foreach (var hook in auraScript.OnProc)
                    hook.Call(eventInfo);

                handled |= auraScript._IsDefaultActionPrevented();
                auraScript._FinishScriptCall();
            }

            return handled;
        }

        public void CallScriptAfterProcHandlers(AuraApplication aurApp, ProcEventInfo eventInfo)
        {
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.AfterProc, aurApp);

                foreach (var hook in auraScript.AfterProc)
                    hook.Call(eventInfo);

                auraScript._FinishScriptCall();
            }
        }

        public bool CallScriptEffectProcHandlers(AuraEffect aurEff, AuraApplication aurApp, ProcEventInfo eventInfo)
        {
            bool preventDefault = false;
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.EffectProc, aurApp);

                foreach (var eff in auraScript.OnEffectProc)
                    if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
                        eff.Call(aurEff, eventInfo);

                if (!preventDefault)
                    preventDefault = auraScript._IsDefaultActionPrevented();

                auraScript._FinishScriptCall();
            }
            return preventDefault;
        }

        public void CallScriptAfterEffectProcHandlers(AuraEffect aurEff, AuraApplication aurApp, ProcEventInfo eventInfo)
        {
            foreach (var auraScript in m_loadedScripts)
            {
                auraScript._PrepareScriptCall(AuraScriptHookType.EffectAfterProc, aurApp);

                foreach (var eff in auraScript.AfterEffectProc)
                    if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
                        eff.Call(aurEff, eventInfo);

                auraScript._FinishScriptCall();
            }
        }

        #endregion

        public SpellInfo GetSpellInfo() { return m_spellInfo; }
        public uint GetId() { return m_spellInfo.Id; }
        public ObjectGuid GetCastGUID() { return m_castGuid; }
        public ObjectGuid GetCasterGUID() { return m_casterGuid; }
        public ObjectGuid GetCastItemGUID() { return m_castItemGuid; }
        public int GetCastItemLevel() { return m_castItemLevel; }
        public uint GetSpellXSpellVisualId() { return m_spellXSpellVisualId; }
        public WorldObject GetOwner() { return m_owner; }
        public Unit GetUnitOwner()
        {
            Contract.Assert(GetAuraType() == AuraObjectType.Unit);
            return m_owner.ToUnit();
        }
        public DynamicObject GetDynobjOwner()
        {
            Contract.Assert(GetAuraType() == AuraObjectType.DynObj);
            return m_owner.ToDynamicObject();
        }

        public void _RegisterForTargets()
        {
            Unit caster = GetCaster();
            UpdateTargetMap(caster, false);
        }
        public void ApplyForTargets()
        {
            Unit caster = GetCaster();
            UpdateTargetMap(caster, true);
        }

        public long GetApplyTime() { return m_applyTime; }
        public int GetMaxDuration() { return m_maxDuration; }
        public void SetMaxDuration(int duration) { m_maxDuration = duration; }
        public int CalcMaxDuration() { return CalcMaxDuration(GetCaster()); }
        public int GetDuration() { return m_duration; }
        public bool IsExpired() { return GetDuration() == 0 && m_dropEvent == null; }
        public bool IsPermanent() { return m_maxDuration == -1; }

        public byte GetCharges() { return m_procCharges; }
        public byte CalcMaxCharges() { return CalcMaxCharges(GetCaster()); }
        byte CalcMaxCharges(Unit caster)
        {
            uint maxProcCharges = m_spellInfo.ProcCharges;
            var procEntry = Global.SpellMgr.GetSpellProcEntry(GetId());
            if (procEntry != null)
                maxProcCharges = procEntry.Charges;

            if (caster != null)
            {
                Player modOwner = caster.GetSpellModOwner();
                if (modOwner != null)
                    modOwner.ApplySpellMod(GetId(), SpellModOp.Charges, ref maxProcCharges);
            }
            return (byte)maxProcCharges;
        }
        public bool DropCharge(AuraRemoveMode removeMode = AuraRemoveMode.Default) { return ModCharges(-1, removeMode); }

        public byte GetStackAmount() { return m_stackAmount; }
        public byte GetCasterLevel() { return (byte)m_casterLevel; }

        public bool IsRemovedOnShapeLost(Unit target)
        {
            return GetCasterGUID() == target.GetGUID()
                    && m_spellInfo.Stances != 0
                    && !m_spellInfo.HasAttribute(SpellAttr2.NotNeedShapeshift)
                    && !m_spellInfo.HasAttribute(SpellAttr0.NotShapeshift);
        }
        public bool IsRemoved() { return m_isRemoved; }

        public bool IsSingleTarget() { return m_isSingleTarget; }
        public void SetIsSingleTarget(bool val) { m_isSingleTarget = val; }

        public bool HasEffect(uint index)
        {
            return GetEffect(index) != null;
        }
        public AuraEffect GetEffect(uint index)
        {
            if (index >= _effects.Length)
                return null;

            return _effects[index];
        }
        public uint GetEffectMask()
        {
            uint effMask = 0;
            foreach (AuraEffect effect in GetAuraEffects())
                if (effect != null)
                    effMask |= (uint)(1 << effect.GetEffIndex());
            return effMask;
        }

        public Dictionary<ObjectGuid, AuraApplication> GetApplicationMap() { return m_applications; }
        public AuraApplication GetApplicationOfTarget(ObjectGuid guid) { return m_applications.LookupByKey(guid); }
        public bool IsAppliedOnTarget(ObjectGuid guid) { return m_applications.ContainsKey(guid); }

        public bool IsUsingCharges() { return m_isUsingCharges; }
        public void SetUsingCharges(bool val) { m_isUsingCharges = val; }

        public virtual void FillTargetMap(ref Dictionary<Unit, uint> targets, Unit caster) { }

        public AuraEffect[] GetAuraEffects() { return _effects; }

        public SpellEffectInfo[] GetSpellEffectInfos() { return _spellEffectInfos; }
        public SpellEffectInfo GetSpellEffectInfo(uint index)
        {
            if (index >= _spellEffectInfos.Length)
                return null;

            return _spellEffectInfos[index];
        }

        public void SetLastProcAttemptTime(DateTime lastProcAttemptTime) { m_lastProcAttemptTime = lastProcAttemptTime; }
        public void SetLastProcSuccessTime(DateTime lastProcSuccessTime) { m_lastProcSuccessTime = lastProcSuccessTime; }

        //Static Methods
        public static uint BuildEffectMaskForOwner(SpellInfo spellProto, uint avalibleEffectMask, WorldObject owner)
        {
            Contract.Assert(spellProto != null);
            Contract.Assert(owner != null);
            uint effMask = 0;
            switch (owner.GetTypeId())
            {
                case TypeId.Unit:
                case TypeId.Player:
                    foreach (SpellEffectInfo effect in spellProto.GetEffectsForDifficulty(owner.GetMap().GetDifficultyID()))
                    {
                        if (effect != null && effect.IsUnitOwnedAuraEffect())
                            effMask |= (uint)(1 << (int)effect.EffectIndex);
                    }
                    break;
                case TypeId.DynamicObject:
                    foreach (SpellEffectInfo effect in spellProto.GetEffectsForDifficulty(owner.GetMap().GetDifficultyID()))
                    {
                        if (effect != null && effect.Effect == SpellEffectName.PersistentAreaAura)
                            effMask |= (uint)(1 << (int)effect.EffectIndex);
                    }
                    break;
                default:
                    break;
            }
            return (effMask & avalibleEffectMask);
        }
        public static Aura TryRefreshStackOrCreate(SpellInfo spellproto, ObjectGuid castId, uint tryEffMask, WorldObject owner, Unit caster, int[] baseAmount = null, Item castItem = null, ObjectGuid casterGUID = default(ObjectGuid), int castItemLevel = -1)
        {
            bool throwway;
            return TryRefreshStackOrCreate(spellproto, castId, tryEffMask, owner, caster, out throwway, baseAmount, castItem, casterGUID);
        }
        public static Aura TryRefreshStackOrCreate(SpellInfo spellproto, ObjectGuid castId, uint tryEffMask, WorldObject owner, Unit caster, out bool refresh, int[] baseAmount, Item castItem = null, ObjectGuid casterGUID = default(ObjectGuid), int castItemLevel = -1)
        {
            Contract.Assert(spellproto != null);
            Contract.Assert(owner != null);
            Contract.Assert(caster || !casterGUID.IsEmpty());
            Contract.Assert(tryEffMask <= SpellConst.MaxEffectMask);
            refresh = false;
            uint effMask = BuildEffectMaskForOwner(spellproto, tryEffMask, owner);
            if (effMask == 0)
                return null;
            Aura foundAura = owner.ToUnit()._TryStackingOrRefreshingExistingAura(spellproto, effMask, caster, baseAmount, castItem, casterGUID, castItemLevel);
            if (foundAura != null)
            {
                // we've here aura, which script triggered removal after modding stack amount
                // check the state here, so we won't create new Aura object
                if (foundAura.IsRemoved())
                    return null;

                refresh = true;
                return foundAura;
            }
            else
                return Create(spellproto, castId, effMask, owner, caster, baseAmount, castItem, casterGUID, castItemLevel);
        }
        public static Aura TryCreate(SpellInfo spellproto, ObjectGuid castId, uint tryEffMask, WorldObject owner, Unit caster, int[] baseAmount, Item castItem = null, ObjectGuid casterGUID = default(ObjectGuid), int castItemLevel = -1)
        {
            Contract.Assert(spellproto != null);
            Contract.Assert(owner != null);
            Contract.Assert(caster != null || !casterGUID.IsEmpty());
            Contract.Assert(tryEffMask <= SpellConst.MaxEffectMask);
            uint effMask = BuildEffectMaskForOwner(spellproto, tryEffMask, owner);
            if (effMask == 0)
                return null;
            return Create(spellproto, castId, effMask, owner, caster, baseAmount, castItem, casterGUID, castItemLevel);
        }
        public static Aura Create(SpellInfo spellproto, ObjectGuid castId, uint effMask, WorldObject owner, Unit caster, int[] baseAmount, Item castItem, ObjectGuid casterGUID, int castItemLevel)
        {
            Contract.Assert(effMask != 0);
            Contract.Assert(spellproto != null);
            Contract.Assert(owner != null);
            Contract.Assert(caster != null || !casterGUID.IsEmpty());
            Contract.Assert(effMask <= SpellConst.MaxEffectMask);
            // try to get caster of aura
            if (!casterGUID.IsEmpty())
            {
                if (owner.GetGUID() == casterGUID)
                    caster = owner.ToUnit();
                else
                    caster = Global.ObjAccessor.GetUnit(owner, casterGUID);
            }
            else
                casterGUID = caster.GetGUID();

            // check if aura can be owned by owner
            if (owner.isTypeMask(TypeMask.Unit))
                if (!owner.IsInWorld || owner.ToUnit().IsDuringRemoveFromWorld())
                    // owner not in world so don't allow to own not self casted single target auras
                    if (casterGUID != owner.GetGUID() && spellproto.IsSingleTarget())
                        return null;

            Aura aura = null;
            switch (owner.GetTypeId())
            {
                case TypeId.Unit:
                case TypeId.Player:
                    aura = new UnitAura(spellproto, castId, effMask, owner, caster, baseAmount, castItem, casterGUID, castItemLevel);
                    break;
                case TypeId.DynamicObject:
                    aura = new DynObjAura(spellproto, castId, effMask, owner, caster, baseAmount, castItem, casterGUID, castItemLevel);
                    break;
                default:
                    Contract.Assert(false);
                    return null;
            }
            // aura can be removed in Unit:_AddAura call
            if (aura.IsRemoved())
                return null;
            return aura;
        }

        #region Fields
        List<AuraScript> m_loadedScripts = new List<AuraScript>();
        SpellInfo m_spellInfo;
        ObjectGuid m_castGuid;
        ObjectGuid m_casterGuid;
        ObjectGuid m_castItemGuid;
        int m_castItemLevel;
        uint m_spellXSpellVisualId;
        long m_applyTime;
        WorldObject m_owner;

        int m_maxDuration;                                // Max aura duration
        int m_duration;                                   // Current time
        int m_timeCla;                                    // Timer for power per sec calcultion
        List<SpellPowerRecord> m_periodicCosts = new List<SpellPowerRecord>();// Periodic costs
        int m_updateTargetMapInterval;                    // Timer for UpdateTargetMapOfEffect

        uint m_casterLevel;                          // Aura level (store caster level for correct show level dep amount)
        byte m_procCharges;                                // Aura charges (0 for infinite)
        byte m_stackAmount;                                // Aura stack amount

        //might need to be arrays still
        SpellEffectInfo[] _spellEffectInfos;
        AuraEffect[] _effects;
        Dictionary<ObjectGuid, AuraApplication> m_applications = new Dictionary<ObjectGuid, AuraApplication>();

        bool m_isRemoved;
        bool m_isSingleTarget;                        // true if it's a single target spell and registered at caster - can change at spell steal for example
        bool m_isUsingCharges;

        ChargeDropEvent m_dropEvent;

        DateTime m_procCooldown;
        DateTime m_lastProcAttemptTime;
        DateTime m_lastProcSuccessTime;

        List<AuraApplication> m_removedApplications = new List<AuraApplication>();
        #endregion
    }

    public class UnitAura : Aura
    {
        public UnitAura(SpellInfo spellproto, ObjectGuid castId, uint effMask, WorldObject owner, Unit caster, int[] baseAmount, Item castItem, ObjectGuid casterGUID, int castItemLevel)
            : base(spellproto, castId, owner, caster, castItem, casterGUID, castItemLevel)
        {
            m_AuraDRGroup = DiminishingGroup.None;
            LoadScripts();
            _InitEffects(effMask, caster, baseAmount);
            GetUnitOwner()._AddAura(this, caster);
        }

        public override void _ApplyForTarget(Unit target, Unit caster, AuraApplication aurApp)
        {
            base._ApplyForTarget(target, caster, aurApp);

            // register aura diminishing on apply
            if (m_AuraDRGroup != DiminishingGroup.None)
                target.ApplyDiminishingAura(m_AuraDRGroup, true);
        }
        public override void _UnapplyForTarget(Unit target, Unit caster, AuraApplication aurApp)
        {
            base._UnapplyForTarget(target, caster, aurApp);

            // unregister aura diminishing (and store last time)
            if (m_AuraDRGroup != DiminishingGroup.None)
                target.ApplyDiminishingAura(m_AuraDRGroup, false);
        }
        public override void Remove(AuraRemoveMode removeMode = AuraRemoveMode.Default)
        {
            if (IsRemoved())
                return;
            GetUnitOwner().RemoveOwnedAura(this, removeMode);
        }

        public override void FillTargetMap(ref Dictionary<Unit, uint> targets, Unit caster)
        {
            foreach (SpellEffectInfo effect in GetSpellEffectInfos())
            {
                if (effect == null || !HasEffect(effect.EffectIndex))
                    continue;

                List<Unit> targetList = new List<Unit>();
                // non-area aura
                if (effect.Effect == SpellEffectName.ApplyAura)
                {
                    targetList.Add(GetUnitOwner());
                }
                else
                {
                    float radius = effect.CalcRadius(caster);

                    if (!GetUnitOwner().HasUnitState(UnitState.Isolated))
                    {
                        switch (effect.Effect)
                        {
                            case SpellEffectName.ApplyAreaAuraParty:
                            case SpellEffectName.ApplyAreaAuraRaid:
                                {
                                    targetList.Add(GetUnitOwner());
                                    var u_check = new AnyGroupedUnitInObjectRangeCheck(GetUnitOwner(), GetUnitOwner(), radius, effect.Effect == SpellEffectName.ApplyAreaAuraRaid);
                                    var searcher = new UnitListSearcher(GetUnitOwner(), targetList, u_check);
                                    Cell.VisitAllObjects(GetUnitOwner(), searcher, radius);
                                    break;
                                }
                            case SpellEffectName.ApplyAreaAuraFriend:
                                {
                                    targetList.Add(GetUnitOwner());
                                    var u_check = new AnyFriendlyUnitInObjectRangeCheck(GetUnitOwner(), GetUnitOwner(), radius);
                                    var searcher = new UnitListSearcher(GetUnitOwner(), targetList, u_check);
                                    Cell.VisitAllObjects(GetUnitOwner(), searcher, radius);
                                    break;
                                }
                            case SpellEffectName.ApplyAreaAuraEnemy:
                                {
                                    var u_check = new AnyAoETargetUnitInObjectRangeCheck(GetUnitOwner(), GetUnitOwner(), radius);
                                    var searcher = new UnitListSearcher(GetUnitOwner(), targetList, u_check);
                                    Cell.VisitAllObjects(GetUnitOwner(), searcher, radius);
                                    break;
                                }
                            case SpellEffectName.ApplyAreaAuraPet:
                                targetList.Add(GetUnitOwner());
                                goto case SpellEffectName.ApplyAreaAuraOwner;
                            case SpellEffectName.ApplyAreaAuraOwner:
                                {
                                    Unit owner = GetUnitOwner().GetCharmerOrOwner();
                                    if (owner != null)
                                        if (GetUnitOwner().IsWithinDistInMap(owner, radius))
                                            targetList.Add(owner);
                                    break;
                                }
                        }
                    }
                }

                foreach (var unit in targetList)
                {
                    if (targets.ContainsKey(unit))
                        targets[unit] |= (byte)(1 << (int)effect.EffectIndex);
                    else
                        targets[unit] = (byte)(1 << (int)effect.EffectIndex);
                }
            }
        }

        // Allow Apply Aura Handler to modify and access m_AuraDRGroup
        public void SetDiminishGroup(DiminishingGroup group) { m_AuraDRGroup = group; }
        public DiminishingGroup GetDiminishGroup() { return m_AuraDRGroup; }

        DiminishingGroup m_AuraDRGroup;              // Diminishing
    }

    public class DynObjAura : Aura
    {
        public DynObjAura(SpellInfo spellproto, ObjectGuid castId, uint effMask, WorldObject owner, Unit caster, int[] baseAmount, Item castItem, ObjectGuid casterGUID, int castItemLevel)
            : base(spellproto, castId, owner, caster, castItem, casterGUID, castItemLevel)
        {
            LoadScripts();
            Contract.Assert(GetDynobjOwner() != null);
            Contract.Assert(GetDynobjOwner().IsInWorld);
            Contract.Assert(GetDynobjOwner().GetMap() == caster.GetMap());
            _InitEffects(effMask, caster, baseAmount);
            GetDynobjOwner().SetAura(this);

        }

        public override void Remove(AuraRemoveMode removeMode = AuraRemoveMode.Default)
        {
            if (IsRemoved())
                return;
            _Remove(removeMode);
        }

        public override void FillTargetMap(ref Dictionary<Unit, uint> targets, Unit caster)
        {
            Unit dynObjOwnerCaster = GetDynobjOwner().GetCaster();
            float radius = GetDynobjOwner().GetRadius();

            foreach (SpellEffectInfo effect in GetSpellEffectInfos())
            {
                if (effect == null || !HasEffect(effect.EffectIndex))
                    continue;

                List<Unit> targetList = new List<Unit>();
                if (effect.TargetB.GetTarget() == Targets.DestDynobjAlly || effect.TargetB.GetTarget() == Targets.UnitDestAreaAlly)
                {
                    var u_check = new AnyFriendlyUnitInObjectRangeCheck(GetDynobjOwner(), dynObjOwnerCaster, radius);
                    var searcher = new UnitListSearcher(GetDynobjOwner(), targetList, u_check);
                    Cell.VisitAllObjects(GetDynobjOwner(), searcher, radius);
                }
                else
                {
                    var u_check = new AnyAoETargetUnitInObjectRangeCheck(GetDynobjOwner(), dynObjOwnerCaster, radius);
                    var searcher = new UnitListSearcher(GetDynobjOwner(), targetList, u_check);
                    Cell.VisitAllObjects(GetDynobjOwner(), searcher, radius);
                }

                foreach (var unit in targetList)
                {
                    if (targets.ContainsKey(unit))
                        targets[unit] |= (byte)(1 << (int)effect.EffectIndex);
                    else
                        targets[unit] = (byte)(1 << (int)effect.EffectIndex);
                }
            }
        }
    }

    public class ChargeDropEvent : BasicEvent
    {
        public ChargeDropEvent(Aura aura, AuraRemoveMode mode)
        {
            _base = aura;
            _mode = mode;
        }

        public override bool Execute(ulong e_time, uint p_time)
        {
            // _base is always valid (look in Aura._Remove())
            _base.ModChargesDelayed(-1, _mode);
            return true;
        }

        Aura _base;
        AuraRemoveMode _mode;
    }

    public class AuraKey : IEquatable<AuraKey>
    {
        public AuraKey(ObjectGuid caster, ObjectGuid item, uint spellId, uint effectMask)
        {
            Caster = caster;
            Item = item;
            SpellId = spellId;
            EffectMask = effectMask;
        }

        public static bool operator ==(AuraKey first, AuraKey other)
        {
            if (ReferenceEquals(first, other))
                return true;

            if ((object)first == null || (object)other == null)
                return false;

            return first.Equals(other);
        }

        public static bool operator !=(AuraKey first, AuraKey other)
        {
            return !(first == other);
        }

        public override bool Equals(object obj)
        {
            return obj != null && obj is AuraKey && Equals((AuraKey)obj);
        }

        public bool Equals(AuraKey other)
        {
            return other.Caster == Caster && other.Item == Item && other.SpellId == SpellId && other.EffectMask == EffectMask;
        }

        public override int GetHashCode()
        {
            return new { Caster, Item, SpellId, EffectMask }.GetHashCode();
        }

        public ObjectGuid Caster;
        public ObjectGuid Item;
        public uint SpellId;
        public uint EffectMask;
    }

    public class AuraLoadEffectInfo
    {
        public int[] Amounts = new int[SpellConst.MaxEffects];
        public int[] BaseAmounts = new int[SpellConst.MaxEffects];
    }
}
