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

using System.Collections;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.Versioning;
using System.Threading;

namespace System.ComponentModel
{
    /// <summary>
    /// Provides properties and methods to add a license
    /// to a component and to manage a <see cref='System.ComponentModel.LicenseProvider'/>. This class cannot be inherited.
    /// </summary>
    public sealed partial class LicenseManager
    {
        private static readonly object s_selfLock = new object();

        private static volatile LicenseContext? s_context;
        private static object? s_contextLockHolder;
        private static volatile Hashtable? s_providers;
        private static volatile Hashtable? s_providerInstances;
        private static readonly object s_internalSyncObject = new object();

        // not creatable...
        private LicenseManager()
        {
        }

        /// <summary>
        /// Gets or sets the current <see cref='System.ComponentModel.LicenseContext'/> which specifies when the licensed object can be
        /// used.
        /// </summary>
        public static LicenseContext CurrentContext
        {
            get
            {
                if (s_context == null)
                {
                    lock (s_internalSyncObject)
                    {
                        s_context ??= new RuntimeLicenseContext();
                    }
                }
                return s_context;
            }
            set
            {
                lock (s_internalSyncObject)
                {
                    if (s_contextLockHolder != null)
                    {
                        throw new InvalidOperationException(SR.LicMgrContextCannotBeChanged);
                    }
                    s_context = value;
                }
            }
        }


        /// <summary>
        /// Gets the <see cref='System.ComponentModel.LicenseUsageMode'/> that
        /// specifies when the licensed object can be used, for the <see cref='System.ComponentModel.LicenseManager.CurrentContext'/>.
        /// </summary>
        public static LicenseUsageMode UsageMode
        {
            get
            {
                if (s_context != null)
                {
                    return s_context.UsageMode;
                }
                return LicenseUsageMode.Runtime;
            }
        }


        /// <summary>
        /// Caches the provider, both in the instance cache, and the type
        /// cache.
        /// </summary>
        private static void CacheProvider(Type type, LicenseProvider? provider)
        {
            if (s_providers == null)
            {
                Interlocked.CompareExchange(ref s_providers, new Hashtable(), null);
            }

            lock (s_providers)
            {
                s_providers[type] = provider;
            }

            if (provider != null)
            {
                if (s_providerInstances == null)
                {
                    Interlocked.CompareExchange(ref s_providerInstances, new Hashtable(), null);
                }

                Type providerType = provider.GetType();
                lock (s_providerInstances)
                {
                    s_providerInstances[providerType] = provider;
                }
            }
        }


        /// <summary>
        /// Creates an instance of the specified type, using
        /// creationContext
        /// as the context in which the licensed instance can be used.
        /// </summary>
        [UnsupportedOSPlatform("browser")]
        public static object? CreateWithContext(
            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type,
            LicenseContext creationContext)
        {
            return CreateWithContext(type, creationContext, Array.Empty<object>());
        }

        /// <summary>
        /// Creates an instance of the specified type with the
        /// specified arguments, using creationContext as the context in which the licensed
        /// instance can be used.
        /// </summary>
        [UnsupportedOSPlatform("browser")]
        public static object? CreateWithContext(
            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type,
            LicenseContext creationContext,
            object[] args)
        {
            object? created = null;

            lock (s_internalSyncObject)
            {
                LicenseContext normal = CurrentContext;
                try
                {
                    CurrentContext = creationContext;
                    LockContext(s_selfLock);
                    try
                    {
                        created = Activator.CreateInstance(type, args);
                    }
                    catch (TargetInvocationException e)
                    {
                        throw e.InnerException!;
                    }
                }
                finally
                {
                    UnlockContext(s_selfLock);
                    CurrentContext = normal;
                }
            }

            return created;
        }


        /// <summary>
        /// Determines if type was actually cached to have _no_ provider,
        /// as opposed to not being cached.
        /// </summary>
        private static bool GetCachedNoLicenseProvider(Type type)
        {
            if (s_providers != null)
            {
                return s_providers.ContainsKey(type);
            }
            return false;
        }


        /// <summary>
        /// Retrieves a cached instance of the provider associated with the
        /// specified type.
        /// </summary>
        private static LicenseProvider? GetCachedProvider(Type type)
        {
            return (LicenseProvider?)s_providers?[type];
        }


        /// <summary>
        /// Retrieves a cached instance of the provider of the specified
        /// type.
        /// </summary>
        private static LicenseProvider? GetCachedProviderInstance(Type providerType)
        {
            Debug.Assert(providerType != null, "Type cannot ever be null");
            return (LicenseProvider?)s_providerInstances?[providerType];
        }

