using System;
using System.Collections.Generic;
using System.Reflection;

namespace Swan.DependencyInjection {
  /// <summary>
  /// Represents an abstract class for Object Factory.
  /// </summary>
  public abstract class ObjectFactoryBase {
    /// <summary>
    /// Whether to assume this factory successfully constructs its objects
    /// 
    /// Generally set to true for delegate style factories as CanResolve cannot delve
    /// into the delegates they contain.
    /// </summary>
    public virtual Boolean AssumeConstruction => false;

    /// <summary>
    /// The type the factory instantiates.
    /// </summary>
    public abstract Type CreatesType {
      get;
    }

    /// <summary>
    /// Constructor to use, if specified.
    /// </summary>
    public ConstructorInfo Constructor {
      get; private set;
    }

    /// <summary>
    /// Gets the singleton variant.
    /// </summary>
    /// <value>
    /// The singleton variant.
    /// </value>
    /// <exception cref="DependencyContainerRegistrationException">singleton.</exception>
    public virtual ObjectFactoryBase SingletonVariant => throw new DependencyContainerRegistrationException(this.GetType(), "singleton");

    /// <summary>
    /// Gets the multi instance variant.
    /// </summary>
    /// <value>
    /// The multi instance variant.
    /// </value>
    /// <exception cref="DependencyContainerRegistrationException">multi-instance.</exception>
    public virtual ObjectFactoryBase MultiInstanceVariant => throw new DependencyContainerRegistrationException(this.GetType(), "multi-instance");

    /// <summary>
    /// Gets the strong reference variant.
    /// </summary>
    /// <value>
    /// The strong reference variant.
    /// </value>
    /// <exception cref="DependencyContainerRegistrationException">strong reference.</exception>
    public virtual ObjectFactoryBase StrongReferenceVariant => throw new DependencyContainerRegistrationException(this.GetType(), "strong reference");

    /// <summary>
    /// Gets the weak reference variant.
    /// </summary>
    /// <value>
    /// The weak reference variant.
    /// </value>
    /// <exception cref="DependencyContainerRegistrationException">weak reference.</exception>
    public virtual ObjectFactoryBase WeakReferenceVariant => throw new DependencyContainerRegistrationException(this.GetType(), "weak reference");

    /// <summary>
    /// Create the type.
    /// </summary>
    /// <param name="requestedType">Type user requested to be resolved.</param>
    /// <param name="container">Container that requested the creation.</param>
    /// <param name="options">The options.</param>
    /// <returns> Instance of type. </returns>
    public abstract Object GetObject(Type requestedType, DependencyContainer container, DependencyContainerResolveOptions options);

    /// <summary>
    /// Gets the factory for child container.
    /// </summary>
    /// <param name="type">The type.</param>
    /// <param name="parent">The parent.</param>
    /// <param name="child">The child.</param>
    /// <returns></returns>
    public virtual ObjectFactoryBase GetFactoryForChildContainer(Type type, DependencyContainer parent, DependencyContainer child) => this;
  }

  /// <inheritdoc />
  /// <summary>
  /// IObjectFactory that creates new instances of types for each resolution.
  /// </summary>
  internal class MultiInstanceFactory : ObjectFactoryBase {
    private readonly Type _registerType;
    private readonly Type _registerImplementation;

    public MultiInstanceFactory(Type registerType, Type registerImplementation) {
      if(registerImplementation.IsAbstract || registerImplementation.IsInterface) {
        throw new DependencyContainerRegistrationException(registerImplementation, "MultiInstanceFactory", true);
      }

      if(!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) {
        throw new DependencyContainerRegistrationException(registerImplementation, "MultiInstanceFactory", true);
      }

      this._registerType = registerType;
      this._registerImplementation = registerImplementation;
    }

    public override Type CreatesType => this._registerImplementation;

    public override ObjectFactoryBase SingletonVariant =>
        new SingletonFactory(this._registerType, this._registerImplementation);

    public override ObjectFactoryBase MultiInstanceVariant => this;

