// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
//
// <OWNER>AlfreMen</OWNER>
//

#if MONO
#undef FEATURE_COMINTEROP
#endif

using System;
using System.Security;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

#if FEATURE_COMINTEROP
using System.Runtime.InteropServices.WindowsRuntime;

using WFD = Windows.Foundation.Diagnostics;
#endif


namespace System.Threading.Tasks
{

    [FriendAccessAllowed]
    internal enum CausalityTraceLevel
    {
#if FEATURE_COMINTEROP
        Required = WFD.CausalityTraceLevel.Required,
        Important = WFD.CausalityTraceLevel.Important,
        Verbose = WFD.CausalityTraceLevel.Verbose
#else
        Required,
        Important,
        Verbose
#endif
    }

    [FriendAccessAllowed]
    internal enum AsyncCausalityStatus
    {
#if FEATURE_COMINTEROP
        Canceled = WFD.AsyncCausalityStatus.Canceled,
        Completed = WFD.AsyncCausalityStatus.Completed,
        Error = WFD.AsyncCausalityStatus.Error,
        Started = WFD.AsyncCausalityStatus.Started
#else
        Started,
        Completed,
        Canceled,
        Error
#endif
    }

    internal enum CausalityRelation
    {
#if FEATURE_COMINTEROP
        AssignDelegate = WFD.CausalityRelation.AssignDelegate,
        Join = WFD.CausalityRelation.Join,
        Choice = WFD.CausalityRelation.Choice,
        Cancel = WFD.CausalityRelation.Cancel,
        Error = WFD.CausalityRelation.Error
#else
        AssignDelegate,
        Join,
        Choice,
        Cancel,
        Error
#endif
    }

    internal enum CausalitySynchronousWork
    {
#if FEATURE_COMINTEROP
        CompletionNotification = WFD.CausalitySynchronousWork.CompletionNotification,
        ProgressNotification = WFD.CausalitySynchronousWork.ProgressNotification,
        Execution = WFD.CausalitySynchronousWork.Execution
#else
        CompletionNotification,
        ProgressNotification,
        Execution
#endif
    }

    [FriendAccessAllowed]
    internal static class AsyncCausalityTracer
    {
        static internal void EnableToETW(bool enabled) 
        { 
#if !MONO
            if (enabled)
               f_LoggingOn |= Loggers.ETW;
            else 
               f_LoggingOn &= ~Loggers.ETW;
#endif
        }

        [FriendAccessAllowed]
        internal static bool LoggingOn
        {
            [FriendAccessAllowed]
            get
            {
#if FEATURE_COMINTEROP
                return f_LoggingOn != 0;
#else
                return false;
#endif
            }
        }

#if FEATURE_COMINTEROP
        //s_PlatformId = {4B0171A6-F3D0-41A0-9B33-02550652B995}
        private static readonly Guid s_PlatformId = new Guid(0x4B0171A6, 0xF3D0, 0x41A0, 0x9B, 0x33, 0x02, 0x55, 0x06, 0x52, 0xB9, 0x95);

        //Indicates this information comes from the BCL Library
        private const WFD.CausalitySource s_CausalitySource = WFD.CausalitySource.Library;

        //Lazy initialize the actual factory
        private static WFD.IAsyncCausalityTracerStatics s_TracerFactory;

        // The loggers that this Tracer knows about. 
        [Flags]
        private enum Loggers : byte {
            CausalityTracer = 1,
            ETW = 2
        }


        //We receive the actual value for these as a callback
        private static Loggers f_LoggingOn; //assumes false by default

        // The precise static constructor will run first time somebody attempts to access this class
        [SecuritySafeCritical]
        static AsyncCausalityTracer()
        {
            if (!Environment.IsWinRTSupported) return;

            //COM Class Id
            string ClassId = "Windows.Foundation.Diagnostics.AsyncCausalityTracer";

            //COM Interface GUID  {50850B26-267E-451B-A890-AB6A370245EE}
            Guid guid = new Guid(0x50850B26, 0x267E, 0x451B, 0xA8, 0x90, 0XAB, 0x6A, 0x37, 0x02, 0x45, 0xEE);

            Object factory = null;

            try
            {
                int hresult = Microsoft.Win32.UnsafeNativeMethods.RoGetActivationFactory(ClassId, ref guid, out factory);

                if (hresult < 0 || factory == null) return; //This prevents having an exception thrown in case IAsyncCausalityTracerStatics isn't registered.
                
                s_TracerFactory = (WFD.IAsyncCausalityTracerStatics)factory;

                EventRegistrationToken token = s_TracerFactory.add_TracingStatusChanged(new EventHandler<WFD.TracingStatusChangedEventArgs>(TracingStatusChangedHandler));
                Contract.Assert(token != default(EventRegistrationToken), "EventRegistrationToken is null");
            }
            catch (Exception ex)
            {
                // Although catching generic Exception is not recommended, this file is one exception
                // since we don't want to propagate any kind of exception to the user since all we are
                // doing here depends on internal state.
                LogAndDisable(ex);
            }
           
        }

