//
//  CombatManager.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.
using NosSmooth.Core.Client;
using NosSmooth.Core.Commands.Attack;
using NosSmooth.Core.Stateful;
using NosSmooth.Extensions.Combat.Errors;
using NosSmooth.Extensions.Combat.Operations;
using NosSmooth.Extensions.Combat.Techniques;
using Remora.Results;
namespace NosSmooth.Extensions.Combat;
/// 
/// The combat manager that uses techniques to attack enemies.
/// 
public class CombatManager : IStatefulEntity
{
    private readonly List _tokenSource;
    private readonly SemaphoreSlim _semaphore;
    private readonly INostaleClient _client;
    private readonly Game.Game _game;
    private bool _cancelling;
    /// 
    /// Initializes a new instance of the  class.
    /// 
    /// The NosTale client.
    /// The game.
    public CombatManager(INostaleClient client, Game.Game game)
    {
        _semaphore = new SemaphoreSlim(1, 1);
        _tokenSource = new List();
        _client = client;
        _game = game;
    }
    /// 
    /// Enter into a combat state using the given technique.
    /// 
    /// The technique to use.
    /// The cancellation token for cancelling the operation.
    /// A result that may or may not succeed.
    public async Task EnterCombatAsync(ICombatTechnique technique, CancellationToken ct = default)
    {
        var combatState = new CombatState(_client, _game, this);
        long? currentTarget = null;
        long? previousTarget = null;
        while (!combatState.ShouldQuit && !ct.IsCancellationRequested)
        {
            var commandResult = await _client.SendCommandAsync
            (
                new AttackCommand
                (
                    currentTarget,
                    async (c) =>
                    {
                        while (!combatState.ShouldQuit && currentTarget == previousTarget)
                        {
                            if (!technique.ShouldContinue(combatState))
                            {
                                combatState.QuitCombat();
                                continue;
                            }
                            var operation = combatState.NextOperation();
                            if (operation is null)
                            { // The operation is null and the step has to be obtained from the technique.
                                var stepResult = technique.HandleCombatStep(combatState);
                                if (!stepResult.IsSuccess)
                                {
                                    return Result.FromError(stepResult);
                                }
                                previousTarget = currentTarget;
                                currentTarget = stepResult.Entity;
                                if (previousTarget != currentTarget)
                                {
                                    continue;
                                }
                                operation = combatState.NextOperation();
                            }
                            if (operation is null)
                            { // The operation could be null just because there is currently not a skill to be used etc.
                                await Task.Delay(5, ct);
                                continue;
                            }
                            Result responseResult;
                            while ((responseResult = operation.CanBeUsed(combatState)).IsSuccess
                                && responseResult.Entity == CanBeUsedResponse.MustWait)
                            { // TODO: wait for just some amount of time
                                await Task.Delay(5, ct);
                            }
                            if (!responseResult.IsSuccess)
                            {
                                return Result.FromError(responseResult);
                            }
                            if (responseResult.Entity == CanBeUsedResponse.WontBeUsable)
                            {
                                return new UnusableOperationError(operation);
                            }
                            var usageResult = await operation.UseAsync(combatState, ct);
                            if (!usageResult.IsSuccess)
                            {
                                var errorHandleResult = technique.HandleError(combatState, operation, usageResult);
                                if (!errorHandleResult.IsSuccess)
                                {
                                    return errorHandleResult;
                                }
                            }
                        }
                        return Result.FromSuccess();
                    }
                ),
                ct
            );
            if (!commandResult.IsSuccess)
            {
                return commandResult;
            }
            previousTarget = currentTarget;
        }
        return Result.FromSuccess();
    }
    /// 
    /// Register the given cancellation token source to be cancelled on skill use/cancel.
    /// 
    /// The token source to register.
    /// The cancellation token for cancelling the operation.
    /// A task.
    public async Task RegisterSkillCancellationTokenAsync(CancellationTokenSource tokenSource, CancellationToken ct)
    {
        await _semaphore.WaitAsync(ct);
        try
        {
            _tokenSource.Add(tokenSource);
        }
        finally
        {
            _semaphore.Release();
        }
    }
    /// 
    /// Unregister the given cancellation token registered using RegisterSkillCancellationToken.
    /// 
    /// The token source to unregister.
    /// The cancellation token for cancelling the operation.
    /// A task.
    public async Task UnregisterSkillCancellationTokenAsync(CancellationTokenSource tokenSource, CancellationToken ct)
    {
        if (_cancelling)
        {
            return;
        }
        await _semaphore.WaitAsync(ct);
        try
        {
            _tokenSource.Remove(tokenSource);
        }
        finally
        {
            _semaphore.Release();
        }
    }
    /// 
    /// Cancel all of the skill tokens.
    /// 
    /// The cancellation token for cancelling the operation.
    /// A task.
    internal async Task CancelSkillTokensAsync(CancellationToken ct)
    {
        await _semaphore.WaitAsync(ct);
        _cancelling = true;
        try
        {
            foreach (var tokenSource in _tokenSource)
            {
                try
                {
                    tokenSource.Cancel();
                }
                catch
                {
                    // ignored
                }
            }
            _tokenSource.Clear();
        }
        finally
        {
            _cancelling = false;
            _semaphore.Release();
        }
    }
}