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

#if !WASM
namespace CommonHelpers {
#else
namespace CommonHelpers.WASM {
#endif

//**************************************************************************************************
/// <summary>
/// </summary>
public static partial class HFatalError {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class FatalErrorParameters
{
   public Assembly Assembly;
   public string Cause, Message;
   public Exception Exception;
   public StackTrace StackTrace, DispatcherCallerStack;

   internal FatalErrorParameters( Assembly assembly, string cause,
                                  string message, Exception e,
                                  StackTrace stackTrace,
                                  StackTrace dispatcherCallerStack )
   {
      Assembly = assembly; Cause = cause; Message = message; Exception = e;
      StackTrace = stackTrace; DispatcherCallerStack = dispatcherCallerStack;
   }
}

//**************************************************************************************************

internal static void Access()
{}

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

static HFatalError()
{
   CommonHelpers_Core.Access();
}

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

[MethodImpl(MethodImplOptions.NoInlining)]
internal static HFakeException RaiseEx( Assembly assembly,
                                        string cause, string message, Exception e,
                                        StackTrace stackTrace,
                                        StackTrace dispatcherCallerStack=null )
{
   Monitor.Enter( _SyncRoot_ );
   if ( _IsWASM_ < 0 )
      _IsWASM_ = HBool.ToInt( CommonHelpersLibrary.IsWASM );

   if ( _IsInTerminationThread_ )
      if ( _IsInEmergencyHandler_ )
         throw new Exception();
      else
         FailOrHang(
            "Reentrance into \"HFatalError.Raise\" method from fatal termination thread." );

   if ( _IsInRaise_ )
      FailOrHang(
         "Reentrance into \"HFatalError.Raise\" method from fatal termination routine." );
   _IsInRaise_ = true;
   if ( _RaiseTrigger_ != null )
      try { _RaiseTrigger_(); } catch {}

   _RaiseThread_ = Thread.CurrentThread;

   lock (_ErrorTimeAccessSyncObject_)
      _ErrorTime_ = DateTime.Now;

   _ShowReport_ = (_Flags_ & HFatalErrorFlags.ShowReport) != 0 && HCompilationCH.IsWin32;

   if ( _ShowReport_ )
   {
      bool? interactsWithDesktopSpecification =
         HProgram.InteractsWithDesktopSpecification;
      _ShowPreliminaryReport_ =
         interactsWithDesktopSpecification.HasValue &&
         interactsWithDesktopSpecification.Value;
   }
   _TerminateEntireProcess_ =
      (_Flags_ & HFatalErrorFlags.TerminateEntireProcess) != 0;
   if ( !_TerminateEntireProcess_ )
      _ShowReport_ = false;
   _RaiseOnUnhandledException_ =
      (_Flags_ & HFatalErrorFlags.RaiseOnUnhandledException) != 0;
   _AvoidFatalErrorExceptionType_ =
      (_Flags_ & HFatalErrorFlags.AvoidFatalErrorExceptionType) != 0;

   ObtainApplicationName();

   if ( _ShowPreliminaryReport_ )
      TrySetWaitCursor();
   try
   {
      if ( cause==null && message==null && e==null ||
           message!=null && e!=null ||
           assembly==null )
      {
         cause   = "Internal program error";
         message = "Incorrect call of \"HFatalError.Raise\" method.";
         e = null;
      }
      if ( e != null )
         message = HException.ComposeChainedMessage( e );
      else
         if ( message != null )
            HString.ConvertAsText( ref message );
   }
   catch (Exception x)
   {
      FailOrHang( x );
   }
   if ( IsWASM )
      PerformFatalError( assembly, cause, message, e, stackTrace, dispatcherCallerStack );
   Thread thread = null;
   try
   {
      thread = HThread.Create( TerminationThreadRoutine );
      object threadStartParameter = new FatalErrorParameters( assembly,
         cause, message, e, stackTrace, dispatcherCallerStack );
      HThread.Start( thread, threadStartParameter );
   }
   catch
   {
      PerformFatalError( assembly, cause, message, e, stackTrace, dispatcherCallerStack );
   }
   if ( _TerminateEntireProcess_ )
      HThread.Hang();
   thread.Join();
   FailOrHang( "Unknown error in fatal error termination thread." );

   return HAssertionFailure.DEAD_BRANCH();
}

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

static void TerminationThreadRoutine(object parametersObject)
{
   _IsInTerminationThread_ = true;
   try
   {
      var errorParameters =
         (FatalErrorParameters)parametersObject;
      PerformFatalError( errorParameters.Assembly,
         errorParameters.Cause, errorParameters.Message,
         errorParameters.Exception, errorParameters.StackTrace,
         errorParameters.DispatcherCallerStack );
   }
   catch (HFatalErrorException e)
   {
      if (_RaiseOnUnhandledException_)
         try
            { DetachUnhandledExceptionHandler(); }
         catch (Exception x)
            { FailOrHang(x); }
      throw RecreateFatalErrorException(e);
   }
   catch (Exception e)
   {
      FailOrHang(e);
   }
}

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

static void FailOrHang(string errorText)
{
   if ( Interlocked.Exchange(ref _FailOrHangCallFlag_,1)!=0 || _IsInFailOrHang_ )
      HThread.Hang();
   _IsInFailOrHang_ = true;
   try { FailFast(errorText); }
   catch {}
   try { Trace(errorText); }
   catch {}
   try { Trace("Termination is hanged."); }
   catch {}
   HThread.Hang();
}

static void FailOrHang(Exception e)
{
   FailOrHang( e?.ToString() );
}

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

static Exception RecreateFatalErrorException(HFatalErrorException e)
{
   string errorText = Environment.NewLine + e.Message;
   Exception x = _AvoidFatalErrorExceptionType_ ?
      new Exception( errorText ) :
      new HFatalErrorException( errorText );
   if ( _AvoidFatalErrorExceptionType_ )
      x.Data[ _FatalErrorExceptionKey_ ] = true;
   return x;
}

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

static void PerformFatalError( Assembly assembly, string cause, string message, Exception e,
                               StackTrace stackTrace, StackTrace dispatcherCallerStack )
{
   _ReportText_ = HString.FormatAsText
   (
      "~~~ Fatal error in \u00AB{0}\u00BB ({1}): ~~~\n"+
      (HCompilationCH.IsWASM ? "{2}\n" : "\n{2}\n\n")+
      "~~~ End of fatal error report. ~~~",
      _MainModuleName_,
      ErrorTime.ToString("f"),
      CombineCauseSourceAndMessage
      (
         cause, assembly,
         e != null ? HException.ComposeChainedMessage( e, true, true ) :
         CombineMessageAndStackTraces( message, stackTrace, dispatcherCallerStack )
      )
   );

   if ( IsWASM )
   {
      ProcessEmergencyEvent( _EmergencyHandler_, _ReportText_ );
      Environment.FailFast( _ReportText_ );
   }

   bool isSameThreadCall = Thread.CurrentThread == _RaiseThread_;
   if ( !isSameThreadCall )
      OutputTraceMessage( _ReportText_ );

   string
      reportFilePath = null;
   Exception
      genReportException = null;
GENERATE_REPORT:
   try
   {
      reportFilePath = GenerateReport( assembly, cause, message, e,
                                       stackTrace, dispatcherCallerStack );
   }
   catch (Exception x)
   {
      genReportException = x;
   }
   if ( genReportException!=null && _ReportFolderPath_!=null && !_SecondAttemptToGenerateReport_ )
   {
      _SecondAttemptToGenerateReport_ = true; goto GENERATE_REPORT;
   }
   string uiErrorText = null;
   if ( _ShowReport_ )
   {
      uiErrorText = HString.FormatAsText
      (
         Verbose._FATAL_ERROR_IN_0_+"\n"+
         "\n"+
         "{1}\n"+
         "\n"+
         "*** "+Verbose.OS_PROCESS_WILL_BE_TERMINATED+" ***\n"+
         "\n"+
         Verbose.DO_YOU_WISH_TO_VIEW_DETAILED_REPORT,
         _MainModuleName_,
         CombineCauseSourceAndMessage( cause, assembly, message )
      );
   }
   TryReportToConsole( cause,
      e!=null ? HException.ComposeChainedMessage(e) : message );

   ProcessEmergencyEvent( _EmergencyHandler_, _ReportText_ );

   if ( isSameThreadCall )
      Environment.FailFast( _ReportText_ );
   if ( !_TerminateEntireProcess_ )
      throw new HFatalErrorException( _ReportText_ );
   if ( _ShowReport_ )
      if ( _ShowPreliminaryReport_ )
      {
         TryBeep();
         if ( ShowErrorBox( uiErrorText, FatalErrorCaption, true ) )
            if (reportFilePath!=null)
               try
               {
                  ShowReport( reportFilePath );
               }
               catch (Exception x)
               {
                  ShowErrorBox(
                     HString.FormatAsText(
                        Verbose._FATAL_ERROR_REPORT_SHOWING_ERROR_+"\n\n"+
                        "{0}", x.Message ),
                     _AppSimpleName_ );
               }
            else
               ShowErrorBox(
                  HString.FormatAsText(
                     Verbose._FATAL_ERROR_REPORT_GENERATION_ERROR_+"\n\n"+
                     "{0}", genReportException.Message ),
                  _AppSimpleName_ );
      }
      else
         if ( reportFilePath != null )
            try
               { ShowReport( reportFilePath ); }
            catch
               {}
   ProcessEmergencyEvent( _EmergencyFinalHandler_, _ReportText_ );
   FailFast( _ReportText_ );
}

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

static void ProcessEmergencyEvent(HFatalErrorEmergencyCallback handler,string reportText)
{
   if ( handler != null )
   {
      _IsInEmergencyHandler_ = true;
      try
         { handler( reportText ); }
      catch
         {}
      finally
         { _IsInEmergencyHandler_ = false; }
   }
}

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

static string CombineCauseSourceAndMessage(string cause,Assembly assembly,string message)
{
   var sb = new StringBuilder();

   if ( cause != null )
      HTextBuilder.AppendLine( sb, cause );

   if ( !HAppDomain.IsInDefaultAppDomain )
      HTextBuilder.AppendLine( sb,
         "(Application domain: {0})",
         AppDomain.CurrentDomain.FriendlyName );
   HTextBuilder.AppendLine( sb,
      "(Source module: {0})",
      HAssembly.GetModuleName(assembly) );
   HTextBuilder.AppendLine( sb,
      "({0}: ID={1})",
      IsMainRaiseThread ?
         "In main program thread" :
         HProgram.IsMainThreadSpecified ?
            "In secondary program thread" :
            "In following program thread",
      _RaiseThread_.ManagedThreadId );

   if ( !string.IsNullOrWhiteSpace( message ) )
      HTextBuilder.AppendLine( sb, !HCompilationCH.IsWASM, message );

   return sb.ToString();
}

static string CombineMessageAndStackTraces( string message,
                                            StackTrace stackTrace,
                                            StackTrace dispatcherCallerStack )
{
   var sb = new StringBuilder( (message ?? "").Trim() );
   var traceText = stackTrace!=null ? HStackTrace.GetText( stackTrace ) : null;
   if ( !string.IsNullOrWhiteSpace( traceText ) )
   {
      if ( sb.Length > 0 )
         { sb.AppendLine(); }
      else
         sb.Append(HString.FormatAsText(
            "=== {0}: ===\n"+
            "\n",
            Verbose.ErrorStackTrace ));
      sb.Append( traceText.Trim() );
   }
   traceText = dispatcherCallerStack!=null ? HStackTrace.GetText( dispatcherCallerStack ) : null;
   if ( !string.IsNullOrWhiteSpace( traceText ) )
   {
      if ( sb.Length > 0 )
         { sb.AppendLine(); sb.AppendLine(); }
      sb.Append(HString.FormatAsText(
         "=== {0}: ===\n"+
         "\n"+
         "{1}",
         Verbose.DispatcherCallerStack,
         traceText.Trim()
      ));
   }
   return sb.Length > 0 ? sb.ToString() :
      string.Format( "({0})", Verbose.NO_STACK_TRACE_INFORMATION_AVAILABLE );
}

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

static void AppDomainUnhandledException_Handler(object sender,UnhandledExceptionEventArgs e)
{
   ProcessUnhandledException( e.ExceptionObject as Exception ?? new Exception(
      "Exception object of unhandled exception event arguments is not of "+
      "\"System.Exception\" type." ) );
}

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

static HFatalErrorFlags
   _Flags_ = HFatalErrorFlags.Default;

static volatile bool
   _ShowReport_,
   _ShowPreliminaryReport_,
   _TerminateEntireProcess_,
   _RaiseOnUnhandledException_,
   _AvoidRaiseOnUnhandledExceptionUnderDebugger_,
   _AvoidFatalErrorExceptionType_;

static HFatalErrorEmergencyCallback
   _EmergencyHandler_,
   _EmergencyFinalHandler_;
static Action
   _RaiseTrigger_;
static volatile string
   _ReportFolderPath_;

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

static DateTime
   _ErrorTime_;

static volatile int
   _IsWASM_ = -1;
static volatile string
   _AppSimpleName_,
   _MainModuleName_,
   _ReportText_,
   _ReportFolderFullPath_;

static bool
   _IsInEmergencyHandler_;
static int
   _FailOrHangCallFlag_;

static volatile Thread
   _RaiseThread_;

[ThreadStatic]
static bool
   _IsInRaise_,
   _IsInFailOrHang_,
   _IsInTerminationThread_,
   _SecondAttemptToGenerateReport_;

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

static readonly object
   _FatalErrorExceptionKey_ = new object(),
   _ErrorTimeAccessSyncObject_ = new object(),
   _SyncRoot_ = new object();

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
} // HFatalError
//**************************************************************************************************

} // CommonHelpers (.WASM)
