using System;
using System.Collections.Generic;
using System.Linq;
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>
    /// Helper class for loading SPI classes from classpath (META-INF files).
    /// This is a light impl of <c>java.util.ServiceLoader</c> but is guaranteed to
    /// be bug-free regarding classpath order and does not instantiate or initialize
    /// the classes found.
    /// <para/>
    /// @lucene.internal
    /// </summary>
    public class SPIClassIterator<S> : IEnumerable<Type>
    {
        private static readonly JCG.HashSet<Type> types = LoadTypes();

        private static JCG.HashSet<Type> LoadTypes() // LUCENENET: Avoid static constructors (see https://github.com/apache/lucenenet/pull/224#issuecomment-469284006)
        {
            var types = new JCG.HashSet<Type>();

            var assembliesToExamine = Support.AssemblyUtils.GetReferencedAssemblies();

            // LUCENENET NOTE: The following hack is not required because we are using abstract factories 
            // and pure DI to ensure the order of the codecs are always correct during testing.

            //// LUCENENET HACK:
            //// Tests such as TestImpersonation.cs expect that the assemblies
            //// are probed in a certain order. NamedSPILoader, lines 68 - 75 adds
            //// the first item it sees with that name. So if you have multiple
            //// codecs, it may not add the right one, depending on the order of
            //// the assemblies that were examined.
            //// This results in many test failures if Types from Lucene.Net.Codecs
            //// are examined and added to NamedSPILoader first before
            //// Lucene.Net.TestFramework.
            //var testFrameworkAssembly = assembliesToExamine.FirstOrDefault(x => string.Equals(x.GetName().Name, "Lucene.Net.TestFramework", StringComparison.Ordinal));
            //if (testFrameworkAssembly != null)
            //{
            //    //assembliesToExamine.Remove(testFrameworkAssembly);
            //    //assembliesToExamine.Insert(0, testFrameworkAssembly);
            //    assembliesToExamine = new Assembly[] { testFrameworkAssembly }.Concat(assembliesToExamine.Where(a => !testFrameworkAssembly.Equals(a)));
            //}

            foreach (var assembly in assembliesToExamine)
            {
                try
                {
                    foreach (var type in assembly.GetTypes().Where(x => x.IsPublic))
                    {
                        try
                        {
                            if (!IsInvokableSubclassOf<S>(type))
                            {
                                continue;
                            }

                            // We are looking for types with a default ctor
                            // (which is used in NamedSPILoader) or has a single parameter
                            // of type IDictionary<string, string> (for AnalysisSPILoader)
                            var matchingCtors = type.GetConstructors().Where(ctor =>
                            {
                                var parameters = ctor.GetParameters();

                                switch (parameters.Length)
                                {
                                    case 0: // default ctor
                                        return false; // LUCENENET NOTE: Now that we have factored Codecs into Abstract Factories, we don't need default constructors here
                                    case 1:
                                        return typeof(IDictionary<string, string>).IsAssignableFrom(parameters[0].ParameterType);
                                    default:
                                        return false;
                                }
                            });

                            if (matchingCtors.Any())
                            {
                                types.Add(type);
                            }
                        }
#pragma warning disable CA1031 // Do not catch general exception types
                        catch
                        {
                            // swallow
                        }
                    }
                }
                catch
                {
                    // swallow
                }
#pragma warning restore CA1031 // Do not catch general exception types
            }
            return types;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static bool IsInvokableSubclassOf<T>(Type type)
        {
            return typeof(T).IsAssignableFrom(type) && !type.IsAbstract && !type.IsInterface;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static SPIClassIterator<S> Get()
        {
            return new SPIClassIterator<S>();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public IEnumerator<Type> GetEnumerator()
        {
            return types.GetEnumerator();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }

    /* Being Re-written
    public sealed class SPIClassIterator<S> : IEnumerator<Type>
    {
      private const string META_INF_SERVICES = "META-INF/services/";

      private readonly Type Clazz;
      private readonly ClassLoader Loader;
      private readonly IEnumerator<URL> ProfilesEnum;
      private IEnumerator<string> LinesIterator;

      public static SPIClassIterator<S> Get<S>(Type clazz)
      {
        return new SPIClassIterator<S>(clazz, Thread.CurrentThread.ContextClassLoader);
      }

      public static SPIClassIterator<S> Get<S>(Type clazz, ClassLoader loader)
      {
        return new SPIClassIterator<S>(clazz, loader);
      }

      /// <summary>
      /// Utility method to check if some class loader is a (grand-)parent of or the same as another one.
      /// this means the child will be able to load all classes from the parent, too.
      /// </summary>
      public static bool IsParentClassLoader(ClassLoader parent, ClassLoader child)
      {
        while (child != null)
        {
          if (child == parent)
          {
            return true;
          }
          child = child.Parent;
        }
        return false;
      }

      private SPIClassIterator(Type clazz, ClassLoader loader)
      {
        this.Clazz = clazz;
        try
        {
          string fullName = META_INF_SERVICES + clazz.Name;
          this.ProfilesEnum = (loader == null) ? ClassLoader.getSystemResources(fullName) : loader.getResources(fullName);
        }
        catch (IOException ioe)
        {
          throw new ServiceConfigurationError("Error loading SPI profiles for type " + clazz.Name + " from classpath", ioe);
        }
        this.Loader = (loader == null) ? ClassLoader.SystemClassLoader : loader;
        this.LinesIterator = Collections.emptySet<string>().GetEnumerator();
      }

      private bool LoadNextProfile()
      {
        List<string> lines = null;
        while (ProfilesEnum.MoveNext())
        {
          if (lines != null)
          {
            lines.Clear();
          }
          else
          {
            lines = new List<string>();
          }
          URL url = ProfilesEnum.Current;
          try
          {
            InputStream @in = url.openStream();
            IOException priorE = null;
            try
            {
              BufferedReader reader = new BufferedReader(new InputStreamReader(@in, IOUtils.CHARSET_UTF_8));
              string line;
              while ((line = reader.readLine()) != null)
              {
                int pos = line.IndexOf('#');
                if (pos >= 0)
                {
                  line = line.Substring(0, pos);
                }
                line = line.Trim();
                if (line.Length > 0)
                {
                  lines.Add(line);
                }
              }
            }
            catch (IOException ioe)
            {
              priorE = ioe;
            }
            finally
            {
              IOUtils.CloseWhileHandlingException(priorE, @in);
            }
          }
          catch (IOException ioe)
          {
            throw new ServiceConfigurationError("Error loading SPI class list from URL: " + url, ioe);
          }
          if (lines.Count > 0)
          {
            this.LinesIterator = lines.GetEnumerator();
            return true;
          }
        }
        return false;
      }

      public override bool HasNext()
      {
      }

      public override Type Next()
      {
        // hasNext() implicitely loads the next profile, so it is essential to call this here!
        if (!HasNext())
        {
          throw new NoSuchElementException();
        }
        string c = LinesIterator.next();
        try
        {
          // don't initialize the class (pass false as 2nd parameter):
          return Type.GetType(c, false, Loader).asSubclass(Clazz);
        }
        catch (ClassNotFoundException cnfe)
        {
          throw new ServiceConfigurationError(string.format(Locale.ROOT, "A SPI class of type %s with classname %s does not exist, " + "please fix the file '%s%1$s' in your classpath.", Clazz.Name, c, META_INF_SERVICES));
        }
      }

      public override void Remove()
      {
        throw new NotSupportedException();
      }
    }*/
}