using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Creaturelib.Generated;
using Pkmnlib.Generated;
using PkmnLibSharp.Utilities;
using Random = PkmnLibSharp.Utilities.Random;

namespace PkmnLibSharp.Library
{
    public class Forme : PointerWrapper
    {
        private ReadOnlyArray<string>? _abilities;
        private ReadOnlyArray<string>? _hiddenAbilities;
        private LearnableMoves? _moves;
        private string? _name;
        private ReadOnlyArray<byte>? _types;
        
        public Forme(string name, float height, float weight, uint baseExperience, byte[] types,
            ushort baseHealth, ushort baseAttack, ushort baseDefense, ushort baseSpecialAttack,
            ushort baseSpecialDefense, ushort baseSpeed, IReadOnlyCollection<string> abilities, 
            IReadOnlyCollection<string> hiddenAbilities, LearnableMoves moves, IReadOnlyCollection<string> tags)
        {
            var abilitiesConverted = abilities.Select(x => x.ToPtr()).ToArray();
            var hiddenAbilitiesConverted = hiddenAbilities.Select(x => x.ToPtr()).ToArray();
            var tagsConverted = tags.Select(x => x.ToPtr()).ToArray();
            var ab = abilitiesConverted.ArrayPtr();
            var hab = hiddenAbilitiesConverted.ArrayPtr();
            var tagsPtr = tagsConverted.ArrayPtr();
            
            var ptr = PokemonForme.Construct(name.ToPtr(), height, weight, baseExperience, types.ArrayPtr(),
                (ulong) types.Length, baseHealth, baseAttack, baseDefense, baseSpecialAttack,
                baseSpecialDefense, baseSpeed, ab, (ulong) abilities.Count, hab,
                (ulong) hiddenAbilities.Count, moves.Ptr, tagsPtr, (ulong) tags.Count);
            foreach (var intPtr in abilitiesConverted)
                Marshal.FreeHGlobal(intPtr);
            foreach (var intPtr in hiddenAbilitiesConverted)
                Marshal.FreeHGlobal(intPtr);
            Initialize(ptr);
        }
        
        public string Name => _name ??= SpeciesVariant.GetName(Ptr).PtrString()!;
        public float Height => SpeciesVariant.GetHeight(Ptr);
        public float Weight => SpeciesVariant.GetWeight(Ptr);
        public float BaseExperience => SpeciesVariant.GetBaseExperience(Ptr);

        public ReadOnlyArray<byte> Types
        {
            get
            {
                if (_types != null)
                    return _types;

                var typesCount = SpeciesVariant.GetTypeCount(Ptr);
                var types = new byte[typesCount];
                for (ulong i = 0; i < typesCount; i++) types[i] = SpeciesVariant.GetType(Ptr, i);

                _types = types.ToReadOnly();
                return _types;
            }
        }

        public uint BaseHealth => SpeciesVariant.GetStatistic(Ptr, (byte) Statistic.Health);
        public uint BaseAttack => SpeciesVariant.GetStatistic(Ptr, (Creaturelib.Statistic) Statistic.Attack);
        public uint BaseDefense => SpeciesVariant.GetStatistic(Ptr, (Creaturelib.Statistic) Statistic.Defense);

        public uint BaseSpecialAttack =>
            SpeciesVariant.GetStatistic(Ptr, (Creaturelib.Statistic) Statistic.SpecialAttack);

        public uint BaseSpecialDefense =>
            SpeciesVariant.GetStatistic(Ptr, (Creaturelib.Statistic) Statistic.SpecialDefense);

        public uint BaseSpeed => SpeciesVariant.GetStatistic(Ptr, (Creaturelib.Statistic) Statistic.Speed);

        public ReadOnlyArray<string> Abilities
        {
            get
            {
                if (_abilities != null)
                    return _abilities;

                var abilityCount = SpeciesVariant.GetTalentCount(Ptr);
                var abilities = new string[abilityCount];
                for (byte i = 0; i < abilityCount; i++)
                {
                    var s = IntPtr.Zero;
                    SpeciesVariant.GetTalent(Ptr, MarshalHelper.False, i, ref s).Assert();
                    abilities[i] = s.PtrString()!;
                }

                _abilities = abilities.ToReadOnly();
                return _abilities;
            }
        }

        public ReadOnlyArray<string> HiddenAbilities
        {
            get
            {
                if (_hiddenAbilities != null)
                    return _hiddenAbilities;

                var abilityCount = SpeciesVariant.GetSecretTalentCount(Ptr);
                var abilities = new string[abilityCount];
                for (byte i = 0; i < abilityCount; i++)
                {
                    var s = IntPtr.Zero;
                    SpeciesVariant.GetTalent(Ptr, MarshalHelper.True, i, ref s).Assert();
                    abilities[i] = s.PtrString()!;
                }

                _hiddenAbilities = abilities.ToReadOnly();
                return _hiddenAbilities;
            }
        }

        public LearnableMoves Moves
        {
            get
            {
                if (_moves != null) return _moves;
                var movesPtr = SpeciesVariant.GetLearnableAttacks(Ptr);
                if (!TryResolvePointer(movesPtr, out _moves)) 
                    _moves = Constructor.Active.ConstructLearnableMoves(movesPtr);

                return _moves!;
            }
        }

        public byte GetPkmnType(int index)
        {
            return Types[index];
        }
        
        public byte GetRandomAbility(Random rand)
        {
            return SpeciesVariant.GetRandomTalent(Ptr, rand.Ptr);
        }

        public bool HasFlag(string flag)
        {
            return SpeciesVariant.HasFlag(Ptr, flag.ToPtr()) == 1;
        }

        protected internal override void MarkAsDeleted()
        {
            base.MarkAsDeleted();
            _moves?.MarkAsDeleted();
        }

        protected override void DeletePtr()
        {
            SpeciesVariant.Destruct(Ptr);
        }
    }
}