//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
// UserRTX.NET satellite library | Author: Sergei Y.Kitáev
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
using System;
using System.Text;
using System.Reflection;
using System.Diagnostics;
using System.Runtime.CompilerServices;

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

static partial class fatal {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

[u.fatal_error_on_failure]
public static u.FatalErrorArgs PrepareArgs( Func<u.FatalErrorArgs> func )
{
   try { return func(); }
   catch ( Exception e ) {
      u.fatal_impl.RaisePreparationFail( e ); }
   throw u.fatal_impl.DeadBranchError();
}

/// Preliminary checking of stack frame count (before entry into actual raise-error routine):
[u.returns_source][u.fatal_error_on_failure]
public static int VerifyStackFrames( [u.range(0,int.MaxValue-1)] int skipFrames )
{
   try {
      // Outer library code ("u.…") is used here outside of actual raise-error routine:
      return u.arg_check.InRange( skipFrames, 0, int.MaxValue-1, nameof(skipFrames) );
   }
   catch ( Exception e ) {
      u.fatal_impl.RaisePreparationFail( e );
   }
   throw u.fatal_impl.DeadBranchError();
}

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

[u.doesnt_return][u.calling_assembly][MethodImpl( MethodImplOptions.NoInlining )]
public static u.EFake RaiseError( string causeOrCategory )
{
   var assembly = Assembly.GetCallingAssembly();
   var stackTrace = new StackTrace( 1, true );
   return RaiseError(PrepareArgs(() =>
      new u.FatalErrorArgs( assembly, causeOrCategory ){
         StackTrace = stackTrace
      }));
}

[u.doesnt_return][u.calling_assembly][MethodImpl( MethodImplOptions.NoInlining )]
public static u.EFake RaiseError( [u.not_negative] int skipFrames, string causeOrCategory )
{
   var assembly = Assembly.GetCallingAssembly();
   var stackTrace = new StackTrace( VerifyStackFrames( skipFrames ) + 1, true );
   return RaiseError(PrepareArgs(() =>
      new u.FatalErrorArgs( assembly, causeOrCategory ){
         StackTrace = stackTrace
      }));
}

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

[u.doesnt_return][u.calling_assembly][MethodImpl( MethodImplOptions.NoInlining )]
public static u.EFake RaiseError( string causeOrCategory, string errorMessage )
{
   var assembly = Assembly.GetCallingAssembly();
   var stackTrace = new StackTrace( 1, true );
   return RaiseError(PrepareArgs(() =>
      new u.FatalErrorArgs( assembly, causeOrCategory ){
         ErrorMessage = errorMessage,
         StackTrace = stackTrace
      }));
}
[u.doesnt_return][u.calling_assembly][MethodImpl( MethodImplOptions.NoInlining )]
public static u.EFake RaiseError( string causeOrCategory,
                                  string errorMessageFmt, object?[] args )
{
   var assembly = Assembly.GetCallingAssembly();
   var stackTrace = new StackTrace( 1, true );
   return RaiseError(PrepareArgs(() =>
      new u.FatalErrorArgs( assembly, causeOrCategory ){
         ErrorMessage = u.fatal_impl.FMT( errorMessageFmt, args ),
         StackTrace = stackTrace
      }));
}

[u.doesnt_return][u.calling_assembly][MethodImpl( MethodImplOptions.NoInlining )]
public static u.EFake RaiseError( [u.not_negative] int skipFrames,
                                  string causeOrCategory, string errorMessage )
{
   var assembly = Assembly.GetCallingAssembly();
   var stackTrace = new StackTrace( VerifyStackFrames( skipFrames ) + 1, true );
   return RaiseError(PrepareArgs(() =>
      new u.FatalErrorArgs( assembly, causeOrCategory ){
         ErrorMessage = errorMessage,
         StackTrace = stackTrace
      }));
}
[u.doesnt_return][u.calling_assembly][MethodImpl( MethodImplOptions.NoInlining )]
public static u.EFake RaiseError( [u.not_negative] int skipFrames,
                                  string causeOrCategory,
                                  string errorMessageFmt, object?[] args )
{
   var assembly = Assembly.GetCallingAssembly();
   var stackTrace = new StackTrace( VerifyStackFrames( skipFrames ) + 1, true );
   return RaiseError(PrepareArgs(() =>
      new u.FatalErrorArgs( assembly, causeOrCategory ){
         ErrorMessage = u.fatal_impl.FMT( errorMessageFmt, args ),
         StackTrace = stackTrace
      }));
}

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

[u.doesnt_return][u.calling_assembly][MethodImpl( MethodImplOptions.NoInlining )]
public static u.EFake RaiseError( string causeOrCategory, Exception e )
{
   var assembly = Assembly.GetCallingAssembly();
   var stackTrace = new StackTrace( 1, true );
   return RaiseError(PrepareArgs(() =>
      new u.FatalErrorArgs( assembly, causeOrCategory ){
         Exception = e,
         StackTrace = stackTrace
      }));
}

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

[u.doesnt_return]
public static u.EFake RaiseError( u.FatalErrorArgs parameters )
{
   lock ( SyncRoot ) {
      if ( !fatal.Settings.AllowProcessTermination )
         throw new EFatalError( fatal_impl.MakeReportText(
            new u.FatalAttributes(), parameters, true ) );
      if ( IsInRaiseErrorThread )
         throw new u.EFatalTerminationReentrance();
      _isInRaiseError = GetDataAL().IsInRaiseErrorThread = true;
      var attr = new u.FatalAttributes();
      try {
         DoRaiseError( attr, parameters );
      }
      catch ( Exception e ) {
         var sb = new StringBuilder( "user_rtx::fatal::RaiseError has failed." );
         sb.AppendLine();
         sb.AppendLine( $"Main module: {attr.MainModuleName};" );
         if ( attr.ProcessId.HasValue )
            sb.AppendLine( $"Process ID: {attr.ProcessId}" );
         sb.Append( $"Managed thread ID: {attr.ManagedThreadId}" );
         if ( attr.IsInMainThread )
            sb.Append( " (main)" );
         else
            if ( attr.IsInSecondaryThread )
               sb.Append( " (secondary)" );
         sb.AppendLine( "." );
         sb.AppendLine( fatal_impl.MakeExceptionText( e ) );
         fatal_impl.TraceError( sb.ToString() );
         Process.GetCurrentProcess().Kill();
      }
      return u.assertion_fail.DEAD_BRANCH();
   }
}

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

[u.doesnt_return]
static void DoRaiseError( u.FatalAttributes attr, u.FatalErrorArgs parameters )
{
   var fatalRoutine_0 = attr.FatalRoutine_0 ?? u.fatal_impl.FatalRoutine_0;
   var fatalRoutine = attr.FatalRoutine ?? u.fatal_impl.FatalRoutine;
   fatalRoutine_0( attr, parameters );
   int? terminationCode = fatalRoutine( attr, parameters );
   var process = Process.GetCurrentProcess();
   if ( terminationCode.HasValue && fatal_impl.IsWin32 )
      w32_core.TerminateProcess( process.Handle, unchecked( (uint)terminationCode ) );
   else
      process.Kill();
   u.assertion_fail.DEAD_BRANCH();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
} // fatal

} // user_rtx (.wasm)
