using System;
using System.Runtime.Serialization;
using PkmnLibSharp.Battling;
using PkmnLibSharp.Library;
using PkmnLibSharp.Library.Moves;
using Battle = PkmnLibSharp.Battling.Battle;
using EvolutionData = PkmnLibSharp.Library.Evolution.EvolutionData;
using Item = PkmnLibSharp.Library.Items.Item;
using LearnableMoves = PkmnLibSharp.Library.LearnableMoves;
using Pokemon = PkmnLibSharp.Battling.Pokemon;

namespace PkmnLibSharp.Utilities
{
    public abstract class Constructor
    {
        public static Constructor Active { get; set; } =
            new Constructor<
                Species, Forme, MoveData, Item, LearnableMoves, 
                Pokemon, Battle, BattleParty, BattleSide, LearnedMove>();
        
        internal enum GenericType
        {
            Species, Forme, MoveData, Item, LearnableMoves, EvolutionData,
            Pokemon, Battle,

            BattleParty,
            BattleSide,
            LearnedMove
        }

        internal abstract Species? ConstructSpecies(IntPtr ptr);
        internal abstract Forme? ConstructForme(IntPtr ptr);
        internal abstract MoveData? ConstructMove(IntPtr ptr);
        internal abstract Item? ConstructItem(IntPtr ptr);
        internal abstract LearnableMoves? ConstructLearnableMoves(IntPtr ptr);
        internal abstract Pokemon? ConstructPokemon(IntPtr ptr);
        internal abstract Battle? ConstructBattle(IntPtr ptr);
        internal abstract BattleParty? ConstructBattleParty(IntPtr ptr);
        internal abstract BattleSide? ConstructBattleSide(IntPtr ptr);
        internal abstract LearnedMove? ConstructLearnedMove(IntPtr ptr);

        internal EvolutionData? ConstructEvolutionData(IntPtr ptr)
        {
            return new EvolutionData(ptr);
        }

        internal object? InstantiateGeneric(GenericType type, IntPtr ptr)
        {
            return type switch
            {
                GenericType.Species => ConstructSpecies(ptr),
                GenericType.Forme => ConstructForme(ptr),
                GenericType.MoveData => ConstructMove(ptr),
                GenericType.Item => ConstructItem(ptr),
                GenericType.LearnableMoves => ConstructLearnableMoves(ptr),
                GenericType.EvolutionData => ConstructEvolutionData(ptr),

                GenericType.Pokemon => ConstructPokemon(ptr),
                GenericType.Battle => ConstructBattle(ptr),
                GenericType.BattleParty => ConstructBattleParty(ptr),
                GenericType.BattleSide => ConstructBattleSide(ptr),
                GenericType.LearnedMove => ConstructLearnedMove(ptr),
                _ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
            };
        }
    }

    public class Constructor<
            TSpecies, TForme, TMoveData, TItem, TLearnableMoves, 
            TPokemon, TBattle, TBattleParty, TBattleSide, TLearnedMove> 
        : Constructor
        where TSpecies : Species 
        where TForme : Forme 
        where TMoveData : MoveData 
        where TItem: Item 
        where TLearnableMoves : LearnableMoves
        
        where TPokemon : Pokemon
        where TBattle : Battle
        where TBattleParty : BattleParty
        where TBattleSide : BattleSide
        where TLearnedMove : LearnedMove
    {
        private readonly Type _speciesType = typeof(TSpecies);
        private readonly Type _formeType = typeof(TForme);
        private readonly Type _moveDataType = typeof(TMoveData);
        private readonly Type _itemType = typeof(TItem);
        private readonly Type _learnableMovesType = typeof(TLearnableMoves);
        
        private readonly Type _pokemonType = typeof(TPokemon);
        private readonly Type _battleType = typeof(TBattle);
        private readonly Type _battlePartyType = typeof(TBattleParty);
        private readonly Type _battleSideType = typeof(TBattleSide);
        private readonly Type _learnedMoveType = typeof(TLearnedMove);
        
        private static T? Create<T>(IntPtr ptr, Type t) where T : PointerWrapper
        {
            if (ptr == IntPtr.Zero) return null;
            var o = (T) FormatterServices.GetUninitializedObject(t);
            o.Initialize(ptr);
            return o;
        }

        internal override Species? ConstructSpecies(IntPtr ptr)
        {
            return Create<TSpecies>(ptr, _speciesType);
        }

        internal override Forme? ConstructForme(IntPtr ptr)
        {
            return Create<TForme>(ptr, _formeType);
        }

        internal override MoveData? ConstructMove(IntPtr ptr)
        {
            return Create<TMoveData>(ptr, _moveDataType);
        }

        internal override Item? ConstructItem(IntPtr ptr)
        {
            return Create<TItem>(ptr, _itemType);
        }

        internal override LearnableMoves? ConstructLearnableMoves(IntPtr ptr)
        {
            return Create<TLearnableMoves>(ptr, _learnableMovesType);
        }

        internal override Pokemon? ConstructPokemon(IntPtr ptr)
        {
            return Create<TPokemon>(ptr, _pokemonType);
        }

        internal override Battle? ConstructBattle(IntPtr ptr)
        {
            return Create<TBattle>(ptr, _battleType);
        }

        internal override BattleParty? ConstructBattleParty(IntPtr ptr)
        {
            return Create<TBattleParty>(ptr, _battlePartyType);
        }

        internal override BattleSide? ConstructBattleSide(IntPtr ptr)
        {
            return Create<TBattleSide>(ptr, _battleSideType);
        }

        internal override LearnedMove? ConstructLearnedMove(IntPtr ptr)
        {
            return Create<TLearnedMove>(ptr, _learnedMoveType);
        }
    }
}