using System;
using System.Collections.Generic;
using System.Linq;
using PkmnLibSharp.Library;
using PkmnLibSharp.Utilities;
using Item = PkmnLibSharp.Library.Items.Item;
using Nature = PkmnLibSharp.Library.Nature;

namespace PkmnLibSharp.Battling
{
    public class Pokemon : PointerWrapper
    {
        protected internal override void Initialize(IntPtr ptr)
        {
            base.Initialize(ptr);
            if (Library == null)
                Library = new BattleLibrary(Creaturelib.Generated.Creature.GetLibrary(ptr));
        }

        public Pokemon(BattleLibrary library, Species species, Forme forme,
            byte level, uint experience, uint uid,
            Gender gender, byte coloring, Item? heldItem, string? nickname, bool hiddenAbility,
            byte abilityIndex,
            IReadOnlyCollection<LearnedMove> moves, StatisticSet<byte> ivs, StatisticSet<byte> evs,
            Nature nature)
            : base(CreatePtr(library, species, forme, level, experience, uid, gender, coloring, heldItem, nickname,
                hiddenAbility, abilityIndex, moves, ivs, evs, nature))
        {
            Library = library;
            Initialize();
        }

        protected static IntPtr CreatePtr(BattleLibrary library, Species species, Forme forme,
            byte level, uint experience, uint uid,
            Gender gender, byte coloring, Item? heldItem, string? nickname, bool hiddenAbility,
            byte abilityIndex,
            IReadOnlyCollection<LearnedMove> moves, StatisticSet<byte> ivs, StatisticSet<byte> evs,
            Nature nature)
        {
            return Pkmnlib.Generated.Pokemon.Construct(library.Ptr, species.Ptr, forme.Ptr, level, experience,
                uid, (Pkmnlib.Gender) gender, coloring, heldItem?.Ptr ?? IntPtr.Zero, nickname.ToPtr(),
                hiddenAbility.ToNative(), abilityIndex,
                moves.Select(x => x?.Ptr ?? IntPtr.Zero).ToArray().ArrayPtr(),
                (ulong) moves.Count,
                ivs.HP, ivs.Attack, ivs.Defense, ivs.SpecialAttack,
                ivs.SpecialDefense, ivs.Speed, evs.HP, evs.Attack, evs.Defense,
                evs.SpecialAttack, evs.SpecialDefense, evs.Speed, nature.Ptr);
        }

        public BattleLibrary Library { get; private set; }

        public Species Species
        {
            get
            {
                if (_species != null) return _species;
                var ptr = Creaturelib.Generated.Creature.GetSpecies(Ptr);
                if (TryResolvePointer(ptr, out _species))
                    return _species!;
                _species = Constructor.Active.ConstructSpecies(ptr)!;
                return _species;
            }
        }

        public Forme Forme
        {
            get
            {
                if (_forme != null) return _forme;
                var ptr = Creaturelib.Generated.Creature.GetVariant(Ptr);
                if (TryResolvePointer(ptr, out _forme))
                    return _forme!;
                _forme = Constructor.Active.ConstructForme(ptr)!;
                return _forme;
            }
        }

        public Species? DisplaySpecies
        {
            get
            {
                if (_displaySpecies != null) return _displaySpecies;
                var ptr = Creaturelib.Generated.Creature.GetDisplaySpecies(Ptr);
                if (TryResolvePointer(ptr, out _displaySpecies))
                    return _displaySpecies;
                _displaySpecies = Constructor.Active.ConstructSpecies(ptr);
                return _displaySpecies;
            }
            set => Creaturelib.Generated.Creature.SetDisplaySpecies(Ptr, value?.Ptr ?? IntPtr.Zero);
        }

        public Forme? DisplayForme
        {
            get
            {
                if (_displayForme != null) return _displayForme;
                var ptr = Creaturelib.Generated.Creature.GetDisplayVariant(Ptr);
                if (TryResolvePointer(ptr, out _displayForme))
                    return _displayForme;
                _displayForme = Constructor.Active.ConstructForme(ptr)!;
                return _displayForme;
            }
            set => Creaturelib.Generated.Creature.SetDisplayVariant(Ptr, value?.Ptr ?? IntPtr.Zero);
        }

        public byte Level => Creaturelib.Generated.Creature.GetLevel(Ptr);
        public uint Experience => Creaturelib.Generated.Creature.GetExperience(Ptr);
        public uint UniqueIdentifier => Creaturelib.Generated.Creature.GetUniqueIdentifier(Ptr);
        public Gender Gender => (Gender) Creaturelib.Generated.Creature.GetGender(Ptr);
        public byte Coloring => Creaturelib.Generated.Creature.GetColoring(Ptr);
        public bool IsShiny => Pkmnlib.Generated.Pokemon.IsShiny(Ptr) == 1;
        public uint CurrentHealth => Creaturelib.Generated.Creature.GetCurrentHealth(Ptr);
        public uint MaxHealth => Creaturelib.Generated.Creature.GetMaxHealth(Ptr);
        public byte Friendship
        {
            get => Pkmnlib.Generated.Pokemon.GetFriendship(Ptr);
            set => Pkmnlib.Generated.Pokemon.SetFriendship(Ptr, value);
        }

