using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using IPA.Config;
using IPA.Logging;
using IPA.Utilities;
#if NET3
using Net3_Proxy;
#endif

namespace IPA.Loader
{
    /// <summary>
    /// The type that handles value injecting into a plugin's Init.
    /// </summary>
    public static class PluginInitInjector
    {

        /// <summary>
        /// A typed injector for a plugin's Init method. When registered, called for all associated types. If it returns null, the default for the type will be used.
        /// </summary>
        /// <param name="previous">the previous return value of the function, or <see langword="null"/> if never called for plugin.</param>
        /// <param name="param">the <see cref="ParameterInfo"/> of the parameter being injected.</param>
        /// <param name="meta">the <see cref="PluginLoader.PluginMetadata"/> for the plugin being loaded.</param>
        /// <returns>the value to inject into that parameter.</returns>
        public delegate object InjectParameter(object previous, ParameterInfo param, PluginLoader.PluginMetadata meta);

        /// <summary>
        /// Adds an injector to be used when calling future plugins' Init methods.
        /// </summary>
        /// <param name="type">the type of the parameter.</param>
        /// <param name="injector">the function to call for injection.</param>
        public static void AddInjector(Type type, InjectParameter injector)
        {
            injectors.Add(new Tuple<Type, InjectParameter>(type, injector));
        }

        private static readonly List<Tuple<Type, InjectParameter>> injectors = new List<Tuple<Type, InjectParameter>>
        {
            new Tuple<Type, InjectParameter>(typeof(Logger), (prev, param, meta) => prev ?? new StandardLogger(meta.Name)),
#pragma warning disable CS0618 // Type or member is obsolete
            new Tuple<Type, InjectParameter>(typeof(IModPrefs), (prev, param, meta) => prev ?? new ModPrefs(meta)),
#pragma warning restore CS0618 // Type or member is obsolete
            new Tuple<Type, InjectParameter>(typeof(PluginLoader.PluginMetadata), (prev, param, meta) => prev ?? meta),
            new Tuple<Type, InjectParameter>(typeof(IConfigProvider), (prev, param, meta) =>
            {
                if (prev != null) return prev;
                var cfgProvider = Config.Config.GetProviderFor(meta.Name, param);
                cfgProvider.Load();
                return cfgProvider;
            })
        };

        internal static void Inject(MethodInfo init, PluginLoader.PluginInfo info)
        {
            var instance = info.Plugin;
            var meta = info.Metadata;

            var initArgs = new List<object>();
            var initParams = init.GetParameters();

            Dictionary<Tuple<Type, InjectParameter>, object> previousValues =
                new Dictionary<Tuple<Type, InjectParameter>, object>(injectors.Count);

            foreach (var param in initParams)
            {
                var paramType = param.ParameterType;

                var value = paramType.GetDefault();
                foreach (var pair in injectors.Where(t => paramType.IsAssignableFrom(t.Item1)))
                {
                    object prev = null;
                    if (previousValues.ContainsKey(pair))
                        prev = previousValues[pair];

                    var val = pair.Item2?.Invoke(prev, param, meta);
                    
                    if (previousValues.ContainsKey(pair))
                        previousValues[pair] = val;
                    else
                        previousValues.Add(pair, val);

                    if (val == null) continue;
                    value = val;
                    break;
                }

                initArgs.Add(value);
            }

            init.Invoke(instance, initArgs.ToArray());
        }
    }
}
