// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Diagnostics.Tracing.Session;

namespace Microsoft.PowerToys.Telemetry
{
    /// <summary>
    ///  This class is based loosely on the C++ ETWTrace class in Win32client/Framework project.
    ///  It is intended to record telemetry events generated by the PowerToys processes so that end users
    ///  can view them if they want.
    /// </summary>
    public class ETWTrace : IDisposable
    {
        internal const EventKeywords TelemetryKeyword = (EventKeywords)0x0000200000000000;
        internal const EventKeywords MeasuresKeyword = (EventKeywords)0x0000400000000000;
        internal const EventKeywords CriticalDataKeyword = (EventKeywords)0x0000800000000000;

        private readonly bool telemetryEnabled = DataDiagnosticsSettings.GetEnabledValue(); // This is the global telemetry setting on whether to log events
        private readonly bool telemetryRecordingEnabled = DataDiagnosticsSettings.GetViewEnabledValue(); // This is the setting for recording telemetry events to disk for viewing
        private string etwFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Microsoft\PowerToys\", "etw");
        private bool disposedValue;
        private string sessionName;
        private string etwFilePath;
        private bool started;
#nullable enable
        private TraceEventSession? traceSession;

        internal sealed class Lister : EventListener
        {
            public Lister()
                : base()
            {
            }
        }

        private Lister? listener;
#nullable disable

        /// <summary>
        /// Initializes a new instance of the <see cref="ETWTrace"/> class.
        /// </summary>
        public ETWTrace()
        {
            Init();
        }

        public ETWTrace(string etwPath)
        {
            this.etwFolderPath = etwPath;

            Init();
        }

        private void Init()
        {
            if (File.Exists(etwFolderPath))
            {
                File.Delete(etwFolderPath);
            }

            if (!Directory.Exists(etwFolderPath))
            {
                Directory.CreateDirectory(etwFolderPath);
            }

            if (this.telemetryEnabled && this.telemetryRecordingEnabled)
            {
                this.Start();
            }

            listener = new Lister();
            listener.EnableEvents(PowerToysTelemetry.Log, EventLevel.LogAlways);
        }

        /// <inheritdoc/>
        public void Dispose()
        {
            // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
            this.Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Starts the trace session.
        /// </summary>
        public void Start()
        {
            lock (this)
            {
                if (this.started)
                {
                    return;
                }

                new Task(() =>
                {
                    while (true)
                    {
                        Thread.Sleep(30 * 1000);

                        this.traceSession.Flush();
                    }
                }).Start();

                string executable = Process.GetCurrentProcess().ProcessName;
                string dateTimeNow = DateTime.Now.ToString("MM-d-yyyy__H_mm_ss", CultureInfo.InvariantCulture);
                this.sessionName = string.Format(CultureInfo.InvariantCulture, "{0}-{1}-{2}", executable, Environment.ProcessId, dateTimeNow);
                this.etwFilePath = Path.Combine(etwFolderPath, $"{this.sessionName}.etl");

                this.traceSession = new TraceEventSession(
                    this.sessionName, this.etwFilePath, (TraceEventSessionOptions)(TraceEventSessionOptions.Create | TraceEventSessionOptions.PrivateLogger | TraceEventSessionOptions.PrivateInProcLogger));
                TraceEventProviderOptions args = new TraceEventProviderOptions();

                this.traceSession.EnableProvider(
                    PowerToysTelemetry.Log.Guid,
                    matchAnyKeywords: (ulong)TelemetryKeyword | (ulong)MeasuresKeyword | (ulong)CriticalDataKeyword);

                this.started = true;
            }
        }

        /// <summary>
        /// Stops the trace session.
        /// </summary>
        public void Stop()
        {
            lock (this)
            {
                if (!this.started)
                {
                    return;
                }

                if (this.traceSession != null)
                {
                    Trace.TraceInformation("Disposing EventTraceSession");
                    this.traceSession.Dispose();
                    this.traceSession = null;
                    this.started = false;
                }
            }
        }

        /// <summary>
        /// Disposes the object.
        /// </summary>
        /// <param name="disposing">boolean for disposing.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposedValue)
            {
                if (disposing)
                {
                    this.Stop();
                }

                this.disposedValue = true;
            }
        }
    }
}
