using Lucene.Net.Support;
using System;
using System.Collections.Generic;
using System.Globalization;
#if NETSTANDARD1_6
using System.Linq;
#endif
using System.Reflection;
using System.Runtime.CompilerServices;
using JCG = J2N.Collections.Generic;

namespace Lucene.Net.Util
{
    /*
     * Licensed to the Apache Software Foundation (ASF) under one or more
     * contributor license agreements.  See the NOTICE file distributed with
     * this work for additional information regarding copyright ownership.
     * The ASF licenses this file to You under the Apache License, Version 2.0
     * (the "License"); you may not use this file except in compliance with
     * the License.  You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    /// <summary>
    /// A utility for keeping backwards compatibility on previously abstract methods
    /// (or similar replacements).
    /// <para>Before the replacement method can be made abstract, the old method must kept deprecated.
    /// If somebody still overrides the deprecated method in a non-sealed class,
    /// you must keep track, of this and maybe delegate to the old method in the subclass.
    /// The cost of reflection is minimized by the following usage of this class:</para>
    /// <para>Define <strong>static readonly</strong> fields in the base class (<c>BaseClass</c>),
    /// where the old and new method are declared:</para>
    /// <code>
    /// internal static readonly VirtualMethod newMethod =
    ///     new VirtualMethod(typeof(BaseClass), "newName", parameters...);
    /// internal static readonly VirtualMethod oldMethod =
    ///     new VirtualMethod(typeof(BaseClass), "oldName", parameters...);
    /// </code>
    /// <para>this enforces the singleton status of these objects, as the maintenance of the cache would be too costly else.
    /// If you try to create a second instance of for the same method/<c>baseClass</c> combination, an exception is thrown.</para>
    /// <para>To detect if e.g. the old method was overridden by a more far subclass on the inheritance path to the current
    /// instance's class, use a <strong>non-static</strong> field:</para>
    /// <code>
    ///  bool isDeprecatedMethodOverridden =
    ///      oldMethod.GetImplementationDistance(this.GetType()) > newMethod.GetImplementationDistance(this.GetType());
    ///
    ///  <em>// alternatively (more readable):</em>
    ///  bool isDeprecatedMethodOverridden =
    ///      VirtualMethod.CompareImplementationDistance(this.GetType(), oldMethod, newMethod) > 0
    /// </code>
    /// <para><seealso cref="GetImplementationDistance"/> returns the distance of the subclass that overrides this method.
    /// The one with the larger distance should be used preferable.
    /// this way also more complicated method rename scenarios can be handled
    /// (think of 2.9 <see cref="Analysis.TokenStream"/> deprecations).</para>
    ///
    /// @lucene.internal
    /// </summary>
    // LUCENENET NOTE: Pointless to make this class generic, since the generic type is never used (the Type class in .NET
    // is not generic).
    public sealed class VirtualMethod
    {
        private static readonly ISet<MethodInfo> singletonSet = new ConcurrentHashSet<MethodInfo>();

        private readonly Type baseClass;
        private readonly string method;
        private readonly Type[] parameters;
        // LUCENENET: Replaced IdentityHashMap with ConditionalWeakTable. A Type IS an identity, so there is
        // no need for the extra IdentityWeakReference.
        private readonly ConditionalWeakTable<Type, Int32Ref> cache = new ConditionalWeakTable<Type, Int32Ref>();

        // LUCENENET specific wrapper needed because ConditionalWeakTable requires a reference type.
        private class Int32Ref : IEquatable<Int32Ref>
        {
            private int value;

            public Int32Ref(int value)
            {
                this.value = value;
            }

            public bool Equals(Int32Ref other)
            {
                if (other == null)
                    return false;
                return value.Equals(other);
            }

            public override bool Equals(object obj)
            {
                if (obj is Int32Ref other)
                    return Equals(other);
                if (obj is int otherInt)
                    return value.Equals(otherInt);
                return false;
            }

            public override int GetHashCode()
            {
                return value.GetHashCode();
            }

            public static implicit operator int(Int32Ref value)
            {
                return value.value;
            }

            public static implicit operator Int32Ref(int value)
            {
                return new Int32Ref(value);
            }
        }


        /// <summary>
        /// Creates a new instance for the given <paramref name="baseClass"/> and method declaration. </summary>
        /// <exception cref="InvalidOperationException"> if you create a second instance of the same
        /// <paramref name="baseClass"/> and method declaration combination. This enforces the singleton status. </exception>
        /// <exception cref="ArgumentException"> If <paramref name="baseClass"/> does not declare the given method. </exception>
        public VirtualMethod(Type baseClass, string method, params Type[] parameters)
        {
            this.baseClass = baseClass;
            this.method = method;
            this.parameters = parameters;
            try
            {
                MethodInfo mi = GetMethod(baseClass, method, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, parameters);
                if (mi == null)
                {
                    throw new ArgumentException(baseClass.Name + " has no such method.");
                }
                else if (!singletonSet.Add(mi))
                {
                    throw new NotSupportedException("VirtualMethod instances must be singletons and therefore " + "assigned to static final members in the same class, they use as baseClass ctor param.");
                }
            }
            catch (NotSupportedException nsme)
            {
                throw new ArgumentException(baseClass.Name + " has no such method: " + nsme.Message);
            }
        }

        /// <summary>
        /// Returns the distance from the <c>baseClass</c> in which this method is overridden/implemented
        /// in the inheritance path between <c>baseClass</c> and the given subclass <paramref name="subclazz"/>. </summary>
        /// <returns> 0 if and only if not overridden, else the distance to the base class. </returns>
        public int GetImplementationDistance(Type subclazz)
        {
            // LUCENENET: Replaced WeakIdentityMap with ConditionalWeakTable - This operation is simplified over Lucene.
            return cache.GetValue(subclazz, (key) => Convert.ToInt32(ReflectImplementationDistance(key), CultureInfo.InvariantCulture));
        }

        /// <summary>
        /// Returns, if this method is overridden/implemented in the inheritance path between
        /// <c>baseClass</c> and the given subclass <paramref name="subclazz"/>.
        /// <para/>You can use this method to detect if a method that should normally be final was overridden
        /// by the given instance's class. </summary>
        /// <returns> <c>false</c> if and only if not overridden. </returns>
        public bool IsOverriddenAsOf(Type subclazz)
        {
            return GetImplementationDistance(subclazz) > 0;
        }

        private int ReflectImplementationDistance(Type subclazz)
        {
            if (!baseClass.IsAssignableFrom(subclazz))
            {
                throw new ArgumentException(subclazz.Name + " is not a subclass of " + baseClass.Name);
            }
            bool overridden = false;
            int distance = 0;
            for (Type clazz = subclazz; clazz != baseClass && clazz != null; clazz = clazz.BaseType)
            {
                // lookup method, if success mark as overridden
                if (!overridden)
                {
                    MethodInfo mi =  GetMethod(clazz, method,
                        BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly,
                        parameters);

                    if (mi != null)
                        overridden = true;
                }

                // increment distance if overridden
                if (overridden)
                {
                    distance++;
                }
            }
            return distance;
        }

        /// <summary>
        /// Utility method that compares the implementation/override distance of two methods. </summary>
        /// <returns> 
        /// <list type="bullet">
        ///     <item><description>&gt; 1, iff <paramref name="m1"/> is overridden/implemented in a subclass of the class overriding/declaring <paramref name="m2"/></description></item>
        ///     <item><description>&lt; 1, iff <paramref name="m2"/> is overridden in a subclass of the class overriding/declaring <paramref name="m1"/></description></item>
        ///     <item><description>0, iff both methods are overridden in the same class (or are not overridden at all)</description></item>
        /// </list>
        /// </returns>
        public static int CompareImplementationDistance(Type clazz, VirtualMethod m1, VirtualMethod m2)
        {
            return m1.GetImplementationDistance(clazz).CompareTo(m2.GetImplementationDistance(clazz));
        }

        private MethodInfo GetMethod(Type clazz, string methodName, BindingFlags bindingFlags, Type[] methodParameters)
        {
#if NETSTANDARD1_6
            var methods = clazz.GetTypeInfo().GetMethods(bindingFlags).Where(x => {
                return x.Name.Equals(methodName, StringComparison.Ordinal)
                    && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(methodParameters);
                }).ToArray();

            if (methods.Length == 0)
            {
                return default(MethodInfo);
            }
            else if (methods.Length == 1)
            {
                return methods[0];
            }
            else
            {
                var formatted = string.Format("Found more than one match for type {0}, methodName {1}, bindingFlags {2}, parameters {3}", clazz, methodName, bindingFlags, methodParameters);
                throw new AmbiguousMatchException(formatted);
            }
#else
            return clazz.GetMethod(methodName, bindingFlags, null, methodParameters, null);
#endif
        }
    }
}