        [SecuritySafeCritical]
        private static void TracingStatusChangedHandler(Object sender, WFD.TracingStatusChangedEventArgs args)
        {
            if (args.Enabled)
               f_LoggingOn |= Loggers.CausalityTracer;
            else 
               f_LoggingOn &= ~Loggers.CausalityTracer;
        }
#endif

        //
        // The TraceXXX methods should be called only if LoggingOn property returned true
        //

        [FriendAccessAllowed]
        [MethodImplAttribute(MethodImplOptions.NoInlining)] // Tracking is slow path. Disable inlining for it.
        internal static void TraceOperationCreation(CausalityTraceLevel traceLevel, int taskId, string operationName, ulong relatedContext)
        {
#if FEATURE_COMINTEROP
            try
            {
                if ((f_LoggingOn & Loggers.ETW) != 0)
                    TplEtwProvider.Log.TraceOperationBegin(taskId, operationName, (long) relatedContext);
                if ((f_LoggingOn & Loggers.CausalityTracer) != 0)
                    s_TracerFactory.TraceOperationCreation((WFD.CausalityTraceLevel)traceLevel, s_CausalitySource, s_PlatformId, GetOperationId((uint)taskId), operationName, relatedContext);
            }
            catch(Exception ex)
            {
                //view function comment
                LogAndDisable(ex);
            }
#endif
        }

        [FriendAccessAllowed]
        [MethodImplAttribute(MethodImplOptions.NoInlining)]
        internal static void TraceOperationCompletion(CausalityTraceLevel traceLevel, int taskId, AsyncCausalityStatus status)
        {
#if FEATURE_COMINTEROP
            try
            {
                if ((f_LoggingOn & Loggers.ETW) != 0)
                    TplEtwProvider.Log.TraceOperationEnd(taskId, status);
                if ((f_LoggingOn & Loggers.CausalityTracer) != 0)
                    s_TracerFactory.TraceOperationCompletion((WFD.CausalityTraceLevel)traceLevel, s_CausalitySource, s_PlatformId, GetOperationId((uint)taskId), (WFD.AsyncCausalityStatus)status);
            }
            catch(Exception ex)
            {
                //view function comment
                LogAndDisable(ex);
            }
#endif
        }

        [MethodImplAttribute(MethodImplOptions.NoInlining)]
        internal static void TraceOperationRelation(CausalityTraceLevel traceLevel, int taskId, CausalityRelation relation)
        {
#if FEATURE_COMINTEROP
            try
            {
                if ((f_LoggingOn & Loggers.ETW) != 0)
                    TplEtwProvider.Log.TraceOperationRelation(taskId, relation);
                if ((f_LoggingOn & Loggers.CausalityTracer) != 0)
                    s_TracerFactory.TraceOperationRelation((WFD.CausalityTraceLevel)traceLevel, s_CausalitySource, s_PlatformId, GetOperationId((uint)taskId), (WFD.CausalityRelation)relation);
            }
            catch(Exception ex)
            {
                //view function comment
                LogAndDisable(ex);
            }
#endif
        }

        [MethodImplAttribute(MethodImplOptions.NoInlining)]
        internal static void TraceSynchronousWorkStart(CausalityTraceLevel traceLevel, int taskId, CausalitySynchronousWork work)
        {
#if FEATURE_COMINTEROP
            try
            {
                if ((f_LoggingOn & Loggers.ETW) != 0)
                    TplEtwProvider.Log.TraceSynchronousWorkBegin(taskId, work);
                if ((f_LoggingOn & Loggers.CausalityTracer) != 0)
                    s_TracerFactory.TraceSynchronousWorkStart((WFD.CausalityTraceLevel)traceLevel, s_CausalitySource, s_PlatformId, GetOperationId((uint)taskId), (WFD.CausalitySynchronousWork)work);
            }
            catch(Exception ex)
            {
                //view function comment
                LogAndDisable(ex);
            }
#endif
        }

        [MethodImplAttribute(MethodImplOptions.NoInlining)]
        internal static void TraceSynchronousWorkCompletion(CausalityTraceLevel traceLevel, CausalitySynchronousWork work)
        {
#if FEATURE_COMINTEROP
            try
            {
                if ((f_LoggingOn & Loggers.ETW) != 0)
                    TplEtwProvider.Log.TraceSynchronousWorkEnd(work);
                if ((f_LoggingOn & Loggers.CausalityTracer) != 0)
                    s_TracerFactory.TraceSynchronousWorkCompletion((WFD.CausalityTraceLevel)traceLevel, s_CausalitySource, (WFD.CausalitySynchronousWork)work);
            }
            catch(Exception ex)
            {
                //view function comment
                LogAndDisable(ex);
            }
#endif
        }

#if FEATURE_COMINTEROP
        //fix for 796185: leaking internal exceptions to customers,
        //we should catch and log exceptions but never propagate them.
        private static void LogAndDisable(Exception ex)
        {
            f_LoggingOn = 0;
            Debugger.Log(0, "AsyncCausalityTracer", ex.ToString());
        }
#endif

        private static ulong GetOperationId(uint taskId)
        {
            return (((ulong)AppDomain.CurrentDomain.Id) << 32) + taskId;
        }

    }
}