        public bool IsActualAbilityHidden => Creaturelib.Generated.Creature.GetRealTalentIsSecret(Ptr) == 1;
        public byte ActualAbilityIndex => Creaturelib.Generated.Creature.GetRealTalentIndex(Ptr);

        public string? ActiveAbility
        {
            get
            {
                var ptr = IntPtr.Zero;
                Creaturelib.Generated.Creature.GetActiveTalent(Ptr, ref ptr).Assert();
                return ptr.PtrString();
            }
        }
        public Battle? Battle
        {
            get
            {
                var ptr = Creaturelib.Generated.Creature.GetBattle(Ptr);
                if (_battle != null && _battle.Ptr == ptr) return _battle;
                if (TryResolvePointer(ptr, out _battle))
                    return _battle;
                _battle = Constructor.Active.ConstructBattle(ptr);
                return _battle;
            }
        }
        public BattleSide? BattleSide
        {
            get
            {
                var ptr = Creaturelib.Generated.Creature.GetBattleSide(Ptr);
                if (_battleSide != null && _battleSide.Ptr == ptr) return _battleSide;
                if (TryResolvePointer(ptr, out _battleSide))
                    return _battleSide;
                _battleSide = new BattleSide(ptr);
                return _battleSide;
            }
        }
        public bool IsOnBattleField => Creaturelib.Generated.Creature.IsOnBattleField(Ptr) == 1;
        public Item? HeldItem
        {
            get
            {
                var ptr = Creaturelib.Generated.Creature.GetHeldItem(Ptr);
                if (ptr == IntPtr.Zero) return null;
                if (!TryResolvePointer(ptr, out Item? item))
                {
                    item = Constructor.Active.ConstructItem(ptr);
                }
                return item;
            }
            set =>
                Creaturelib.Generated.Creature.SetHeldItemFromItem(Ptr,
                    value?.Ptr ?? IntPtr.Zero);
        }
        public string? Nickname
        {
            get
            {
                if (_nickname != null) return _nickname;
                _nickname = Creaturelib.Generated.Creature.GetNickname(Ptr).PtrString();
                return _nickname;
            }
        }
        public ReadOnlyNativePtrArray<LearnedMove> Moves
        {
            get
            {
                if (_moves != null) return _moves;
                var movesLength = Creaturelib.Generated.Creature.GetAttacksCount(Ptr);
                var movesPtr = Creaturelib.Generated.Creature.GetAttacks(Ptr);
                _moves = new ReadOnlyNativePtrArray<LearnedMove>(movesPtr, (int) movesLength,
                    Constructor.GenericType.LearnedMove);
                return _moves;
            }
        }
        
        public sbyte HpBoost => GetStatBoost(Statistic.Health);
        public sbyte AttackBoost => GetStatBoost(Statistic.Attack);
        public sbyte DefenseBoost => GetStatBoost(Statistic.Defense);
        public sbyte SpecialAttackBoost => GetStatBoost(Statistic.SpecialAttack);
        public sbyte SpecialDefenseBoost => GetStatBoost(Statistic.SpecialDefense);
        public sbyte SpeedBoost => GetStatBoost(Statistic.Speed);

        public uint BaseHp => GetBaseStat(Statistic.Health);
        public uint BaseAttack => GetBaseStat(Statistic.Attack);
        public uint BaseDefense => GetBaseStat(Statistic.Defense);
        public uint BaseSpecialAttack => GetBaseStat(Statistic.SpecialAttack);
        public uint BaseSpecialDefense => GetBaseStat(Statistic.SpecialDefense);
        public uint BaseSpeed => GetBaseStat(Statistic.Speed);

        public uint FlatHp => GetFlatStat(Statistic.Health);
        public uint FlatAttack => GetFlatStat(Statistic.Attack);
        public uint FlatDefense => GetFlatStat(Statistic.Defense);
        public uint FlatSpecialAttack => GetFlatStat(Statistic.SpecialAttack);
        public uint FlatSpecialDefense => GetFlatStat(Statistic.SpecialDefense);
        public uint FlatSpeed => GetFlatStat(Statistic.Speed);

