// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections;
using System.Data.Common;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Principal;

namespace System.Data.ProviderBase
{
    internal abstract class DbConnectionPoolCounters
    {
        private static class CreationData
        {
            internal static readonly CounterCreationData HardConnectsPerSecond = new CounterCreationData(
                                                                        "HardConnectsPerSecond",
                                                                        "The number of actual connections per second that are being made to servers",
                                                                        PerformanceCounterType.RateOfCountsPerSecond32);

            internal static readonly CounterCreationData HardDisconnectsPerSecond = new CounterCreationData(
                                                                        "HardDisconnectsPerSecond",
                                                                        "The number of actual disconnects per second that are being made to servers",
                                                                        PerformanceCounterType.RateOfCountsPerSecond32);

            internal static readonly CounterCreationData SoftConnectsPerSecond = new CounterCreationData(
                                                                        "SoftConnectsPerSecond",
                                                                        "The number of connections we get from the pool per second",
                                                                        PerformanceCounterType.RateOfCountsPerSecond32);

            internal static readonly CounterCreationData SoftDisconnectsPerSecond = new CounterCreationData(
                                                                        "SoftDisconnectsPerSecond",
                                                                        "The number of connections we return to the pool per second",
                                                                        PerformanceCounterType.RateOfCountsPerSecond32);

            internal static readonly CounterCreationData NumberOfNonPooledConnections = new CounterCreationData(
                                                                        "NumberOfNonPooledConnections",
                                                                        "The number of connections that are not using connection pooling",
                                                                        PerformanceCounterType.NumberOfItems32);

            internal static readonly CounterCreationData NumberOfPooledConnections = new CounterCreationData(
                                                                        "NumberOfPooledConnections",
                                                                        "The number of connections that are managed by the connection pooler",
                                                                        PerformanceCounterType.NumberOfItems32);

            internal static readonly CounterCreationData NumberOfActiveConnectionPoolGroups = new CounterCreationData(
                                                                        "NumberOfActiveConnectionPoolGroups",
                                                                        "The number of unique connection strings",
                                                                        PerformanceCounterType.NumberOfItems32);

            internal static readonly CounterCreationData NumberOfInactiveConnectionPoolGroups = new CounterCreationData(
                                                                        "NumberOfInactiveConnectionPoolGroups",
                                                                        "The number of unique connection strings waiting for pruning",
                                                                        PerformanceCounterType.NumberOfItems32);

            internal static readonly CounterCreationData NumberOfActiveConnectionPools = new CounterCreationData(
                                                                        "NumberOfActiveConnectionPools",
                                                                        "The number of connection pools",
                                                                        PerformanceCounterType.NumberOfItems32);

            internal static readonly CounterCreationData NumberOfInactiveConnectionPools = new CounterCreationData(
                                                                        "NumberOfInactiveConnectionPools",
                                                                        "The number of connection pools",
                                                                        PerformanceCounterType.NumberOfItems32);

            internal static readonly CounterCreationData NumberOfActiveConnections = new CounterCreationData(
                                                                        "NumberOfActiveConnections",
                                                                        "The number of connections currently in-use",
                                                                        PerformanceCounterType.NumberOfItems32);

            internal static readonly CounterCreationData NumberOfFreeConnections = new CounterCreationData(
                                                                        "NumberOfFreeConnections",
                                                                        "The number of connections currently available for use",
                                                                        PerformanceCounterType.NumberOfItems32);

            internal static readonly CounterCreationData NumberOfStasisConnections = new CounterCreationData(
                                                                        "NumberOfStasisConnections",
                                                                        "The number of connections currently waiting to be made ready for use",
                                                                        PerformanceCounterType.NumberOfItems32);

            internal static readonly CounterCreationData NumberOfReclaimedConnections = new CounterCreationData(
                                                                        "NumberOfReclaimedConnections",
                                                                        "The number of connections we reclaim from GC'd external connections",
                                                                        PerformanceCounterType.NumberOfItems32);
        };

        internal sealed class Counter
        {
            private PerformanceCounter? _instance;

            internal Counter(string? categoryName, string? instanceName, string counterName)
            {
                if (ADP.IsPlatformNT5)
                {
                    try
                    {
                        if (!ADP.IsEmpty(categoryName) && !ADP.IsEmpty(instanceName))
                        {
                            PerformanceCounter instance = new PerformanceCounter();
                            instance.CategoryName = categoryName;
                            instance.CounterName = counterName;
                            instance.InstanceName = instanceName;
                            instance.InstanceLifetime = PerformanceCounterInstanceLifetime.Process;
                            instance.ReadOnly = false;
                            instance.RawValue = 0;  // make sure we start out at zero
                            _instance = instance;
                        }
                    }
                    catch (InvalidOperationException e)
                    {
                        ADP.TraceExceptionWithoutRethrow(e);
                        // TODO: generate Application EventLog entry about inability to find perf counter
                    }
                }
            }

