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

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

/// <summary>
/// Exception support
/// </summary>
public static class except {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

public static string Message( Exception e ) =>
   !u.str.IsNLWS( e.Message ) ?
      e.Message : e.GetType().ToString();

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

public static string SignificantMessage( Exception e ) =>
   Message( BypassPackingExceptions( e ) );

public static string SignificantMessage( Exception e, params Type[] packingExceptionTypes ) =>
   Message( BypassPackingExceptions( e, packingExceptionTypes ) );

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

/// <summary>
/// Error message (chained message to display)
/// </summary>
public static string EM( Exception e ) =>
   EM( e, PackingExceptionTypes() );
/// <summary>
/// Error message (chained message to display)
/// </summary>
public static string EM( Exception e, params Type[] packingExceptionTypes )
{
   VerifyExceptionArgTypes( packingExceptionTypes );

   var list = new List<Exception>();

   for ( Exception? ex=e; ex!=null; ex=GetFlattenedInner( ex ) )
      if ( !u.type.IsValueOneOf( ex, packingExceptionTypes ) )
         list.Add( ex );

   if ( list.Count == 0 )
      return Message( e );

   var sb = new StringBuilder();
   for ( int i=0; i<list.Count; i++ ) {
      if ( i > 0 )
         sb.AppendLine();
      if ( list.Count > 1 ) {
         sb.Append( u.unicode.Bullet );
         sb.Append( ' ' );
      }
      sb.Append( Message( list[i] ) );
   }
   return sb.ToString();
}

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

public static string ComposeMessage( Exception e ) =>
   ComposeMessage( e, false );

public static string ComposeMessage( Exception e, bool withStackTrace )
{
   var sb = new StringBuilder();
   sb.Append( e.GetType() );
   sb.Append( ": " );
   sb.Append( e.Message );
   if ( withStackTrace ) {
      string? stackTraceText = e.StackTrace;
      if ( stackTraceText != null ) {
         sb.AppendLine();
         sb.Append( stackTraceText );
      }
   }
   return sb.ToString();
}

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

public static string ChainedMessage( Exception e ) =>
   ChainedMessage( e, false, false );

public static string ChainedMessage( Exception e, bool withStackTrace ) =>
   ChainedMessage( e, withStackTrace, false );

public static string ChainedMessage( Exception e, bool withStackTrace, bool separateItems )
{
   var sb = new StringBuilder();
   for ( Exception? x=e; x!=null; x=GetFlattenedInner( x ) ) {
      if ( x != e ) {
         sb.AppendLine();
         if ( separateItems )
            sb.AppendLine();
      }
      if ( e.InnerException != null ) {
         sb.Append( u.unicode.Bullet );
         sb.Append( ' ' );
      }
      sb.Append( ComposeMessage( x, withStackTrace ) );
   }
   return sb.ToString();
}

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

public static Exception? GetFlattenedInner( Exception e )
{
   var x = e.InnerException;
   return (x is AggregateException ea) ? ea.Flatten() : x;
}

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

static string NormalMessage(string? message) =>
   message != null ? message.TXT() : "";

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

public static bool IsPackingException( Exception e )
{
   lock ( _syncRoot )
      return _packingRegistrations.ContainsKey( e.GetType() );
}

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

public static Exception BypassPackingExceptions( Exception e )
{
   lock ( _syncRoot )
      for ( Exception? x=e; x!=null; x=GetFlattenedInner( x ) )
         if ( !IsPackingException( x ) )
            return x;
   return e;
}
/// <exception cref="ArgumentException" />
public static Exception BypassPackingExceptions( Exception e, params Type[] packingExceptionTypes )
{
   VerifyExceptionArgTypes( packingExceptionTypes );
   for ( Exception? ex=e; ex!=null; ex=GetFlattenedInner( ex ) )
      if ( !u.type.IsValueOneOf( ex, packingExceptionTypes ) )
         return ex;
   return e;
}

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

public static Type[] PackingExceptionTypes()
{
   lock ( _syncRoot ) {
      ICollection keys = _packingRegistrations.Keys;
      var array = new Type[ keys.Count ];
      keys.CopyTo( array, 0 );
      return array;
   }
}

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

/// <exception cref="ArgumentException" />
public static void RegisterPackingException( Type exceptionType )
{
   VerifyExceptionArgType( exceptionType );
   lock ( _syncRoot )
      _packingRegistrations[ exceptionType ] = null;
}

/// <exception cref="ArgumentException" />
public static void UnregisterPackingException( Type exceptionType )
{
   VerifyExceptionArgType( exceptionType );
   lock ( _syncRoot )
      _packingRegistrations.Remove( exceptionType );
}

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

/// <exception cref="ArgumentException" />
public static void RegisterPackingExceptions( params Type[] exceptionTypes )
{
   VerifyExceptionArgTypes( exceptionTypes );
   lock ( _syncRoot )
      foreach ( var type in exceptionTypes )
         RegisterPackingException( type );
}
/// <exception cref="ArgumentException" />
public static void UnregisterPackingExceptions( params Type[] exceptionTypes )
{
   VerifyExceptionArgTypes( exceptionTypes );
   lock ( _syncRoot )
      foreach ( var type in exceptionTypes )
         UnregisterPackingException( type );
}

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

/// <exception cref="ArgumentException" />
static void VerifyExceptionArgType( Type exceptionType )
{
   if ( !u.type.IsTypeOf<Exception>( exceptionType ) )
      throw new ArgumentException(
         $"Exception type was expected for {nameof(exceptionType)} parameter." );
}

/// <exception cref="ArgumentException" />
static void VerifyExceptionArgTypes( Type[] exceptionTypes )
{
   foreach ( var type in exceptionTypes )
      if ( !u.type.IsTypeOf<Exception>( type ) )
         throw new ArgumentException(
            $"Array of exception types was expected for {nameof(exceptionTypes)} parameter." );
}

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

static E MarkWith<E>( E exeption, object marker ) where E: Exception
{
   exeption.Data[ marker ] = true;
   return exeption;
}
static bool IsMarkedWith<E>( E exeption, object marker ) where E: Exception =>
   object.Equals( exeption.Data[ marker ], true );

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

static except() =>
   RegisterPackingExceptions(
      typeof( TargetInvocationException ),
      typeof( TypeInitializationException ),
      typeof( AggregateException ) );

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

static readonly Dictionary<Type,object?>
   _packingRegistrations = new ();
static readonly object
   _syncRoot = new ();

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
} // except

} // user_rtx (.wasm)