        public uint BoostedHp => GetBoostedStat(Statistic.Health);
        public uint BoostedAttack => GetBoostedStat(Statistic.Attack);
        public uint BoostedDefense => GetBoostedStat(Statistic.Defense);
        public uint BoostedSpecialAttack => GetBoostedStat(Statistic.SpecialAttack);
        public uint BoostedSpecialDefense => GetBoostedStat(Statistic.SpecialDefense);
        public uint BoostedSpeed => GetBoostedStat(Statistic.Speed);
        
        public byte HpIv
        {
            get => GetIndividualValue(Statistic.Health);
            set => SetEffortValue(Statistic.Health, value);
        }

        public byte AttackIv{
            get => GetIndividualValue(Statistic.Attack);
            set => SetEffortValue(Statistic.Attack, value);
        }
        public byte DefenseIv{
            get => GetIndividualValue(Statistic.Defense);
            set => SetEffortValue(Statistic.Defense, value);
        }
        public byte SpecialAttackIv{
            get => GetIndividualValue(Statistic.SpecialAttack);
            set => SetEffortValue(Statistic.SpecialAttack, value);
        }
        public byte SpecialDefenseIv{
            get => GetIndividualValue(Statistic.SpecialDefense);
            set => SetEffortValue(Statistic.SpecialDefense, value);
        }
        public byte SpeedIv{
            get => GetIndividualValue(Statistic.Speed);
            set => SetEffortValue(Statistic.Speed, value);
        }

        public byte HpEv
        {
            get => GetEffortValue(Statistic.Health);
            set => SetEffortValue(Statistic.Health, value);
        }

        public byte AttackEv
        {
            get => GetEffortValue(Statistic.Attack);
            set => SetEffortValue(Statistic.Attack, value);
        }

        public byte DefenseEv{
            get => GetEffortValue(Statistic.Defense);
            set => SetEffortValue(Statistic.Defense, value);
        }
        public byte SpecialAttackEv{
            get => GetEffortValue(Statistic.SpecialAttack);
            set => SetEffortValue(Statistic.SpecialAttack, value);
        }
        public byte SpecialDefenseEv {
            get => GetEffortValue(Statistic.SpecialDefense);
            set => SetEffortValue(Statistic.SpecialDefense, value);
        }
        public byte SpeedEv {
            get => GetEffortValue(Statistic.Speed);
            set => SetEffortValue(Statistic.Speed, value);
        }

        public Nature Nature
        {
            get
            {
                if (_nature != null) return _nature;
                var ptr = Pkmnlib.Generated.Pokemon.GetNature(Ptr);
                if (TryResolvePointer(ptr, out _nature))
                    return _nature!;
                _nature = new Nature(ptr);
                return _nature;
            }
        }

        public string? StatusName => Pkmnlib.Generated.Pokemon.GetStatusName(Ptr).PtrString();

        public void Initialize()
        {
            Creaturelib.Generated.Creature.Initialize(Ptr).Assert();
        }

        public void ChangeForme(Forme forme)
        {
            _forme = null;
            Creaturelib.Generated.Creature.ChangeVariant(Ptr, forme.Ptr);
        }
        public bool HasHeldItem(string itemName)
        {
            return Creaturelib.Generated.Creature.HasHeldItem(Ptr, itemName.ToPtr()) == 1;
        }
        public void SetHeldItem(string item)
        {
            if (string.IsNullOrEmpty(item))
                Creaturelib.Generated.Creature.SetHeldItemFromItem(Ptr, IntPtr.Zero);
            else
                Creaturelib.Generated.Creature.SetHeldItem(Ptr, item.ToPtr());
        }
        public bool HasType(string type)
        {
            var typeByte = Library.StaticLibrary.TypeLibrary.GetTypeId(type);
            return Creaturelib.Generated.Creature.HasType(Ptr, typeByte) == 1;
        }
        public void ChangeLevelBy(sbyte amount)
        {
            Creaturelib.Generated.Creature.ChangeLevelBy(Ptr, amount).Assert();
        }
        public void AddExperience(uint experience)
        {
            Creaturelib.Generated.Creature.AddExperience(Ptr, experience).Assert();
        }
        public void Damage(uint damage, DamageSource source)
        {
            Creaturelib.Generated.Creature.Damage(Ptr, damage, (Creaturelib.DamageSource) source);
        }
        public void Heal(uint damage, bool canRevive)
        {
            Creaturelib.Generated.Creature.Heal(Ptr, damage, canRevive.ToNative());
        }
        public void RestoreAllPP()
        {
            Creaturelib.Generated.Creature.RestoreAllAttackUses(Ptr);
        }
        public void OverrideActiveAbility(string ability)
        {
            Creaturelib.Generated.Creature.OverrideActiveTalent(Ptr, ability.ToPtr()).Assert();
        }
        public void ClearVolatileScripts()
        {
            Creaturelib.Generated.Creature.ClearVolatileScripts(Ptr).Assert();
        }
        public void AddVolatileScript(string scriptName)
        {
            Creaturelib.Generated.Creature.AddVolatileScriptByName(Ptr, scriptName.ToPtr()).Assert();
        }
        public void AddVolatileScript(Script script)
        {
            Creaturelib.Generated.Creature.AddVolatileScript(Ptr, script.Ptr).Assert();
        }
        public void RemoveVolatileScript(string scriptName)
        {
            Creaturelib.Generated.Creature.RemoveVolatileScriptByName(Ptr, scriptName.ToPtr()).Assert();
        }
        public void RemoveVolatileScript(Script script)
        {
            Creaturelib.Generated.Creature.RemoveVolatileScript(Ptr, script.Ptr).Assert();
        }
        public bool HasVolatileScript(string scriptName)
        {
            return Creaturelib.Generated.Creature.HasVolatileScript(Ptr, scriptName.ToPtr()) == 1;
        }

