using System;
using System.Collections.Generic;
using System.Linq;
using PkmnLibSharp.Battling.ChoiceTurn;
using PkmnLibSharp.Battling.Events;
using PkmnLibSharp.Battling.History;
using PkmnLibSharp.Utilities;

namespace PkmnLibSharp.Battling
{
    public class Battle : PointerWrapper
    {
        public Battle(BattleLibrary library, BattleParty[] parties, bool canFlee, byte numberOfSides, byte pokemonPerSide,
            ulong randomSeed)
        {
            var ptr = IntPtr.Zero;
            var arr = parties.Select(x => x.Ptr).ToArray();
            Pkmnlib.Generated.Battle.Construct(ref ptr, library.Ptr, arr.ArrayPtr(), (ulong) arr.Length,
                canFlee.ToNative(), numberOfSides, pokemonPerSide, randomSeed);
            Initialize(ptr);
        }

        public BattleLibrary Library
        {
            get
            {
                if (_library != null) return _library;
                var ptr = Creaturelib.Generated.Battle.GetLibrary(Ptr);
                if (TryResolvePointer(ptr, out _library))
                {
                    return _library!;
                }
                _library = new BattleLibrary(ptr);
                return _library;
            }
        }
        
        public BattleRandom Random
        {
            get
            {
                if (_random != null) return _random;
                var ptr = Creaturelib.Generated.Battle.GetRandom(Ptr);
                if (TryResolvePointer(ptr, out _random))
                {
                    return _random!;
                }
                _random = new BattleRandom(ptr);
                return _random;
            }
        }

        public bool CanFlee => Creaturelib.Generated.Battle.CanFlee(Ptr) == 1;
        public bool HasEnded => Creaturelib.Generated.Battle.HasEnded(Ptr) == 1;
        public bool HasConclusiveResult => Creaturelib.Generated.Battle.HasConclusiveResult(Ptr) == 1;
        public byte WinningSide => Creaturelib.Generated.Battle.GetWinningSide(Ptr);
        public ulong SidesCount => Creaturelib.Generated.Battle.GetSidesCount(Ptr);
        public ulong PartiesCount => Creaturelib.Generated.Battle.GetPartiesCount(Ptr);
        public ulong MonsPerSide => Creaturelib.Generated.Battle.GetCreaturesPerSide(Ptr);
        public long LastTurnTimeMicroseconds => Creaturelib.Generated.Battle.GetLastTurnTimeMicroseconds(Ptr);

        public ReadOnlyNativePtrArray<BattleSide> Sides
        {
            get
            {
                if (_sides != null) return _sides;
                var ptr = Creaturelib.Generated.Battle.GetSides(Ptr);
                _sides = new ReadOnlyNativePtrArray<BattleSide>(ptr, (int) SidesCount, Constructor.GenericType.BattleSide);
                return _sides;
            }
        }

        public uint CurrentTurn => Creaturelib.Generated.Battle.GetCurrentTurn(Ptr);
        
        public ReadOnlyNativePtrArray<BattleParty> Parties
        {
            get
            {
                if (_parties != null) return _parties;
                var ptr = Creaturelib.Generated.Battle.GetParties(Ptr);
                _parties = new ReadOnlyNativePtrArray<BattleParty>(ptr, (int) PartiesCount, Constructor.GenericType.BattleParty);
                return _parties;
            }
        }
        
        public string? WeatherName => Pkmnlib.Generated.Battle.GetWeatherName(Ptr).PtrString();

        public HistoryHandler History
        {
            get
            {
                if (_history != null) return _history;
                var ptr = Creaturelib.Generated.Battle.GetHistory(Ptr);
                if (TryResolvePointer(ptr, out _history))
                    return _history!;
                _history = new HistoryHandler(ptr);
                return _history;
            }
        }


        public bool CanUse(BaseTurnChoice turnChoice)
        {
            byte b = 0;
            Creaturelib.Generated.Battle.CanUse(ref b, Ptr, turnChoice.Ptr).Assert();
            return b == 1;
        }