        /// <summary>
        /// Determines if the given type has a valid license or not.
        /// </summary>
        public static bool IsLicensed(Type type)
        {
            Debug.Assert(type != null, "IsValid Type cannot ever be null");
            bool value = ValidateInternal(type, null, false, out License? license);
            license?.Dispose();
            return value;
        }

        /// <summary>
        /// Determines if a valid license can be granted for the specified type.
        /// </summary>
        public static bool IsValid(Type type)
        {
            Debug.Assert(type != null, "IsValid Type cannot ever be null");
            bool value = ValidateInternal(type, null, false, out License? license);
            license?.Dispose();
            return value;
        }

        /// <summary>
        /// Determines if a valid license can be granted for the
        /// specified instance of the type. This method creates a valid <see cref='System.ComponentModel.License'/>.
        /// </summary>
        public static bool IsValid(Type type, object? instance, out License? license)
        {
            return ValidateInternal(type, instance, false, out license);
        }

        public static void LockContext(object contextUser)
        {
            lock (s_internalSyncObject)
            {
                if (s_contextLockHolder != null)
                {
                    throw new InvalidOperationException(SR.LicMgrAlreadyLocked);
                }
                s_contextLockHolder = contextUser;
            }
        }

        public static void UnlockContext(object contextUser)
        {
            lock (s_internalSyncObject)
            {
                if (s_contextLockHolder != contextUser)
                {
                    throw new ArgumentException(SR.LicMgrDifferentUser);
                }
                s_contextLockHolder = null;
            }
        }

        /// <summary>
        /// Internal validation helper.
        /// </summary>
        private static bool ValidateInternal(Type type, object? instance, bool allowExceptions, out License? license)
        {
            return ValidateInternalRecursive(CurrentContext,
                                             type,
                                             instance,
                                             allowExceptions,
                                             out license,
                                             out _);
        }


        /// <summary>
        /// Since we want to walk up the entire inheritance change, when not
        /// give an instance, we need another helper method to walk up
        /// the chain...
        /// </summary>
        private static bool ValidateInternalRecursive(LicenseContext context, Type type, object? instance, bool allowExceptions, out License? license, out string? licenseKey)
        {
            LicenseProvider? provider = GetCachedProvider(type);
            if (provider == null && !GetCachedNoLicenseProvider(type))
            {
                // NOTE : Must look directly at the class, we want no inheritance.
                LicenseProviderAttribute? attr = (LicenseProviderAttribute?)Attribute.GetCustomAttribute(type, typeof(LicenseProviderAttribute), false);

                if (attr != null)
                {
                    Type providerType = attr.LicenseProvider!;
                    provider = GetCachedProviderInstance(providerType) ?? (LicenseProvider)Activator.CreateInstance(providerType)!;
                }

                CacheProvider(type, provider);
            }

            license = null;
            bool isValid = true;

            licenseKey = null;
            if (provider != null)
            {
                license = provider.GetLicense(context, type, instance, allowExceptions);
                if (license == null)
                {
                    isValid = false;
                }
                else
                {
                    // For the case where a COM client is calling "RequestLicKey",
                    // we try to squirrel away the first found license key
                    licenseKey = license.LicenseKey;
                }
            }

            // When looking only at a type, we need to recurse up the inheritance
            // chain, however, we can't give out the license, since this may be
            // from more than one provider.
            if (isValid && instance == null)
            {
                Type? baseType = type.BaseType;
                if (baseType != typeof(object) && baseType != null)
                {
                    if (license != null)
                    {
                        license.Dispose();
#pragma warning disable IDE0059 // ValidateInternalRecursive does not null licence all the time (https://github.com/dotnet/roslyn/issues/42761)
                        license = null;
#pragma warning restore IDE0059
                    }
                    isValid = ValidateInternalRecursive(context, baseType, null, allowExceptions, out license, out _);
                    if (license != null)
                    {
                        license.Dispose();
                        license = null;
                    }
                }
            }

            return isValid;
        }


        /// <summary>
        /// Determines if a license can be granted for the specified type.
        /// </summary>
        public static void Validate(Type type)
        {
            if (!ValidateInternal(type, null, true, out License? lic))
            {
                throw new LicenseException(type);
            }

            lic?.Dispose();
        }


        /// <summary>
        /// Determines if a license can be granted for the instance of the specified type.
        /// </summary>
        public static License? Validate(Type type, object? instance)
        {
            if (!ValidateInternal(type, instance, true, out License? lic))
            {
                throw new LicenseException(type, instance);
            }

            return lic;
        }
    }
}