    public override Object GetObject(Type requestedType, DependencyContainer container, DependencyContainerResolveOptions options) {
      try {
        return container.RegisteredTypes.ConstructType(this._registerImplementation, this.Constructor, options);
      } catch(DependencyContainerResolutionException ex) {
        throw new DependencyContainerResolutionException(this._registerType, ex);
      }
    }
  }

  /// <inheritdoc />
  /// <summary>
  /// IObjectFactory that invokes a specified delegate to construct the object.
  /// </summary>
  internal class DelegateFactory : ObjectFactoryBase {
    private readonly Type _registerType;

    private readonly Func<DependencyContainer, Dictionary<String, Object>, Object> _factory;

    public DelegateFactory(
        Type registerType,
        Func<DependencyContainer, Dictionary<String, Object>, Object> factory) {
      this._factory = factory ?? throw new ArgumentNullException(nameof(factory));

      this._registerType = registerType;
    }

    public override Boolean AssumeConstruction => true;

    public override Type CreatesType => this._registerType;

    public override ObjectFactoryBase WeakReferenceVariant => new WeakDelegateFactory(this._registerType, this._factory);

    public override ObjectFactoryBase StrongReferenceVariant => this;

    public override Object GetObject(Type requestedType, DependencyContainer container, DependencyContainerResolveOptions options) {
      try {
        return this._factory.Invoke(container, options.ConstructorParameters);
      } catch(Exception ex) {
        throw new DependencyContainerResolutionException(this._registerType, ex);
      }
    }
  }

  /// <inheritdoc />
  /// <summary>
  /// IObjectFactory that invokes a specified delegate to construct the object
  /// Holds the delegate using a weak reference.
  /// </summary>
  internal class WeakDelegateFactory : ObjectFactoryBase {
    private readonly Type _registerType;

    private readonly WeakReference _factory;

    public WeakDelegateFactory(Type registerType, Func<DependencyContainer, Dictionary<String, Object>, Object> factory) {
      if(factory == null) {
        throw new ArgumentNullException(nameof(factory));
      }

      this._factory = new WeakReference(factory);

      this._registerType = registerType;
    }

    public override Boolean AssumeConstruction => true;

    public override Type CreatesType => this._registerType;

    public override ObjectFactoryBase StrongReferenceVariant {
      get {
        if(!(this._factory.Target is Func<DependencyContainer, Dictionary<global::System.String, global::System.Object>, global::System.Object> factory)) {
          throw new DependencyContainerWeakReferenceException(this._registerType);
        }

        return new DelegateFactory(this._registerType, factory);
      }
    }

    public override ObjectFactoryBase WeakReferenceVariant => this;

    public override Object GetObject(Type requestedType, DependencyContainer container, DependencyContainerResolveOptions options) {
      if(!(this._factory.Target is Func<DependencyContainer, Dictionary<global::System.String, global::System.Object>, global::System.Object> factory)) {
        throw new DependencyContainerWeakReferenceException(this._registerType);
      }

      try {
        return factory.Invoke(container, options.ConstructorParameters);
      } catch(Exception ex) {
        throw new DependencyContainerResolutionException(this._registerType, ex);
      }
    }
  }

  /// <summary>
  /// Stores an particular instance to return for a type.
  /// </summary>
  internal class InstanceFactory : ObjectFactoryBase, IDisposable {
    private readonly Type _registerType;
    private readonly Type _registerImplementation;
    private readonly Object _instance;

    public InstanceFactory(Type registerType, Type registerImplementation, Object instance) {
      if(!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) {
        throw new DependencyContainerRegistrationException(registerImplementation, "InstanceFactory", true);
      }

      this._registerType = registerType;
      this._registerImplementation = registerImplementation;
      this._instance = instance;
    }

    public override Boolean AssumeConstruction => true;

    public override Type CreatesType => this._registerImplementation;

    public override ObjectFactoryBase MultiInstanceVariant => new MultiInstanceFactory(this._registerType, this._registerImplementation);

