using System;
using System.Collections.Generic;
using System.Linq;
using Creaturelib.Generated;
using Pkmnlib.Generated;
using PkmnLibSharp.Utilities;
using EvolutionData = PkmnLibSharp.Library.Evolution.EvolutionData;
using Random = PkmnLibSharp.Utilities.Random;

namespace PkmnLibSharp.Library
{
    public class Species : PointerWrapper
    {
        public Species(ushort id, string name, Forme defaultForme, float genderRatio, string growthRate,
            byte captureRate, byte baseHappiness, IReadOnlyCollection<string> eggGroups, IReadOnlyCollection<string> tags)
        {
            var ptr = IntPtr.Zero;
            var tagsConverted = tags.Select(x => x.ToPtr()).ToArray();
            var eggGroupsConverted = eggGroups.Select(x => x.ToPtr()).ToArray();
            var eggGroupsPtr = eggGroupsConverted.ArrayPtr();
            var tagsPtr = tagsConverted.ArrayPtr();

            PokemonSpecies.Construct(ref ptr, id, name.ToPtr(), defaultForme.Ptr, genderRatio,
                growthRate.ToPtr(), captureRate, baseHappiness, eggGroupsPtr, (ulong) eggGroups.Count,
                tagsPtr, (ulong) tags.Count).Assert();
            _formeCache.Add("default", defaultForme);
            Initialize(ptr);
        }

        public ushort Id => CreatureSpecies.GetId(Ptr);
        public float GenderRate => CreatureSpecies.GetGenderRate(Ptr);
        public byte CaptureRate => CreatureSpecies.GetCaptureRate(Ptr);
        public string Name => _name ??= CreatureSpecies.GetName(Ptr).PtrString()!;
        public string GrowthRate => _growthRate ??= CreatureSpecies.GetGrowthRate(Ptr).PtrString()!;

        public string[] EggGroups
        {
            get
            {
                if (_eggGroups != null) return _eggGroups;
                var count = Pkmnlib.Generated.PokemonSpecies.GetEggGroupCount(Ptr);
                _eggGroups = new string[count];
                for (ulong i = 0; i < count; i++)
                {
                    _eggGroups[i] = PokemonSpecies.GetEggGroup(Ptr, i).PtrString()!;
                }
                return _eggGroups;
            }
        }

        public ReadOnlyNativePtrArray<Forme> Formes
        {
            get
            {
                if (_formes != null) return _formes;
                var startPtr = CreatureSpecies.GetVariants(Ptr);
                var size = CreatureSpecies.GetVariantsCount(Ptr);
                _formes = new ReadOnlyNativePtrArray<Forme>(startPtr, (int) size, Constructor.GenericType.Forme);
                return _formes;
            }
        }

        public ReadOnlyNativePtrArray<EvolutionData> Evolutions
        {
            get
            {
                if (_evolutions != null) return _evolutions;
                var ptr = IntPtr.Zero;
                PokemonSpecies.GetEvolutions(Ptr, ref ptr);
                var length = PokemonSpecies.GetEvolutionCount(Ptr);
                _evolutions = new ReadOnlyNativePtrArray<EvolutionData>(ptr, (int) length, Constructor.GenericType.EvolutionData);
                return _evolutions;
            }
        }

        public bool HasForme(string s)
        {
            return CreatureSpecies.HasVariant(Ptr, s.ToPtr()) == MarshalHelper.True;
        }

        public bool TryGetForme(string s, out Forme? forme)
        {
            if (_formeCache.TryGetValue(s, out forme))
                return true;
            var ptr = IntPtr.Zero;
            if (CreatureSpecies.TryGetVariant(Ptr, s.ToPtr(), ref ptr) == MarshalHelper.True)
            {
                if (TryResolvePointer(ptr, out forme))
                {
                    _formeCache.Add(s, forme!);
                    return true;
                }

                forme = Constructor.Active.ConstructForme(ptr)!;
                _formeCache.Add(s, forme);
                return true;
            }

            return false;
        }

        public Forme GetForme(string s)
        {
            if (_formeCache.TryGetValue(s, out var forme))
                return forme;
            var ptr = IntPtr.Zero;
            CreatureSpecies.GetVariant(ref ptr, Ptr, s.ToPtr()).Assert();
            if (TryResolvePointer(ptr, out Forme? f))
            {
                _formeCache.Add(s, f!);
                return f!;
            }
            forme = Constructor.Active.ConstructForme(ptr)!;
            _formeCache.Add(s, forme);
            return forme;
        }
        
        public void SetForme(string s, Forme forme)
        {
            _formeCache.Add(s, forme);
            CreatureSpecies.SetVariant(Ptr, s.ToPtr(), forme.Ptr).Assert();
        }

        public Gender GetRandomGender(Random random)
        {
            return (Gender) CreatureSpecies.GetRandomGender(Ptr, random.Ptr);
        }

        public void AddEvolution(EvolutionData evolutionData)
        {
            Pkmnlib.Generated.PokemonSpecies.AddEvolution(Ptr, evolutionData.Ptr);
        }
        
        public bool HasFlag(string flag)
        {
            return CreatureSpecies.HasFlag(Ptr, flag.ToPtr()) == 1;
        }

        private string? _name;
        private string? _growthRate;

        private readonly Dictionary<string, Forme> _formeCache =
            new Dictionary<string, Forme>(StringComparer.InvariantCultureIgnoreCase);

        private ReadOnlyNativePtrArray<EvolutionData>? _evolutions;
        private ReadOnlyNativePtrArray<Forme>? _formes;
        private string[]? _eggGroups;

        protected internal override void MarkAsDeleted()
        {
            base.MarkAsDeleted();
            foreach (var forme in _formeCache)
            {
                forme.Value.MarkAsDeleted();
            }
        }

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