//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
// 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>
/// Speeker based playback
/// </summary>
[u.win32][u.thread_safe]
public static partial class beeper {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

public const int
   MinFrequency = 37, // (0x25)
   MaxFrequency = Int16.MaxValue, // 32767 (0x7FFF)

   MinDuration = 0,
   MaxDuration = Int32.MaxValue; // 32767 (0x7FFF)

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

public const int
   BaseFrequencyDef = 440,
   BaseDurationDef  = 100;

public const double
   SimpleBeepRelativeFrequencyDef = 2.0,
   SimpleBeepRelativeDurationDef  = 2.0;

const ThreadPriority
   PlaybackPriorityDef = ThreadPriority.Highest,
   IdlePriorityDef     = ThreadPriority.Highest;

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

[u.volatile_access]
public static bool IsMute {
   get => _isMute;
   set => _isMute = value; }

[u.volatile_access]
public static bool IsInitialized => _isInitialized;

[u.volatile_access]
public static bool IsPlaybackActive => _isPlaybackActive;

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

/// <exception cref="ArgumentOutOfRangeException" />
[u.def( BaseFrequencyDef )][u.positive][u.volatile_access]
public static int BaseFrequency {
   get => _baseFrequency;
   set => _baseFrequency = u.arg_check.Positive_PV( value ); }

/// <exception cref="ArgumentOutOfRangeException" />
[u.def( BaseDurationDef )][u.positive][u.volatile_access]
public static int BaseDuration {
   get => _baseDuration;
   set => _baseDuration = u.arg_check.Positive_PV( value ); }

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

/// <exception cref="ArgumentOutOfRangeException" />
[u.def( SimpleBeepRelativeFrequencyDef )]
public static double SimpleBeepRelativeFrequency {
   get { lock ( SyncRoot ) return _simpleBeepRelativeFrequency; }
   set { lock ( SyncRoot ) _simpleBeepRelativeFrequency = u.arg_check.InRange_PV(
      value, MinFrequency, MaxFrequency ); }}

/// <exception cref="ArgumentOutOfRangeException" />
[u.def( SimpleBeepRelativeDurationDef )]
public static double SimpleBeepRelativeDuration {
   get { lock ( SyncRoot ) return _simpleBeepRelativeDurationDef; }
   set { lock ( SyncRoot ) _simpleBeepRelativeDurationDef = u.arg_check.InRange_PV(
      value, MinDuration, MaxDuration ); }}

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

[u.def( PlaybackPriorityDef )][u.volatile_access]
public static ThreadPriority PlaybackPriority {
   get => _playbackPriority;
   set => _playbackPriority = value; }

[u.def( IdlePriorityDef )][u.volatile_access]
public static ThreadPriority IdlePriority {
   get => _idlePriority;
   set => _idlePriority = value; }

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

public static bool IsValidFrequency( int frequency ) =>
   u.val.IsBetween( frequency, MinFrequency, MaxFrequency );

public static bool IsValidDuration( int duration ) =>
   u.val.IsBetween( duration, MinDuration, MaxDuration );

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

/// <exception cref="ArgumentOutOfRangeException" />
[u.returns_source]
public static int VerifyFrequency( int frequency )
{
   if ( !IsValidFrequency( frequency ) )
      throw e_argument_outofrange.New(
         "Incorrect beep frequency: {0} (Hz).\n"+
         "Valid range is: {1}..{2} (Hz).",
         frequency, MinFrequency, MaxFrequency );
   return frequency;
}

/// <exception cref="ArgumentOutOfRangeException" />
[u.returns_source]
public static int VerifyDuration( int duration )
{
   if ( !IsValidDuration( duration ) )
      throw e_argument_outofrange.New(
         "Incorrect beep duration: {0} (ms).\n"+
         "Valid range is: {1}..{2}.",
         duration, MinDuration, MaxDuration );
   return duration;
}

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

public static void Initialize()
{
   lock ( InitializationLocker )
      if ( !_isInitialized ) {
         _playbackThread = new Thread( PlaybackThreadRoutine ) {
            Priority = _idlePriority,
            IsBackground = true };

         _playStartedEvent = new AutoResetEvent( false );

         lock ( ThreadActivityLocker ) {
            _playbackThread.Start();
            Monitor.Wait( ThreadActivityLocker );
         }

         _isInitialized = true;
      }
}

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

public static bool Beep() =>
   Beep( SimpleBeepRelativeFrequency );

public static bool Beep( bool synchronously ) =>
   Beep( synchronously, SimpleBeepRelativeFrequency );

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

public static bool Beep( double relativeFrequency ) =>
   Beep( false, relativeFrequency, SimpleBeepRelativeDuration );

public static bool Beep( bool synchronously, double relativeFrequency ) =>
   Beep( synchronously, relativeFrequency, SimpleBeepRelativeDuration );

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

public static bool Beep( double relativeFrequency, double relativeDuration ) =>
   Beep( false, relativeFrequency, relativeDuration );

public static bool Beep( bool synchronously, double relativeFrequency, double relativeDuration ) =>
   Play( synchronously, new u.BeepNote[]{ (relativeFrequency, relativeDuration) } );

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

public static bool SyncBeep() =>
   SyncBeep( SimpleBeepRelativeFrequency );

public static bool SyncBeep( double relativeFrequency ) =>
   SyncBeep( relativeFrequency, SimpleBeepRelativeDuration );

public static bool SyncBeep( double relativeFrequency, double relativeDuration ) =>
   Beep( true, relativeFrequency, relativeDuration );

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

public static bool Play( params u.BeepNote[] beeps ) =>
   Play( false, beeps );

public static bool Play( bool synchronously, params u.BeepNote[] beeps )
{
   if ( !u.platform.IsWin32 || beeps.Length==0 || IsMute )
      return false;

   var beepsToPlay = PrepareBeeps( beeps );
   if ( beepsToPlay == null )
      return false;

   Monitor.Enter( PlayStopSignalLocker );
   lock ( PlayStartLocker ) {
      try {
         Initialize(); }
      catch {
         Monitor.Exit( PlayStopSignalLocker );
         throw; }

      lock ( ThreadActivityLocker ) {
         Monitor.Exit( PlayStopSignalLocker );

         _isPlaybackActive = false;
         _playbackBeeps = beepsToPlay;
         PlaybackThread.Priority = _playbackPriority;

         Monitor.Pulse( ThreadActivityLocker );
      }
      _playInitiatorLastThread = Thread.CurrentThread;
      PlayStartedEvent.WaitOne();

      if ( synchronously )
         WaitForPlaybackFinish();
   }
   return true;
}

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

public static void SyncPlay( params u.BeepNote[] beeps ) =>
   Play( true, beeps );

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

public static bool StopActivePlayback() =>
   StopActivePlayback( true );

public static bool StopActivePlayback( bool waitForPlaybackFinish )
{
   if ( !u.platform.IsWin32 )
      return false;
   _playStopFlag = true;
   if ( !waitForPlaybackFinish )
      return false;
   lock ( PlayStopControlLocker ) {
      _playStopInitiatorThread = Thread.CurrentThread;
      _playStopOwnerThread = null;
      lock ( PlayStopSignalLocker )
         WaitForPlaybackFinish();
      return _playStopOwnerThread == Thread.CurrentThread;
   }
}

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

public static bool WaitForPlaybackFinish()
{
   if ( !u.platform.IsWin32 || !IsInitialized )
      return false;
   lock ( PlaybackLocker ) {}
   return true;
}

public static async Task<bool> WaitForPlaybackFinish_A()
{
   if ( !u.platform.IsWin32 || !IsInitialized )
      return false;
   var playInitiatorLastThread = _playInitiatorLastThread;
   while ( !Monitor.TryEnter( PlaybackLocker ) ) {
      await Task.Delay( u.asynch.PollInterval ).ConfigureAwait(false);
      if ( _playInitiatorLastThread != playInitiatorLastThread )
         return true;
   }
   Monitor.Exit( PlaybackLocker );
   return true;
}

/// <exception cref="ArgumentOutOfRangeException" />
public static async Task<bool> WaitForPlaybackFinish_A( [u.not_negative] int milliseconds )
{
   u.arg_check.NotNegative( milliseconds, nameof(milliseconds) );
   if ( !u.platform.IsWin32 || !IsInitialized )
      return false;
   var playInitiatorLastThread = _playInitiatorLastThread;
   long initialTicks = u.ticks.Current;
   while ( !Monitor.TryEnter( PlaybackLocker ) ) {
      if ( u.ticks.ElapsedMilliseconds( initialTicks ) > milliseconds )
         return false;
      await Task.Delay( u.asynch.PollInterval ).ConfigureAwait(false);
      if ( _playInitiatorLastThread != playInitiatorLastThread )
         return true;
   }
   Monitor.Exit( PlaybackLocker );
   return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
} // beeper

} // user_rtx (.wasm)
