//
//  WalkStatus.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.LocalClient.Hooks;
namespace NosSmooth.LocalClient.CommandHandlers.Walk;
/// 
/// The status for .
/// 
public class WalkStatus
{
    private readonly NostaleHookManager _hookManager;
    private readonly SemaphoreSlim _semaphore;
    private CancellationTokenSource? _walkingCancellation;
    private bool _userCanCancel;
    private bool _walkHooked;
    /// 
    /// Initializes a new instance of the  class.
    /// 
    /// The hooking manager.
    public WalkStatus(NostaleHookManager hookManager)
    {
        _hookManager = hookManager;
        _semaphore = new SemaphoreSlim(1, 1);
    }
    /// 
    /// Gets if the walk command is in progress.
    /// 
    public bool IsWalking => _walkingCancellation is not null;
    /// 
    /// Gets if the current walk command has been finished.
    /// 
    public bool IsFinished { get; private set; }
    /// 
    /// Gets the last time of walk.
    /// 
    public DateTimeOffset LastWalkTime { get; private set; }
    /// 
    /// Gets the walk target x coordinate.
    /// 
    public int TargetX { get; private set; }
    /// 
    /// Gets the walk target y coordinate.
    /// 
    public int TargetY { get; private set; }
    /// 
    /// Gets the characters current x coordinate.
    /// 
    public int? CurrentX { get; private set; }
    /// 
    /// Gets the characters current y coordinate.
    /// 
    public int? CurrentY { get; private set; }
    /// 
    /// Gets the error cause of cancellation.
    /// 
    public WalkCancelReason? Error { get; private set; }
    /// 
    /// Update the time of last walk, called on WalkPacket.
    /// 
    /// The current characters x coordinate.
    /// The current characters y coordinate.
    internal void UpdateWalkTime(int currentX, int currentY)
    {
        CurrentX = currentX;
        CurrentY = currentY;
        LastWalkTime = DateTimeOffset.Now;
    }
    /// 
    /// Sets that the walk command handler is handling walk command.
    /// 
    /// The cancellation token source for cancelling the operation.
    /// The walk target x coordinate.
    /// The walk target y coordinate.
    /// Whether the user can cancel the operation by moving elsewhere.
    /// A task that may or may not have succeeded.
    internal async Task SetWalking(CancellationTokenSource cancellationTokenSource, int targetX, int targetY, bool userCanCancel)
    {
        await _semaphore.WaitAsync(cancellationTokenSource.Token);
        if (IsWalking)
        {
            // Cannot call CancelWalkingAsync as that would result in a deadlock
            _walkingCancellation?.Cancel();
            _walkingCancellation = null;
        }
        IsFinished = false;
        Error = null;
        TargetX = targetX;
        TargetY = targetY;
        CurrentX = CurrentY = null;
        _walkingCancellation = cancellationTokenSource;
        LastWalkTime = DateTime.Now;
        _userCanCancel = userCanCancel;
        if (!_walkHooked)
        {
            _hookManager.ClientWalked += OnCharacterWalked;
            _walkHooked = true;
        }
        _semaphore.Release();
    }
    /// 
    /// Cancel the walking token.
    /// 
    /// The cause.
    /// The cancellation token for cancelling the operation.
    /// A task that may or may not have succeeded.
    internal async Task CancelWalkingAsync(WalkCancelReason? error = null, CancellationToken ct = default)
    {
        await _semaphore.WaitAsync(ct);
        if (!IsWalking)
        {
            _semaphore.Release();
            return;
        }
        Error = error;
        try
        {
            _walkingCancellation?.Cancel();
        }
        catch
        {
            // ignored
        }
        _walkingCancellation = null;
        _semaphore.Release();
    }
    /// 
    /// Finish the walk successfully.
    /// 
    /// The cancellation token for cancelling the operation.
    /// A task that may or may not have succeeded.
    internal async Task FinishWalkingAsync(CancellationToken ct = default)
    {
        await _semaphore.WaitAsync(ct);
        IsFinished = true;
        _semaphore.Release();
        await CancelWalkingAsync(ct: ct);
    }
    private bool OnCharacterWalked(WalkEventArgs walkEventArgs)
    {
        if (IsWalking)
        {
            if (!_userCanCancel)
            {
                return false;
            }
            CancelWalkingAsync(WalkCancelReason.UserWalked)
                .GetAwaiter()
                .GetResult();
        }
        return true;
    }
}