            internal void Decrement()
            {
                PerformanceCounter? instance = _instance;
                instance?.Decrement();
            }

            internal void Dispose()
            { // TODO: race condition, Dispose at the same time as Increment/Decrement
                PerformanceCounter? instance = _instance;
                _instance = null;
                instance?.RemoveInstance();
            }

            internal void Increment()
            {
                _instance?.Increment();
            }
        };

        private const int CounterInstanceNameMaxLength = 127;

        internal readonly Counter HardConnectsPerSecond;
        internal readonly Counter HardDisconnectsPerSecond;
        internal readonly Counter SoftConnectsPerSecond;
        internal readonly Counter SoftDisconnectsPerSecond;
        internal readonly Counter NumberOfNonPooledConnections;
        internal readonly Counter NumberOfPooledConnections;
        internal readonly Counter NumberOfActiveConnectionPoolGroups;
        internal readonly Counter NumberOfInactiveConnectionPoolGroups;
        internal readonly Counter NumberOfActiveConnectionPools;
        internal readonly Counter NumberOfInactiveConnectionPools;
        internal readonly Counter NumberOfActiveConnections;
        internal readonly Counter NumberOfFreeConnections;
        internal readonly Counter NumberOfStasisConnections;
        internal readonly Counter NumberOfReclaimedConnections;

        protected DbConnectionPoolCounters() : this(null)
        {
        }

        protected DbConnectionPoolCounters(string? categoryName)
        {
            AppDomain.CurrentDomain.DomainUnload += new EventHandler(this.UnloadEventHandler);
            AppDomain.CurrentDomain.ProcessExit += new EventHandler(this.ExitEventHandler);
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(this.ExceptionEventHandler);

            string? instanceName = null;

            if (!ADP.IsEmpty(categoryName))
            {
                if (ADP.IsPlatformNT5)
                {
                    instanceName = GetInstanceName();
                }
            }

            // level 0-3: hard connects/disconnects, plus basic pool/pool entry statistics
            string? basicCategoryName = categoryName;
            HardConnectsPerSecond = new Counter(basicCategoryName, instanceName, CreationData.HardConnectsPerSecond.CounterName);
            HardDisconnectsPerSecond = new Counter(basicCategoryName, instanceName, CreationData.HardDisconnectsPerSecond.CounterName);
            NumberOfNonPooledConnections = new Counter(basicCategoryName, instanceName, CreationData.NumberOfNonPooledConnections.CounterName);
            NumberOfPooledConnections = new Counter(basicCategoryName, instanceName, CreationData.NumberOfPooledConnections.CounterName);
            NumberOfActiveConnectionPoolGroups = new Counter(basicCategoryName, instanceName, CreationData.NumberOfActiveConnectionPoolGroups.CounterName);
            NumberOfInactiveConnectionPoolGroups = new Counter(basicCategoryName, instanceName, CreationData.NumberOfInactiveConnectionPoolGroups.CounterName);
            NumberOfActiveConnectionPools = new Counter(basicCategoryName, instanceName, CreationData.NumberOfActiveConnectionPools.CounterName);
            NumberOfInactiveConnectionPools = new Counter(basicCategoryName, instanceName, CreationData.NumberOfInactiveConnectionPools.CounterName);
            NumberOfStasisConnections = new Counter(basicCategoryName, instanceName, CreationData.NumberOfStasisConnections.CounterName);
            NumberOfReclaimedConnections = new Counter(basicCategoryName, instanceName, CreationData.NumberOfReclaimedConnections.CounterName);

            // level 4: expensive stuff
            string? verboseCategoryName = null;
            if (!ADP.IsEmpty(categoryName))
            {
                // don't load TraceSwitch if no categoryName so that Odbc/OleDb have a chance of not loading TraceSwitch
                // which are also used by System.Diagnostics.PerformanceCounter.ctor & System.Transactions.get_Current
                TraceSwitch perfCtrSwitch = new TraceSwitch("ConnectionPoolPerformanceCounterDetail", "level of detail to track with connection pool performance counters");
                if (TraceLevel.Verbose == perfCtrSwitch.Level)
                {
                    verboseCategoryName = categoryName;
                }
            }
            SoftConnectsPerSecond = new Counter(verboseCategoryName, instanceName, CreationData.SoftConnectsPerSecond.CounterName);
            SoftDisconnectsPerSecond = new Counter(verboseCategoryName, instanceName, CreationData.SoftDisconnectsPerSecond.CounterName);
            NumberOfActiveConnections = new Counter(verboseCategoryName, instanceName, CreationData.NumberOfActiveConnections.CounterName);
            NumberOfFreeConnections = new Counter(verboseCategoryName, instanceName, CreationData.NumberOfFreeConnections.CounterName);
        }
        private static string? GetAssemblyName()
        {
            string? result = null;

            // First try GetEntryAssembly name, then AppDomain.FriendlyName.
            Assembly? assembly = Assembly.GetEntryAssembly();

            if (null != assembly)
            {
                AssemblyName name = assembly.GetName();
                if (name != null)
                {
                    result = name.Name;
                }
            }
            return result;
        }

