/*
 * 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 System.Collections.Generic;
using System.Diagnostics.Contracts;

namespace Game.Entities
{
    public class TempSummon : Creature
    {
        public TempSummon(SummonPropertiesRecord properties, Unit owner, bool isWorldObject) : base(isWorldObject)
        {
            m_Properties = properties;
            m_type = TempSummonType.ManualDespawn;

            m_summonerGUID = owner != null ? owner.GetGUID() : ObjectGuid.Empty;
            m_unitTypeMask |= UnitTypeMask.Summon;
        }

        public Unit GetSummoner()
        {
            return !m_summonerGUID.IsEmpty() ? Global.ObjAccessor.GetUnit(this, m_summonerGUID) : null;
        }

        public Creature GetSummonerCreatureBase()
        {
            return !m_summonerGUID.IsEmpty() ? ObjectAccessor.GetCreature(this, m_summonerGUID) : null;
        }

        public override void Update(uint diff)
        {
            base.Update(diff);

            if (m_deathState == DeathState.Dead)
            {
                UnSummon();
                return;
            }
            switch (m_type)
            {
                case TempSummonType.ManualDespawn:
                    break;
                case TempSummonType.TimedDespawn:
                    {
                        if (m_timer <= diff)
                        {
                            UnSummon();
                            return;
                        }

                        m_timer -= diff;
                        break;
                    }
                case TempSummonType.TimedDespawnOOC:
                    {
                        if (!IsInCombat())
                        {
                            if (m_timer <= diff)
                            {
                                UnSummon();
                                return;
                            }

                            m_timer -= diff;
                        }
                        else if (m_timer != m_lifetime)
                            m_timer = m_lifetime;

                        break;
                    }

                case TempSummonType.CorpseTimedDespawn:
                    {
                        if (m_deathState == DeathState.Corpse)
                        {
                            if (m_timer <= diff)
                            {
                                UnSummon();
                                return;
                            }

                            m_timer -= diff;
                        }
                        break;
                    }
                case TempSummonType.CorpseDespawn:
                    {
                        // if m_deathState is DEAD, CORPSE was skipped
                        if (m_deathState == DeathState.Corpse || m_deathState == DeathState.Dead)
                        {
                            UnSummon();
                            return;
                        }

                        break;
                    }
                case TempSummonType.DeadDespawn:
                    {
                        if (m_deathState == DeathState.Dead)
                        {
                            UnSummon();
                            return;
                        }
                        break;
                    }
                case TempSummonType.TimedOrCorpseDespawn:
                    {
                        // if m_deathState is DEAD, CORPSE was skipped
                        if (m_deathState == DeathState.Corpse || m_deathState == DeathState.Dead)
                        {
                            UnSummon();
                            return;
                        }

                        if (!IsInCombat())
                        {
                            if (m_timer <= diff)
                            {
                                UnSummon();
                                return;
                            }
                            else
                                m_timer -= diff;
                        }
                        else if (m_timer != m_lifetime)
                            m_timer = m_lifetime;
                        break;
                    }
                case TempSummonType.TimedOrDeadDespawn:
                    {
                        // if m_deathState is DEAD, CORPSE was skipped
                        if (m_deathState == DeathState.Dead)
                        {
                            UnSummon();
                            return;
                        }

                        if (!IsInCombat() && IsAlive())
                        {
                            if (m_timer <= diff)
                            {
                                UnSummon();
                                return;
                            }
                            else
                                m_timer -= diff;
                        }
                        else if (m_timer != m_lifetime)
                            m_timer = m_lifetime;
                        break;
                    }
                default:
                    UnSummon();
                    Log.outError(LogFilter.Unit, "Temporary summoned creature (entry: {0}) have unknown type {1} of ", GetEntry(), m_type);
                    break;
            }
        }

        public virtual void InitStats(uint duration)
        {
            Contract.Assert(!IsPet());

            m_timer = duration;
            m_lifetime = duration;

            if (m_type == TempSummonType.ManualDespawn)
                m_type = (duration == 0) ? TempSummonType.DeadDespawn : TempSummonType.TimedDespawn;

            Unit owner = GetSummoner();

            if (owner != null && IsTrigger() && m_spells[0] != 0)
            {
                SetFaction(owner.getFaction());
                SetLevel(owner.getLevel());
                if (owner.IsTypeId(TypeId.Player))
                  m_ControlledByPlayer = true;
            }


            if (m_Properties == null)
                return;

            if (owner != null)
            {
                int slot = m_Properties.Slot;
                if (slot > 0)
                {
                    if (!owner.m_SummonSlot[slot].IsEmpty() && owner.m_SummonSlot[slot] != GetGUID())
                    {
                        Creature oldSummon = GetMap().GetCreature(owner.m_SummonSlot[slot]);
                        if (oldSummon != null && oldSummon.IsSummon())
                            oldSummon.ToTempSummon().UnSummon();
                    }
                    owner.m_SummonSlot[slot] = GetGUID();
                }
            }

            if (m_Properties.Faction != 0)
                SetFaction(m_Properties.Faction);
            else if (IsVehicle() && owner != null) // properties should be vehicle
                SetFaction(owner.getFaction());
        }

        public virtual void InitSummon()
        {
            Unit owner = GetSummoner();
            if (owner != null)
            {
                if (owner.IsTypeId(TypeId.Unit) && owner.ToCreature().IsAIEnabled)
                    owner.ToCreature().GetAI().JustSummoned(this);
                if (IsAIEnabled)
                    GetAI().IsSummonedBy(owner);
            }
        }

        public override void UpdateObjectVisibilityOnCreate()
        {
            base.UpdateObjectVisibility(true);
        }

        public void SetTempSummonType(TempSummonType type)
        {
            m_type = type;
        }

        public virtual void UnSummon(uint msTime = 0)
        {
            if (msTime != 0)
            {
                ForcedUnsummonDelayEvent pEvent = new ForcedUnsummonDelayEvent(this);

                m_Events.AddEvent(pEvent, m_Events.CalculateTime(msTime));
                return;
            }

            Contract.Assert(!IsPet());
            if (IsPet())
            {
                ToPet().Remove(PetSaveMode.NotInSlot);
                Contract.Assert(!IsInWorld);
                return;
            }

            Unit owner = GetSummoner();
            if (owner != null && owner.IsTypeId(TypeId.Unit) && owner.ToCreature().IsAIEnabled)
                owner.ToCreature().GetAI().SummonedCreatureDespawn(this);

            AddObjectToRemoveList();
        }

        public override void RemoveFromWorld()
        {
            if (!IsInWorld)
                return;

            if (m_Properties != null)
            {
                int slot = m_Properties.Slot;
                if (slot > 0)
                {
                    Unit owner = GetSummoner();
                    if (owner != null)
                        if (owner.m_SummonSlot[slot] == GetGUID())
                            owner.m_SummonSlot[slot].Clear();
                }
            }

            if (!GetOwnerGUID().IsEmpty())
                Log.outError(LogFilter.Unit, "Unit {0} has owner guid when removed from world", GetEntry());

            base.RemoveFromWorld();
        }

        public override void SaveToDB(uint mapid, uint spawnMask, uint phaseMask) { }

        public ObjectGuid GetSummonerGUID() { return m_summonerGUID; }

        TempSummonType GetSummonType() { return m_type; }

        public uint GetTimer() { return m_timer; }

        public SummonPropertiesRecord m_Properties;
        TempSummonType m_type;
        uint m_timer;
        uint m_lifetime;
        ObjectGuid m_summonerGUID;
    }

    public class Minion : TempSummon
    {
        public Minion(SummonPropertiesRecord properties, Unit owner, bool isWorldObject)
            : base(properties, owner, isWorldObject)
        {
            m_owner = owner;
            Contract.Assert(m_owner);
            m_unitTypeMask |= UnitTypeMask.Minion;
            m_followAngle = SharedConst.PetFollowAngle;
        }

        public override void InitStats(uint duration)
        {
            base.InitStats(duration);

            SetReactState(ReactStates.Passive);

            SetCreatorGUID(GetOwner().GetGUID());
            SetFaction(GetOwner().getFaction());

            GetOwner().SetMinion(this, true);
        }

        public override void RemoveFromWorld()
        {
            if (!IsInWorld)
                return;

            GetOwner().SetMinion(this, false);
            base.RemoveFromWorld();
        }

        public bool IsGuardianPet()
        {
            return IsPet() || (m_Properties != null && m_Properties.Category == SummonCategory.Pet);
        }

        public override Unit GetOwner() { return m_owner; }

        public override float GetFollowAngle() { return m_followAngle; }

        public void SetFollowAngle(float angle) { m_followAngle = angle; }

        public bool IsPetGhoul() { return GetEntry() == 26125; } // Ghoul may be guardian or pet

        public bool IsSpiritWolf() { return GetEntry() == 29264; } // Spirit wolf from feral spirits


        Unit m_owner;
        float m_followAngle;
    }

    public class Guardian : Minion
    {
        public Guardian(SummonPropertiesRecord properties, Unit owner, bool isWorldObject)
            : base(properties, owner, isWorldObject)
        {
            m_bonusSpellDamage = 0;

            m_unitTypeMask |= UnitTypeMask.Guardian;
            if (properties != null && (properties.Type == SummonType.Pet || properties.Category == SummonCategory.Pet))
            {
                m_unitTypeMask |= UnitTypeMask.ControlableGuardian;
                InitCharmInfo();
            }
        }

        public override void InitStats(uint duration)
        {
            base.InitStats(duration);

            InitStatsForLevel(GetOwner().getLevel());

            if (GetOwner().IsTypeId(TypeId.Player) && HasUnitTypeMask(UnitTypeMask.ControlableGuardian))
                GetCharmInfo().InitCharmCreateSpells();

            SetReactState(ReactStates.Aggressive);
        }

        public override void InitSummon()
        {
            base.InitSummon();

            if (GetOwner().IsTypeId(TypeId.Player) && GetOwner().GetMinionGUID() == GetGUID()
                && GetOwner().GetCharmGUID().IsEmpty())
            {
                GetOwner().ToPlayer().CharmSpellInitialize();
            }
        }

        // @todo Move stat mods code to pet passive auras
        public bool InitStatsForLevel(uint petlevel)
        {
            CreatureTemplate cinfo = GetCreatureTemplate();
            Contract.Assert(cinfo != null);

            SetLevel(petlevel);

            //Determine pet type
            PetType petType = PetType.Max;
            if (IsPet() && GetOwner().IsTypeId(TypeId.Player))
            {
                if (GetOwner().GetClass() == Class.Warlock
                        || GetOwner().GetClass() == Class.Shaman        // Fire Elemental
                        || GetOwner().GetClass() == Class.Deathknight) // Risen Ghoul
                {
                    petType = PetType.Summon;
                }
                else if (GetOwner().GetClass() == Class.Hunter)
                {
                    petType = PetType.Hunter;
                    m_unitTypeMask |= UnitTypeMask.HunterPet;
                }
                else
                {
                    Log.outError(LogFilter.Unit, "Unknown type pet {0} is summoned by player class {1}", GetEntry(), GetOwner().GetClass());
                }
            }

            uint creature_ID = (petType == PetType.Hunter) ? 1 : cinfo.Entry;

            SetMeleeDamageSchool((SpellSchools)cinfo.DmgSchool);

            SetModifierValue(UnitMods.Armor, UnitModifierType.BaseValue, (float)petlevel * 50);

            SetBaseAttackTime(WeaponAttackType.BaseAttack, SharedConst.BaseAttackTime);
            SetBaseAttackTime(WeaponAttackType.OffAttack, SharedConst.BaseAttackTime);
            SetBaseAttackTime(WeaponAttackType.RangedAttack, SharedConst.BaseAttackTime);

            SetFloatValue(UnitFields.ModCastSpeed, 1.0f);
            SetFloatValue(UnitFields.ModCastHaste, 1.0f);

            //scale
            var cFamily = CliDB.CreatureFamilyStorage.LookupByKey(cinfo.Family);
            if (cFamily != null && cFamily.MinScale > 0.0f && petType == PetType.Hunter)
            {
                float scale;
                if (getLevel() >= cFamily.MaxScaleLevel)
                    scale = cFamily.MaxScale;
                else if (getLevel() <= cFamily.MinScaleLevel)
                    scale = cFamily.MinScale;
                else
                    scale = cFamily.MinScale + (float)(getLevel() - cFamily.MinScaleLevel) / cFamily.MaxScaleLevel * (cFamily.MaxScale - cFamily.MinScale);

                SetObjectScale(scale);
            }

            // Resistance
            for (int i = (int)SpellSchools.Holy; i < (int)SpellSchools.Max; ++i)
                SetModifierValue(UnitMods.ResistanceStart + i, UnitModifierType.BaseValue, cinfo.Resistance[i]);

            //health, mana, armor and resistance
            PetLevelInfo pInfo = Global.ObjectMgr.GetPetLevelInfo(creature_ID, petlevel);
            if (pInfo != null)                                      // exist in DB
            {
                SetCreateHealth(pInfo.health);
                if (petType != PetType.Hunter) //hunter pet use focus
                    SetCreateMana(pInfo.mana);

                if (pInfo.armor > 0)
                    SetModifierValue(UnitMods.Armor, UnitModifierType.BaseValue, pInfo.armor);

                for (byte stat = 0; stat < (int)Stats.Max; ++stat)
                    SetCreateStat((Stats)stat, pInfo.stats[stat]);
            }
            else                                            // not exist in DB, use some default fake data
            {
                // remove elite bonuses included in DB values
                CreatureBaseStats stats = Global.ObjectMgr.GetCreatureBaseStats(petlevel, cinfo.UnitClass);
                SetCreateHealth(stats.BaseHealth[cinfo.HealthScalingExpansion]);
                SetCreateMana(stats.BaseMana);

                SetCreateStat(Stats.Strength, 22);
                SetCreateStat(Stats.Agility, 22);
                SetCreateStat(Stats.Stamina, 25);
                SetCreateStat(Stats.Intellect, 28);
            }

            SetBonusDamage(0);
            switch (petType)
            {
                case PetType.Summon:
                    {
                        // the damage bonus used for pets is either fire or shadow damage, whatever is higher
                        int fire = GetOwner().GetInt32Value(PlayerFields.ModDamageDonePos + (int)SpellSchools.Fire);
                        int shadow = GetOwner().GetInt32Value(PlayerFields.ModDamageDonePos + (int)SpellSchools.Shadow);
                        int val = (fire > shadow) ? fire : shadow;
                        if (val < 0)
                            val = 0;

                        SetBonusDamage((int)(val * 0.15f));

                        SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, petlevel - (petlevel / 4));
                        SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, petlevel + (petlevel / 4));
                        break;
                    }
                case PetType.Hunter:
                    {
                        SetUInt32Value(UnitFields.PetNextLevelExp, (uint)(Global.ObjectMgr.GetXPForLevel(petlevel) * 0.05f));
                        //these formula may not be correct; however, it is designed to be close to what it should be
                        //this makes dps 0.5 of pets level
                        SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, petlevel - (petlevel / 4));
                        //damage range is then petlevel / 2
                        SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, petlevel + (petlevel / 4));
                        //damage is increased afterwards as strength and pet scaling modify attack power
                        break;
                    }
                default:
                    {
                        switch (GetEntry())
                        {
                            case 510: // mage Water Elemental
                                {
                                    SetBonusDamage((int)(GetOwner().SpellBaseDamageBonusDone(SpellSchoolMask.Frost) * 0.33f));
                                    break;
                                }
                            case 1964: //force of nature
                                {
                                    if (pInfo == null)
                                        SetCreateHealth(30 + 30 * petlevel);
                                    float bonusDmg = GetOwner().SpellBaseDamageBonusDone(SpellSchoolMask.Nature) * 0.15f;
                                    SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, petlevel * 2.5f - (petlevel / 2) + bonusDmg);
                                    SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, petlevel * 2.5f + (petlevel / 2) + bonusDmg);
                                    break;
                                }
                            case 15352: //earth elemental 36213
                                {
                                    if (pInfo == null)
                                        SetCreateHealth(100 + 120 * petlevel);
                                    SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, petlevel - (petlevel / 4));
                                    SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, petlevel + (petlevel / 4));
                                    break;
                                }
                            case 15438: //fire elemental
                                {
                                    if (pInfo == null)
                                    {
                                        SetCreateHealth(40 * petlevel);
                                        SetCreateMana(28 + 10 * petlevel);
                                    }
                                    SetBonusDamage((int)(GetOwner().SpellBaseDamageBonusDone(SpellSchoolMask.Fire) * 0.5f));
                                    SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, petlevel * 4 - petlevel);
                                    SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, petlevel * 4 + petlevel);
                                    break;
                                }
                            case 19668: // Shadowfiend
                                {
                                    if (pInfo == null)
                                    {
                                        SetCreateMana(28 + 10 * petlevel);
                                        SetCreateHealth(28 + 30 * petlevel);
                                    }
                                    int bonus_dmg = (int)(GetOwner().SpellBaseDamageBonusDone(SpellSchoolMask.Shadow) * 0.3f);
                                    SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, (petlevel * 4 - petlevel) + bonus_dmg);
                                    SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, (petlevel * 4 + petlevel) + bonus_dmg);

                                    break;
                                }
                            case 19833: //Snake Trap - Venomous Snake
                                {
                                    SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, (petlevel / 2) - 25);
                                    SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, (petlevel / 2) - 18);
                                    break;
                                }
                            case 19921: //Snake Trap - Viper
                                {
                                    SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, petlevel / 2 - 10);
                                    SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, petlevel / 2);
                                    break;
                                }
                            case 29264: // Feral Spirit
                                {
                                    if (pInfo == null)
                                        SetCreateHealth(30 * petlevel);

                                    // wolf attack speed is 1.5s
                                    SetBaseAttackTime(WeaponAttackType.BaseAttack, cinfo.BaseAttackTime);

                                    SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, (petlevel * 4 - petlevel));
                                    SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, (petlevel * 4 + petlevel));

                                    SetModifierValue(UnitMods.Armor, UnitModifierType.BaseValue, GetOwner().GetArmor() * 0.35f);  // Bonus Armor (35% of player armor)
                                    SetModifierValue(UnitMods.StatStamina, UnitModifierType.BaseValue, GetOwner().GetStat(Stats.Stamina) * 0.3f);  // Bonus Stamina (30% of player stamina)
                                    if (!HasAura(58877))//prevent apply twice for the 2 wolves
                                        AddAura(58877, this);//Spirit Hunt, passive, Spirit Wolves' attacks heal them and their master for 150% of damage done.
                                    break;
                                }
                            case 31216: // Mirror Image
                                {
                                    SetBonusDamage((int)(GetOwner().SpellBaseDamageBonusDone(SpellSchoolMask.Frost) * 0.33f));
                                    SetDisplayId(GetOwner().GetDisplayId());
                                    if (pInfo == null)
                                    {
                                        SetCreateMana(28 + 30 * petlevel);
                                        SetCreateHealth(28 + 10 * petlevel);
                                    }
                                    break;
                                }
                            case 27829: // Ebon Gargoyle
                                {
                                    if (pInfo == null)
                                    {
                                        SetCreateMana(28 + 10 * petlevel);
                                        SetCreateHealth(28 + 30 * petlevel);
                                    }
                                    SetBonusDamage((int)(GetOwner().GetTotalAttackPowerValue(WeaponAttackType.BaseAttack) * 0.5f));
                                    SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, petlevel - (petlevel / 4));
                                    SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, petlevel + (petlevel / 4));
                                    break;
                                }
                            case 28017: // Bloodworms
                                {
                                    SetCreateHealth(4 * petlevel);
                                    SetBonusDamage((int)(GetOwner().GetTotalAttackPowerValue(WeaponAttackType.BaseAttack) * 0.006f));
                                    SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, petlevel - 30 - (petlevel / 4));
                                    SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, petlevel - 30 + (petlevel / 4));
                                }
                                break;
                        }
                        break;
                    }
            }

            UpdateAllStats();

            SetFullHealth();
            SetPower(PowerType.Mana, GetMaxPower(PowerType.Mana));
            return true;
        }

        const int ENTRY_IMP = 416;
        const int ENTRY_VOIDWALKER = 1860;
        const int ENTRY_SUCCUBUS = 1863;
        const int ENTRY_FELHUNTER = 417;
        const int ENTRY_FELGUARD = 17252;
        const int ENTRY_WATER_ELEMENTAL = 510;
        const int ENTRY_TREANT = 1964;
        const int ENTRY_FIRE_ELEMENTAL = 15438;
        const int ENTRY_GHOUL = 26125;
        const int ENTRY_BLOODWORM = 28017;

        public override bool UpdateStats(Stats stat)
        {
            float value = GetTotalStatValue(stat);
            ApplyStatBuffMod(stat, m_statFromOwner[(int)stat], false);
            float ownersBonus = 0.0f;

            Unit owner = GetOwner();
            // Handle Death Knight Glyphs and Talents
            float mod = 0.75f;
            if (IsPetGhoul() && (stat == Stats.Stamina || stat == Stats.Strength))
            {
                switch (stat)
                {
                    case Stats.Stamina:
                        mod = 0.3f;
                        break;                // Default Owner's Stamina scale
                    case Stats.Strength:
                        mod = 0.7f;
                        break;                // Default Owner's Strength scale
                    default: break;
                }

                ownersBonus = owner.GetStat(stat) * mod;
                value += ownersBonus;
            }
            else if (stat == Stats.Stamina)
            {
                ownersBonus = MathFunctions.CalculatePct(owner.GetStat(Stats.Stamina), 30);
                value += ownersBonus;
            }
            //warlock's and mage's pets gain 30% of owner's intellect
            else if (stat == Stats.Intellect)
            {
                if (owner.GetClass() == Class.Warlock || owner.GetClass() == Class.Mage)
                {
                    ownersBonus = MathFunctions.CalculatePct(owner.GetStat(stat), 30);
                    value += ownersBonus;
                }
            }

            SetStat(stat, (int)value);
            m_statFromOwner[(int)stat] = ownersBonus;
            ApplyStatBuffMod(stat, m_statFromOwner[(int)stat], true);

            switch (stat)
            {
                case Stats.Strength:
                    UpdateAttackPowerAndDamage();
                    break;
                case Stats.Agility:
                    UpdateArmor();
                    break;
                case Stats.Stamina:
                    UpdateMaxHealth();
                    break;
                case Stats.Intellect:
                    UpdateMaxPower(PowerType.Mana);
                    break;
                default:
                    break;
            }

            return true;
        }

        public override bool UpdateAllStats()
        {
            for (var i = Stats.Strength; i < Stats.Max; ++i)
                UpdateStats(i);

            for (var i = PowerType.Mana; i < PowerType.Max; ++i)
                UpdateMaxPower(i);

            UpdateAllResistances();

            return true;
        }

        public override void UpdateResistances(SpellSchools school)
        {
            if (school > SpellSchools.Normal)
            {
                float value = GetTotalAuraModValue(UnitMods.ResistanceStart + (int)school);

                // hunter and warlock pets gain 40% of owner's resistance
                if (IsPet())
                    value += MathFunctions.CalculatePct(GetOwner().GetResistance(school), 40);

                SetResistance(school, (int)value);
            }
            else
                UpdateArmor();
        }

        public override void UpdateArmor()
        {
            float value = 0.0f;
            float bonus_armor = 0.0f;
            UnitMods unitMod = UnitMods.Armor;

            // hunter pets gain 35% of owner's armor value, warlock pets gain 100% of owner's armor
            if (IsHunterPet())
                bonus_armor = MathFunctions.CalculatePct(GetOwner().GetArmor(), 70);
            else if (IsPet())
                bonus_armor = GetOwner().GetArmor();

            value = GetModifierValue(unitMod, UnitModifierType.BaseValue);
            value *= GetModifierValue(unitMod, UnitModifierType.BasePCT);
            value += GetModifierValue(unitMod, UnitModifierType.TotalValue) + bonus_armor;
            value *= GetModifierValue(unitMod, UnitModifierType.TotalPCT);

            SetArmor((int)value);
        }

        public override void UpdateMaxHealth()
        {
            UnitMods unitMod = UnitMods.Health;
            float stamina = GetStat(Stats.Stamina) - GetCreateStat(Stats.Stamina);

            float multiplicator;
            switch (GetEntry())
            {
                case ENTRY_IMP:
                    multiplicator = 8.4f;
                    break;
                case ENTRY_VOIDWALKER:
                    multiplicator = 11.0f;
                    break;
                case ENTRY_SUCCUBUS:
                    multiplicator = 9.1f;
                    break;
                case ENTRY_FELHUNTER:
                    multiplicator = 9.5f;
                    break;
                case ENTRY_FELGUARD:
                    multiplicator = 11.0f;
                    break;
                case ENTRY_BLOODWORM:
                    multiplicator = 1.0f;
                    break;
                default:
                    multiplicator = 10.0f;
                    break;
            }

            float value = GetModifierValue(unitMod, UnitModifierType.BaseValue) + GetCreateHealth();
            value *= GetModifierValue(unitMod, UnitModifierType.BasePCT);
            value += GetModifierValue(unitMod, UnitModifierType.TotalValue) + stamina * multiplicator;
            value *= GetModifierValue(unitMod, UnitModifierType.TotalPCT);

            SetMaxHealth((uint)value);
        }

        public override void UpdateMaxPower(PowerType power)
        {
            UnitMods unitMod = UnitMods.PowerStart + (int)power;

            float addValue = (power == PowerType.Mana) ? GetStat(Stats.Intellect) - GetCreateStat(Stats.Intellect) : 0.0f;
            float multiplicator = 15.0f;

            switch (GetEntry())
            {
                case ENTRY_IMP:
                    multiplicator = 4.95f;
                    break;
                case ENTRY_VOIDWALKER:
                case ENTRY_SUCCUBUS:
                case ENTRY_FELHUNTER:
                case ENTRY_FELGUARD:
                    multiplicator = 11.5f;
                    break;
                default:
                    multiplicator = 15.0f;
                    break;
            }

            float value = GetModifierValue(unitMod, UnitModifierType.BaseValue) + GetCreatePowers(power);
            value *= GetModifierValue(unitMod, UnitModifierType.BasePCT);
            value += GetModifierValue(unitMod, UnitModifierType.TotalValue) + addValue * multiplicator;
            value *= GetModifierValue(unitMod, UnitModifierType.TotalPCT);

            SetMaxPower(power, (int)value);
        }

        public override void UpdateAttackPowerAndDamage(bool ranged = false)
        {
            if (ranged)
                return;

            float val = 0.0f;
            float bonusAP = 0.0f;
            UnitMods unitMod = UnitMods.AttackPower;

            if (GetEntry() == ENTRY_IMP)                                   // imp's attack power
                val = GetStat(Stats.Strength) - 10.0f;
            else
                val = 2 * GetStat(Stats.Strength) - 20.0f;

            Unit owner = GetOwner();
            if (owner != null && owner.IsTypeId(TypeId.Player))
            {
                if (IsHunterPet())                      //hunter pets benefit from owner's attack power
                {
                    float mod = 1.0f;                                                 //Hunter contribution modifier
                    bonusAP = owner.GetTotalAttackPowerValue(WeaponAttackType.RangedAttack) * 0.22f * mod;
                    SetBonusDamage((int)(owner.GetTotalAttackPowerValue(WeaponAttackType.RangedAttack) * 0.1287f * mod));
                }
                else if (IsPetGhoul()) //ghouls benefit from deathknight's attack power (may be summon pet or not)
                {
                    bonusAP = owner.GetTotalAttackPowerValue(WeaponAttackType.BaseAttack) * 0.22f;
                    SetBonusDamage((int)(owner.GetTotalAttackPowerValue(WeaponAttackType.BaseAttack) * 0.1287f));
                }
                else if (IsSpiritWolf()) //wolf benefit from shaman's attack power
                {
                    float dmg_multiplier = 0.31f;
                    if (GetOwner().GetAuraEffect(63271, 0) != null) // Glyph of Feral Spirit
                        dmg_multiplier = 0.61f;
                    bonusAP = owner.GetTotalAttackPowerValue(WeaponAttackType.BaseAttack) * dmg_multiplier;
                    SetBonusDamage((int)(owner.GetTotalAttackPowerValue(WeaponAttackType.BaseAttack) * dmg_multiplier));
                }
                //demons benefit from warlocks shadow or fire damage
                else if (IsPet())
                {
                    int fire = (int)((owner.GetUInt32Value(PlayerFields.ModDamageDonePos + (int)SpellSchools.Fire)) + owner.GetUInt32Value(PlayerFields.ModDamageDoneNeg + (int)SpellSchools.Fire));
                    int shadow = (int)((owner.GetUInt32Value(PlayerFields.ModDamageDonePos + (int)SpellSchools.Shadow)) + owner.GetUInt32Value(PlayerFields.ModDamageDoneNeg + (int)SpellSchools.Shadow));
                    int maximum = (fire > shadow) ? fire : shadow;
                    if (maximum < 0)
                        maximum = 0;
                    SetBonusDamage((int)(maximum * 0.15f));
                    bonusAP = maximum * 0.57f;
                }
                //water elementals benefit from mage's frost damage
                else if (GetEntry() == ENTRY_WATER_ELEMENTAL)
                {
                    int frost = (int)((owner.GetUInt32Value(PlayerFields.ModDamageDonePos + (int)SpellSchools.Frost)) + owner.GetUInt32Value(PlayerFields.ModDamageDoneNeg + (int)SpellSchools.Frost));
                    if (frost < 0)
                        frost = 0;
                    SetBonusDamage((int)(frost * 0.4f));
                }
            }

            SetModifierValue(UnitMods.AttackPower, UnitModifierType.BaseValue, val + bonusAP);

            //in BASE_VALUE of UNIT_MOD_ATTACK_POWER for creatures we store data of meleeattackpower field in DB
            float base_attPower = GetModifierValue(unitMod, UnitModifierType.BaseValue) * GetModifierValue(unitMod, UnitModifierType.BasePCT);
            float attPowerMultiplier = GetModifierValue(unitMod, UnitModifierType.TotalPCT) - 1.0f;

            //UNIT_FIELD_(RANGED)_ATTACK_POWER field
            SetInt32Value(UnitFields.AttackPower, (int)base_attPower);
            //UNIT_FIELD_(RANGED)_ATTACK_POWER_MULTIPLIER field
            SetFloatValue(UnitFields.AttackPowerMultiplier, attPowerMultiplier);

            //automatically update weapon damage after attack power modification
            UpdateDamagePhysical(WeaponAttackType.BaseAttack);
        }

        public override void UpdateDamagePhysical(WeaponAttackType attType)
        {
            if (attType > WeaponAttackType.BaseAttack)
                return;

            float bonusDamage = 0.0f;
            if (GetOwner().IsTypeId(TypeId.Player))
            {
                //force of nature
                if (GetEntry() == ENTRY_TREANT)
                {
                    int spellDmg = (int)((GetOwner().GetUInt32Value(PlayerFields.ModDamageDonePos + (int)SpellSchools.Nature)) + GetOwner().GetUInt32Value(PlayerFields.ModDamageDoneNeg + (int)SpellSchools.Nature));
                    if (spellDmg > 0)
                        bonusDamage = spellDmg * 0.09f;
                }
                //greater fire elemental
                else if (GetEntry() == ENTRY_FIRE_ELEMENTAL)
                {
                    int spellDmg = (int)((GetOwner().GetUInt32Value(PlayerFields.ModDamageDonePos + (int)SpellSchools.Fire)) + GetOwner().GetUInt32Value(PlayerFields.ModDamageDoneNeg + (int)SpellSchools.Fire));
                    if (spellDmg > 0)
                        bonusDamage = spellDmg * 0.4f;
                }
            }

            UnitMods unitMod = UnitMods.DamageMainHand;

            float att_speed = GetBaseAttackTime(WeaponAttackType.BaseAttack) / 1000.0f;

            float base_value = GetModifierValue(unitMod, UnitModifierType.BaseValue) + GetTotalAttackPowerValue(attType) / 3.5f * att_speed + bonusDamage;
            float base_pct = GetModifierValue(unitMod, UnitModifierType.BasePCT);
            float total_value = GetModifierValue(unitMod, UnitModifierType.TotalValue);
            float total_pct = GetModifierValue(unitMod, UnitModifierType.TotalPCT);

            float weapon_mindamage = GetWeaponDamageRange(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage);
            float weapon_maxdamage = GetWeaponDamageRange(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage);

            float mindamage = ((base_value + weapon_mindamage) * base_pct + total_value) * total_pct;
            float maxdamage = ((base_value + weapon_maxdamage) * base_pct + total_value) * total_pct;

            var mDummy = GetAuraEffectsByType(AuraType.ModAttackspeed);
            foreach (var eff in mDummy)
            {
                switch (eff.GetSpellInfo().Id)
                {
                    case 61682:
                    case 61683:
                        MathFunctions.AddPct(ref mindamage, -eff.GetAmount());
                        MathFunctions.AddPct(ref maxdamage, -eff.GetAmount());
                        break;
                    default:
                        break;
                }
            }

            SetStatFloatValue(UnitFields.MinDamage, mindamage);
            SetStatFloatValue(UnitFields.MaxDamage, maxdamage);
        }

        void SetBonusDamage(int damage)
        {
            m_bonusSpellDamage = damage;
            if (GetOwner().IsTypeId(TypeId.Player))
                GetOwner().SetUInt32Value(PlayerFields.PetSpellPower, (uint)damage);
        }

        public int GetBonusDamage() { return m_bonusSpellDamage; }

        int m_bonusSpellDamage;
        float[] m_statFromOwner = new float[(int)Stats.Max];
    }

    public class Puppet : Minion
    {
        public Puppet(SummonPropertiesRecord properties, Unit owner)
            : base(properties, owner, false)
        {
            Contract.Assert(owner.IsTypeId(TypeId.Player));
            m_unitTypeMask |= UnitTypeMask.Puppet;
        }

        public override void InitStats(uint duration)
        {
            base.InitStats(duration);

            SetLevel(GetOwner().getLevel());
            SetReactState(ReactStates.Passive);
        }

        public override void InitSummon()
        {
            base.InitSummon();
            if (!SetCharmedBy(GetOwner(), CharmType.Possess))
              Contract.Assert(false);
        }

        public override void Update(uint diff)
        {
            base.Update(diff);
            //check if caster is channelling?
            if (IsInWorld)
            {
                if (!IsAlive())
                {
                    UnSummon();
                    // @todo why long distance .die does not remove it
                }
            }
        }

        public override void RemoveFromWorld()
        {
            if (!IsInWorld)
                return;

            RemoveCharmedBy(null);
            base.RemoveFromWorld();
        }
    }

    public class ForcedUnsummonDelayEvent : BasicEvent
    {
        public ForcedUnsummonDelayEvent(TempSummon owner)
        {
            m_owner = owner;
        }
        public override bool Execute(ulong e_time, uint p_time)
        {
            m_owner.UnSummon();
            return true;
        }

        TempSummon m_owner;
    }

    public class TempSummonData
    {
        public uint entry;        // Entry of summoned creature
        public Position pos;        // Position, where should be creature spawned
        public TempSummonType type; // Summon type, see TempSummonType for available types
        public uint time;         // Despawn time, usable only with certain temp summon types
    }
}
