//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
// UserRTX.NET satellite library | Author: Sergei Y.Kitáev
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
using System;
using System.Threading;
using System.Threading.Tasks;

#if !WASM
namespace user_rtx { using u = user_rtx;
#else
namespace user_rtx.wasm { using u = user_rtx.wasm;
#endif

/// <summary>
/// Mutex for asynchronous threads
/// </summary>
public class AsyncMonitor {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class DataAL {
   public int OwnedCount;
} // DataAL
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

[u.volatile_access]
public bool IsCaptured => _isCaptured;

/// <exception cref="InvalidOperationException" />
[u.async_local]
public bool IsOwned => OwnedCount > 0;

/// <exception cref="InvalidOperationException" />
[u.async_local]
public int OwnedCount {
   get {
      var data = _dataAL.Value;
      return data != null ? data.OwnedCount :
         throw new InvalidOperationException(
            "Async-local data has not been initialized (AsyncMonitor)." );
   }
   private set => _dataAL.Value.NN().OwnedCount = value;
}

//==================================================================================================

[u.async_local]
public bool InitializeAL()
{
   if ( _dataAL.Value != null )
      return false;
   _dataAL.Value = new DataAL();
   return true;
}

//--------------------------------------------------------------------------------------------------

/// <exception cref="InvalidOperationException" />
public async ValueTask<bool> Enter_A()
{
   int count = OwnedCount;
   if ( count > 0 ) {
      OwnedCount = count + 1;
      return false;
   }
   await _semaphore.WaitAsync();
   OwnedCount = 1;
   return _isCaptured = true;
}

/// <exception cref="ArgumentOutOfRangeException" />
/// <exception cref="InvalidOperationException" />
public async ValueTask<bool?> TryEnter_A( int millisecondTimeout )
{
   u.arg_check.NotNegative( millisecondTimeout, nameof(millisecondTimeout) );
   int count = OwnedCount;
   if ( count > 0 ) {
      OwnedCount = count + 1;
      return null;
   }
   if ( !await _semaphore.WaitAsync( millisecondTimeout ) )
      return false;
   OwnedCount = 1;
   return _isCaptured = true;
}

//--------------------------------------------------------------------------------------------------
/*
/// <exception cref="InvalidOperationException" />
public bool SyncEnter()
{
   int count = OwnedCount;
   if ( count > 0 ) {
      OwnedCount = count + 1;
      return false;
   }
   _semaphore.Wait();
   OwnedCount = 1;
   return _isCaptured = true;
}

/// <exception cref="ArgumentOutOfRangeException" />
/// <exception cref="InvalidOperationException" />
public bool? TrySyncEnter( int millisecondTimeout )
{
   u.arg_check.NotNegative( millisecondTimeout, nameof(millisecondTimeout) );
   int count = OwnedCount;
   if ( count > 0 ) {
      OwnedCount = count + 1;
      return null;
   }
   if ( !_semaphore.Wait( millisecondTimeout ) )
      return false;
   OwnedCount = 1;
   return _isCaptured = true;
}
*/
//--------------------------------------------------------------------------------------------------

/// <exception cref="InvalidOperationException" />
public bool Exit()
{
   int count = OwnedCount;
   if ( count == 0 )
      throw new InvalidOperationException(urtx.MSG(
         nameof(AsyncMonitor),
         "The monitor is not owned." ));
   if ( count > 1 ) {
      OwnedCount = count - 1;
      return false;
   }
   _isCaptured = false;
   _semaphore.Release();
   OwnedCount = 0;
   return true;
}

/// <exception cref="InvalidOperationException" />
public bool ExitIfOwned()
{
   int count = OwnedCount;
   if ( count == 0 )
      return false;
   if ( count > 1 ) {
      OwnedCount = count - 1;
      return false;
   }
   _isCaptured = false;
   _semaphore.Release();
   OwnedCount = 0;
   return true;
}

//==================================================================================================

readonly SemaphoreSlim _semaphore = new ( 1, 1 );
readonly AsyncLocal<DataAL?> _dataAL = new ();
volatile bool _isCaptured;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
} // AsyncMonitor

} // user_rtx (.wasm)
