// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable enable

#if !MICROSOFT_CODEANALYSIS_CONTRACTS_NO_ERROR_REPORTING

using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace Microsoft.CodeAnalysis.ErrorReporting
{
    internal static class FailFast
    {
        /// <summary>
        /// A pre-created delegate to assign to <see cref="FatalError.ErrorReporterHandler" /> if needed.
        /// </summary>
        internal static readonly FatalError.ErrorReporterHandler Handler = static (e, _, _) => OnFatalException(e);

        [DebuggerHidden]
        [DoesNotReturn]
        [MethodImpl(MethodImplOptions.Synchronized)]
        internal static void OnFatalException(Exception exception)
        {
            // EDMAURER Now using the managed API to fail fast so as to default
            // to the managed VS debug engine and hopefully get great
            // Watson bucketing. Before vanishing trigger anyone listening.
            if (Debugger.IsAttached)
            {
                Debugger.Break();
            }

            // don't fail fast with an aggregate exception that is masking true exception
            if (exception is AggregateException aggregate && aggregate.InnerExceptions.Count == 1)
            {
                exception = aggregate.InnerExceptions[0];
            }

            DumpStackTrace(exception: exception);

            Environment.FailFast(exception.ToString(), exception);
            throw ExceptionUtilities.Unreachable(); // to satisfy [DoesNotReturn]
        }

        [DebuggerHidden]
        [DoesNotReturn]
        [MethodImpl(MethodImplOptions.Synchronized)]
        internal static void Fail(string message)
        {
            DumpStackTrace(message: message);
            Environment.FailFast(message);
            throw ExceptionUtilities.Unreachable(); // to satisfy [DoesNotReturn]
        }

        /// <summary>
        /// Dumps the stack trace of the exception and the handler to the console. This is useful
        /// for debugging unit tests that hit a fatal exception
        /// </summary>
        [Conditional("DEBUG")]
        internal static void DumpStackTrace(Exception? exception = null, string? message = null)
        {
            Console.WriteLine("Dumping info before call to failfast");
            if (message is object)
            {
                Console.WriteLine(message);
            }

            if (exception is object)
            {
                Console.WriteLine("Exception info");
                for (Exception? current = exception; current is object; current = current.InnerException)
                {
                    Console.WriteLine(current.Message);
                    Console.WriteLine(current.StackTrace);
                }
            }

            Console.WriteLine("Stack trace of handler");
            var stackTrace = new StackTrace();
            Console.WriteLine(stackTrace.ToString());

            Console.Out.Flush();
        }

        /// <summary>
        /// Checks for the given <paramref name="condition"/>; if the <paramref name="condition"/> is <c>true</c>, 
        /// immediately terminates the process without running any pending <c>finally</c> blocks or finalizers
        /// and causes a crash dump to be collected (if the system is configured to do so). 
        /// Otherwise, the process continues normally.
        /// </summary>
        /// <param name="condition">The conditional expression to evaluate.</param>
        /// <param name="message">An optional message to be recorded in the dump in case of failure. Can be <c>null</c>.</param>
        [Conditional("DEBUG")]
        [DebuggerHidden]
        internal static void Assert([DoesNotReturnIf(false)] bool condition, string? message = null)
        {
            if (condition)
            {
                return;
            }

            if (Debugger.IsAttached)
            {
                Debugger.Break();
            }

            Fail("ASSERT FAILED" + Environment.NewLine + message);
        }
    }
}
#endif