        private readonly List<BaseTurnChoice> _choiceCache = new List<BaseTurnChoice>();
        public bool TrySetChoice(BaseTurnChoice turnChoice)
        {
            byte b = 0;
            var initialTurn = CurrentTurn;
            _choiceCache.Add(turnChoice);
            var t1 = System.Diagnostics.Stopwatch.StartNew();
            Creaturelib.Generated.Battle.TrySetChoice(ref b, Ptr, turnChoice.Ptr).Assert();
            t1.Stop();
            if (b == 1 && CurrentTurn != initialTurn)
            {
                LogHandler.Log(LogHandler.LogLevel.Information,
                    $"Ran turn in {t1.ElapsedMilliseconds} ms, battle reported {LastTurnTimeMicroseconds} microseconds");
                _choiceCache.Clear();
            }
            return b == 1;
        }

        public void CheckChoicesSetAndRun()
        {
            Creaturelib.Generated.Battle.CheckChoicesSetAndRun(Ptr);
        }

        public bool IsPokemonInField(Pokemon pokemon)
        {
            byte b = 0;
            Creaturelib.Generated.Battle.CreatureInField(ref b, Ptr, pokemon.Ptr).Assert();
            return b == 1;
        }

        public Pokemon GetPokemonInField(byte side, byte index)
        {
            var ptr = IntPtr.Zero;
            Creaturelib.Generated.Battle.GetCreature(ref ptr, Ptr, side, index).Assert();
            return TryResolvePointer(ptr, out Pokemon? pokemon) ? pokemon! : Constructor.Active.ConstructPokemon(ptr)!;
        }

        public void ForceRecall(byte side, byte index)
        {
            Creaturelib.Generated.Battle.ForceRecall(Ptr, side, index).Assert();
        }
        
        public void SwitchPokemon(byte side, byte index, Pokemon newPokemon)
        {
            Creaturelib.Generated.Battle.SwitchCreature(Ptr, side, index, newPokemon.Ptr).Assert();
        }

        public bool CanSlotBeFilled(byte side, byte index)
        {
            byte b = 0;
            Creaturelib.Generated.Battle.CanSlotBeFilled(ref b, Ptr, side, index).Assert();
            return b == 1;
        }

        public void ValidateBattleState()
        {
            Creaturelib.Generated.Battle.ValidateBattleState(Ptr).Assert();
        }
        
        public Script GetVolatileScript(string key)
        {
            var ptr = Creaturelib.Generated.Battle.GetVolatileScript(Ptr, key.ToPtr());
            if (TryResolvePointer(ptr, out Script? script))
            {
                return script!;
            }
            // TODO: Handle with different script providers.
            return new AngelscriptScript(ptr);
        }

        public void AddVolatileScript(string key)
        {
            Creaturelib.Generated.Battle.AddVolatileScriptByName(Ptr, key.ToPtr()).Assert();
        }
        
        public void AddVolatileScript(Script script)
        {
            Creaturelib.Generated.Battle.AddVolatileScript(Ptr, script.Ptr).Assert();
        }
        
        public void RemoveVolatileScript(string key)
        {
            Creaturelib.Generated.Battle.RemoveVolatileScript(Ptr, key.ToPtr()).Assert();
        }
        
        public void RemoveVolatileScript(Script script)
        {
            Creaturelib.Generated.Battle.RemoveVolatileScriptWithScript(Ptr, script.Ptr).Assert();
        }
        
        public bool HasVolatileScript(string key)
        {
            return Creaturelib.Generated.Battle.HasVolatileScript(Ptr, key.ToPtr()) == 1;
        }

        private List<BattleEventListener> _listeners = new List<BattleEventListener>();
        public void RegisterEventListener(BattleEventListener listener)
        {
            _listeners.Add(listener);
            Creaturelib.Generated.Battle.RegisterEventListener(Ptr, listener.FunctionPtr).Assert();
        }
        
        public void SetWeather(string weatherName)
        {
            Pkmnlib.Generated.Battle.SetWeather(Ptr, weatherName.ToPtr());
        }
        
        public void ClearWeather()
        {
            Pkmnlib.Generated.Battle.ClearWeather(Ptr);
        }

        private BattleLibrary? _library;
        private BattleRandom? _random;
        private ReadOnlyNativePtrArray<BattleSide>? _sides;
        private ReadOnlyNativePtrArray<BattleParty>? _parties;
        private HistoryHandler? _history;
        
        protected override void DeletePtr()
        {
            Pkmnlib.Generated.Battle.Destruct(Ptr);
        }
    }
}