        // SxS: this method uses GetCurrentProcessId to construct the instance name.
        private static string GetInstanceName()
        {
            string? instanceName = GetAssemblyName(); // instance perfcounter name

            if (ADP.IsEmpty(instanceName))
            {
                AppDomain appDomain = AppDomain.CurrentDomain;
                if (null != appDomain)
                {
                    instanceName = appDomain.FriendlyName;
                }
            }

            uint pid = Interop.Kernel32.GetCurrentProcessId();

            // there are several characters which have special meaning
            // to PERFMON.  They recommend that we translate them as shown below, to
            // prevent problems.

            string result = $"{instanceName}[{pid}]";
            result = result.Replace('(', '[').Replace(')', ']').Replace('#', '_').Replace('/', '_').Replace('\\', '_');

            // counter instance name cannot be greater than 127
            if (result.Length > CounterInstanceNameMaxLength)
            {
                // Replacing the middle part with "[...]"
                // For example: if path is c:\long_path\very_(Ax200)_long__path\perftest.exe and process ID is 1234 than the resulted instance name will be:
                // c:\long_path\very_(AxM)[...](AxN)_long__path\perftest.exe[1234]
                // while M and N are adjusted to make each part before and after the [...] = 61 (making the total = 61 + 5 + 61 = 127)
                const string insertString = "[...]";
                int firstPartLength = (CounterInstanceNameMaxLength - insertString.Length) / 2;
                int lastPartLength = CounterInstanceNameMaxLength - firstPartLength - insertString.Length;
                result = $"{result.Substring(0, firstPartLength)}{insertString}{result.Substring(result.Length - lastPartLength, lastPartLength)}";

                Debug.Assert(result.Length == CounterInstanceNameMaxLength,
                    $"wrong calculation of the instance name: expected {CounterInstanceNameMaxLength}, actual: {result.Length}");
            }

            return result;
        }

        public void Dispose()
        {
            // ExceptionEventHandler with IsTerminiating may be called before
            // the Connection Close is called or the variables are initialized
            SafeDispose(HardConnectsPerSecond);
            SafeDispose(HardDisconnectsPerSecond);
            SafeDispose(SoftConnectsPerSecond);
            SafeDispose(SoftDisconnectsPerSecond);
            SafeDispose(NumberOfNonPooledConnections);
            SafeDispose(NumberOfPooledConnections);
            SafeDispose(NumberOfActiveConnectionPoolGroups);
            SafeDispose(NumberOfInactiveConnectionPoolGroups);
            SafeDispose(NumberOfActiveConnectionPools);
            SafeDispose(NumberOfActiveConnections);
            SafeDispose(NumberOfFreeConnections);
            SafeDispose(NumberOfStasisConnections);
            SafeDispose(NumberOfReclaimedConnections);
        }

        private static void SafeDispose(Counter counter)
        {
            counter?.Dispose();
        }

        private void ExceptionEventHandler(object sender, UnhandledExceptionEventArgs e)
        {
            if ((null != e) && e.IsTerminating)
            {
                Dispose();
            }
        }

        private void ExitEventHandler(object? sender, EventArgs e)
        {
            Dispose();
        }

        private void UnloadEventHandler(object? sender, EventArgs e)
        {
            Dispose();
        }
    }

    internal sealed class DbConnectionPoolCountersNoCounters : DbConnectionPoolCounters
    {
        public static readonly DbConnectionPoolCountersNoCounters SingletonInstance = new DbConnectionPoolCountersNoCounters();

        private DbConnectionPoolCountersNoCounters() : base()
        {
        }
    }
}