        public bool HasMove(string moveName)
        {
            return Creaturelib.Generated.Creature.HasAttack(Ptr, moveName.ToPtr()) == 1;
        }

        public sbyte GetStatBoost(Statistic stat)
        {
            return Creaturelib.Generated.Creature.GetStatBoost(Ptr, (Creaturelib.Statistic) stat);
        }
        public void ChangeStatBoost(Statistic stat, sbyte amount)
        {
            Creaturelib.Generated.Creature.ChangeStatBoost(Ptr, (Creaturelib.Statistic) stat, amount);
        }

        public uint GetBaseStat(Statistic stat)
        {
            return Creaturelib.Generated.Creature.GetBaseStat(Ptr, (Creaturelib.Statistic) stat);
        }
        public uint GetFlatStat(Statistic stat)
        {
            return Creaturelib.Generated.Creature.GetFlatStat(Ptr, (Creaturelib.Statistic) stat);
        }
        public uint GetBoostedStat(Statistic stat)
        {
            return Creaturelib.Generated.Creature.GetBoostedStat(Ptr, (Creaturelib.Statistic) stat);
        }
        public byte GetIndividualValue(Statistic stat)
        {
            return Pkmnlib.Generated.Pokemon.GetIndividualValue(Ptr, (Pkmnlib.Statistic) stat);
        }
        public void SetIndividualValue(Statistic stat, byte value)
        {
            Pkmnlib.Generated.Pokemon.SetIndividualValue(Ptr, (Pkmnlib.Statistic) stat, value);
        }

        public byte GetEffortValue(Statistic stat)
        {
            return Pkmnlib.Generated.Pokemon.GetEffortValue(Ptr, (Pkmnlib.Statistic) stat);
        }
        public void SetEffortValue(Statistic stat, byte value)
        {
            Pkmnlib.Generated.Pokemon.SetEffortValue(Ptr, (Pkmnlib.Statistic) stat, value);
        }

        public void SetStatus(string name)
        {
            Pkmnlib.Generated.Pokemon.SetStatus(Ptr, name.ToPtr());
        }
        public void ClearStatus()
        {
            Pkmnlib.Generated.Pokemon.ClearStatus(Ptr);
        }

        public void ChangeFriendship(sbyte amount)
        {
            Pkmnlib.Generated.Pokemon.ChangeFriendship(Ptr, amount);
        }

        public byte FirstAvailableMoveSlot()
        {
            return Creaturelib.Generated.Creature.GetAvailableAttackSlot(Ptr);
        }

        public void AddMove(LearnedMove move)
        {
            Creaturelib.Generated.Creature.AddAttack(Ptr, move.Ptr).Assert();
        }
        public void ReplaceMove(byte index, LearnedMove move)
        {
            Creaturelib.Generated.Creature.ReplaceAttack(Ptr, index, move.Ptr).Assert();
        }

        public void SwapMoves(byte a, byte b)
        {
            Creaturelib.Generated.Creature.SwapAttack(Ptr, a, b).Assert();
        }

        public void Evolve(Species species, Forme forme)
        {
            Pkmnlib.Generated.Pokemon.Evolve(Ptr, species.Ptr, forme.Ptr).Assert();
        }

        private Species? _displaySpecies;
        private Forme? _displayForme;
        private Species? _species;
        private Forme? _forme;
        private string? _nickname;
        private ReadOnlyNativePtrArray<LearnedMove>? _moves;
        private Nature? _nature;
        private Battle? _battle;
        private BattleSide? _battleSide;
        
        protected override void DeletePtr()
        {
            Pkmnlib.Generated.Pokemon.Destruct(Ptr);
        }

        protected internal override void MarkAsDeleted()
        {
            foreach (var move in Moves)
            {
                move?.MarkAsDeleted();
            }
            base.MarkAsDeleted();
        }
    }
}