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

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

static partial class fatal_impl {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

/// <exception cref="u.EFatalTerminationError" />
public static string MakeHtmlReport( u.FatalAttributes attr, u.FatalErrorArgs parameters )
{
   string reportFolderPath, filePath;
   if ( attr.ReportPathMaker != null ) {
      try {
         filePath = attr.ReportPathMaker();
      }
      catch ( Exception e ) {
         throw e_fatal_termination_error.New(
            "Can not obtain fatal error report path.\n"+
            "{0}", e.Message );
      }
      try {
         string? directory = Path.GetDirectoryName( filePath );
         if ( string.IsNullOrEmpty( directory ) )
            throw new EFatalTerminationError( "Nothing is specified in folder path." );
         reportFolderPath = NN( directory );
      }
      catch ( Exception e ) {
         throw e_fatal_termination_error.New(
            "Can not extract directory from fatal report path.\n"+
            "{0}\n"+
            "File: “{1}”.",
            e.Message, filePath );
      }
      reportFolderPath = GetReportFolderFullPath( reportFolderPath );
   }
   else {
      reportFolderPath = GetReportFolderPath( attr );
      filePath = GetReportPath( reportFolderPath, attr );
   }
   CreateReportDirectory( reportFolderPath );
   try {
      DoMakeHtmlReport( filePath, attr, parameters );
   }
   catch ( Exception e ) {
      throw e_fatal_termination_error.New(
         "Can not generate fatal error report.\n"+
         "{0}\n"+
         "File: “{1}”.",
         e.Message, filePath );
   }
   return filePath;
}

public static void DoMakeHtmlReport( string filePath,
                                     u.FatalAttributes attr, u.FatalErrorArgs parameters )
{
   const string
      fontFamilyList = "Trebuchet MS, Arial";
   string
      fileName = Path.GetFileName( filePath ),
      fullPath = Path.GetFullPath( filePath );
   using ( TextWriter writer = new StreamWriter( filePath, false, new UTF8Encoding(true) )) {
      writer.WriteLine( "<html>" );

      writer.WriteLine( "<head>" );
      writer.WriteLine( "<meta http-equiv=content-type content=\"text/html; charset=utf-8\">" );
      writer.WriteLine( "<title>{0}</title>", fileName );
      writer.WriteLine( "<style>" );
      writer.WriteLine( "h4 {{ font-family: \"{0}\"; }}", fontFamilyList );
      writer.WriteLine( "</style>" );
      writer.WriteLine( "</head>" );

      writer.WriteLine( "<body bgcolor=\"Linen\"><font style=\"font-family: {0}\" size=\"2\">",
                        fontFamilyList );

      writer.WriteLine( "<div align=\"center\" style=\"font-weight: bold\">" );
      writer.WriteLine(
         "<u>Fatal error in &laquo;{0}&raquo; (<i>{1}</i>):</u><br>",
         attr.MainModuleName, attr.ErrorTime.ToString( "f", DateTimeFormatInfo.InvariantInfo ) );
      if ( parameters.CauseOrCategory != null )
         writer.WriteLine(
            "<font color=\"Navy\" size=\"4\"><i>{0}</i></font><br>",
            parameters.CauseOrCategory );
      writer.WriteLine(
         "Source assembly (raise-call): <i>{0}</i><br>",
         parameters.SourceAssembly );
      writer.WriteLine(
         "Managed thread ID: <b>{0}</b>{1}<br>",
         attr.ManagedThreadId,
         attr.IsInMainThread ? " (main)" :
         attr.IsInSecondaryThread ? " (secondary)" : "" );
      writer.WriteLine( "</div>" );

      var e = parameters.Exception;
      writer.WriteLine( "<hr><p>" );
      if ( e != null ) {
         if ( e.InnerException != null )
            writer.WriteLine( "<ul>" );
         for ( var x=e; x!=null; x=GetFlattenedInnerException( x ) ) {
            if ( x != e )
               WriteLineToHtml( writer );
            if ( e.InnerException != null )
               writer.Write( "<li>" );
            writer.Write( "<font color=\"Red\"><b><u>" );
            WriteLinesToHtml( writer, ComposeExceptionMessage( x ) );
            writer.Write( "</u></b></font>" );
            if ( x.StackTrace != null )
               WriteLinesToHtml( writer, x.StackTrace );
         }
         if ( e.InnerException != null )
            writer.WriteLine( "</ul>" );
      }
      else {
         var message = parameters.ErrorMessage ?? "";
         var stackTrace = parameters.StackTrace;
         var dispatcherCallerStack = parameters.DispatcherCallerStack;
         writer.WriteLine( "<h4><font color=\"Red\"><b>{0}</b></font></h4>", message );
         if ( stackTrace != null ) {
            if ( message == "" ) {
               WriteLineToHtml( writer, "=== ERROR STACK TRACE: ===" );
               WriteLineToHtml( writer );
            }
            WriteLinesToHtml( writer, StackTraceToText( stackTrace ) );
         }
         if ( dispatcherCallerStack != null ) {
            if ( message != "" || stackTrace != null )
               WriteLineToHtml( writer );
            WriteLineToHtml( writer, "=== DISPATCHER CALLER STACK: ===" );
            WriteLineToHtml( writer );
            WriteLinesToHtml( writer, StackTraceToText( dispatcherCallerStack ) );
         }
         if ( message == "" && stackTrace == null && dispatcherCallerStack == null )
            WriteLineToHtml( writer, "({0})", "No stack trace information available." );
      }
      writer.WriteLine( "</p><hr>" );

      writer.WriteLine(
         "<p><h4 align=\"Center\"><font color=\"DimGray\" size=3>{0}</font></h4></p>",
         "<i>&laquo; End of fatal error report &raquo;</i>" );

      writer.WriteLine( "</body>" );

      writer.WriteLine( "</html>" );

      //writer.Flush(); // <- may be it'll prevent prevent report content desappearance in browser
      writer.Close();
   }
   attr.ReportFilePath = fullPath;
}

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

/// <exception cref="u.EFatalTerminationError" />
public static void ShowHtmlReport( string reportPath )
{
   try {
      var startupInfo =
         RuntimeInformation.IsOSPlatform( OSPlatform.Windows ) ?
            new ProcessStartInfo( reportPath ){
               UseShellExecute = true,
               Verb = "open" } :
            RuntimeInformation.IsOSPlatform( OSPlatform.OSX ) ?
               new ProcessStartInfo( "open", reportPath ) :
            RuntimeInformation.IsOSPlatform( OSPlatform.Linux ) ||
            RuntimeInformation.IsOSPlatform( OSPlatform.FreeBSD ) ?
               new ProcessStartInfo( "xdg-open", reportPath )
            : throw new PlatformNotSupportedException();
      startupInfo.WindowStyle = ProcessWindowStyle.Maximized;
      Process.Start( startupInfo );
   }
   catch ( Exception e ) {
      throw e_fatal_termination_error.New(
         "Can not open fatal error report (HTML-document).\n"+
         "{0}\n"+
         "File: “{1}”.",
         e.Message, reportPath );
   }
}

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

/// <exception cref="u.EFatalTerminationError" />
public static string GetReportFolderPath( u.FatalAttributes attr )
{
   string? rootFolderPath = attr.ReportRootFolder;
   if ( rootFolderPath == null )
      try {
         rootFolderPath = Path.GetTempPath();
      }
      catch ( Exception e ) {
         throw e_fatal_termination_error.New(
            "Can not obtain temporary folder path.\n"+
            "{0}", e.Message );
      }
   rootFolderPath = GetReportFolderFullPath( rootFolderPath );
   try {
      string folderPath = Path.Combine( rootFolderPath, attr.AppSimpleName );
      return attr.AddDateSubfolder ?
         Path.Combine( folderPath, ReportDaySubfolderName( attr ) ) :
         folderPath;
   }
   catch ( Exception e ) {
      throw e_fatal_termination_error.New(
         "Can not compose fatal error report folder path.\n"+
         "{0}", e.Message );
   }
}

/// <exception cref="u.EFatalTerminationError" />
public static string GetReportFolderFullPath( string path )
{
   try {
      return Path.GetFullPath( path );
   }
   catch ( Exception e ) {
      throw e_fatal_termination_error.New(
         "Can not determine full path to fatal error report folder.\n"+
         "{0}", e.Message );
   }
}

/// <exception cref="u.EFatalTerminationError" />
public static void CreateReportDirectory( string folderPath )
{
   try {
      Directory.CreateDirectory( folderPath );
   }
   catch ( Exception e ) {
      throw e_fatal_termination_error.New(
         "Can not prepare fatal error report folder.\n"+
         "{0}", e.Message );
   }
}

public static string ReportDaySubfolderName( u.FatalAttributes attr ) =>
   string.Format( "{0:yyyy}-{0:MM}-{0:dd}", attr.ErrorTime );

/// <exception cref="u.EFatalTerminationError" />
public static string GetReportPath( string folderPath, u.FatalAttributes attr ) =>
   Path.Combine( folderPath, string.Format(
      "{0} - FatalErrorReport {1}.html", attr.AppSimpleName,
      attr.ErrorTime.ToString( "yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture ) ) );

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

static void WriteLineToHtml( TextWriter writer ) =>
   WriteLineToHtml( writer, "" );

static void WriteLineToHtml( TextWriter writer, string line ) =>
   writer.WriteLine( "{0}<br>", HtmlProvideWithEscapes( line ) );

static void WriteLineToHtml( TextWriter writer, string lineFmt, params object?[] args ) =>
   WriteLineToHtml( writer, FMT( lineFmt, args ) );

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

static void WriteLinesToHtml( TextWriter writer, string lines )
{
   if ( lines == "" ) {
      WriteLineToHtml( writer );
      return;
   }
   var text = TXT( lines );
   int from=0, to;
   string newLine = Environment.NewLine;
   while ( (to = text.IndexOf(newLine,from)) >= 0 ) {
      WriteLineToHtml( writer, text[ from..to ] );
      from = to + newLine.Length;
   }
   if ( from < text.Length )
      WriteLineToHtml( writer, text[ from..text.Length ] );
}

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

static string HtmlCharToStr( char ch )
{
   switch ( ch )
   {
   case '<': return "&lt;";
   case '>': return "&gt;";
   case '&': return "&amp;";
   case '"': return "&quot;";
   case '\'': return "&apos;";
   case '/': return "&#x2F;";
   case '\\': return "&#x5C;";
   //case '': return "&;";
   default: return ch.ToString();
   } // switch
}

static string HtmlProvideWithEscapes( string text )
{
   var sb = new StringBuilder();
   foreach ( char ch in text )
      if ( ch == '\n' )
         sb.Append( ' ' );
      else
         if ( ch != '\r' )
            sb.Append( HtmlCharToStr( ch ) );
   return sb.ToString();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
} // fatal_impl

} // user_rtx (.wasm)