    public override ObjectFactoryBase WeakReferenceVariant => new WeakInstanceFactory(this._registerType, this._registerImplementation, this._instance);

    public override ObjectFactoryBase StrongReferenceVariant => this;

    public override Object GetObject(Type requestedType, DependencyContainer container, DependencyContainerResolveOptions options) => this._instance;

    public void Dispose() {
      IDisposable disposable = this._instance as IDisposable;

      disposable?.Dispose();
    }
  }

  /// <summary>
  /// Stores the instance with a weak reference.
  /// </summary>
  internal class WeakInstanceFactory : ObjectFactoryBase, IDisposable {
    private readonly Type _registerType;
    private readonly Type _registerImplementation;
    private readonly WeakReference _instance;

    public WeakInstanceFactory(Type registerType, Type registerImplementation, Object instance) {
      if(!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) {
        throw new DependencyContainerRegistrationException(registerImplementation, "WeakInstanceFactory", true);
      }

      this._registerType = registerType;
      this._registerImplementation = registerImplementation;
      this._instance = new WeakReference(instance);
    }

    public override Type CreatesType => this._registerImplementation;

    public override ObjectFactoryBase MultiInstanceVariant => new MultiInstanceFactory(this._registerType, this._registerImplementation);

    public override ObjectFactoryBase WeakReferenceVariant => this;

    public override ObjectFactoryBase StrongReferenceVariant {
      get {
        Object instance = this._instance.Target;

        if(instance == null) {
          throw new DependencyContainerWeakReferenceException(this._registerType);
        }

        return new InstanceFactory(this._registerType, this._registerImplementation, instance);
      }
    }

    public override Object GetObject(Type requestedType, DependencyContainer container, DependencyContainerResolveOptions options) {
      Object instance = this._instance.Target;

      if(instance == null) {
        throw new DependencyContainerWeakReferenceException(this._registerType);
      }

      return instance;
    }

    public void Dispose() => (this._instance.Target as IDisposable)?.Dispose();
  }

  /// <summary>
  /// A factory that lazy instantiates a type and always returns the same instance.
  /// </summary>
  internal class SingletonFactory : ObjectFactoryBase, IDisposable {
    private readonly Type _registerType;
    private readonly Type _registerImplementation;
    private readonly Object _singletonLock = new Object();
    private Object _current;

    public SingletonFactory(Type registerType, Type registerImplementation) {
      if(registerImplementation.IsAbstract || registerImplementation.IsInterface) {
        throw new DependencyContainerRegistrationException(registerImplementation, nameof(SingletonFactory), true);
      }

      if(!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) {
        throw new DependencyContainerRegistrationException(registerImplementation, nameof(SingletonFactory), true);
      }

      this._registerType = registerType;
      this._registerImplementation = registerImplementation;
    }

    public override Type CreatesType => this._registerImplementation;

    public override ObjectFactoryBase SingletonVariant => this;

    public override ObjectFactoryBase MultiInstanceVariant =>
        new MultiInstanceFactory(this._registerType, this._registerImplementation);

    public override Object GetObject(
        Type requestedType,
        DependencyContainer container,
        DependencyContainerResolveOptions options) {
      if(options.ConstructorParameters.Count != 0) {
        throw new ArgumentException("Cannot specify parameters for singleton types");
      }

      lock(this._singletonLock) {
        if(this._current == null) {
          this._current = container.RegisteredTypes.ConstructType(this._registerImplementation, this.Constructor, options);
        }
      }

      return this._current;
    }

    public override ObjectFactoryBase GetFactoryForChildContainer(
        Type type,
        DependencyContainer parent,
        DependencyContainer child) {
      // We make sure that the singleton is constructed before the child container takes the factory.
      // Otherwise the results would vary depending on whether or not the parent container had resolved
      // the type before the child container does.
      _ = this.GetObject(type, parent, DependencyContainerResolveOptions.Default);
      return this;
    }

    public void Dispose() => (this._current as IDisposable)?.Dispose();
  }
}