IOC Container

/// <summary>
	/// Shared instance of a type factory that allows accessing exposed extensibility points
	/// </summary>
	/// <remarks>
	/// This is different to the Type Factory which is its bigger brother as it's a simpler implementation and intended 
	/// for exposure as the base layer of the system below any dependent IOC.
	/// 
	/// While this is technically IOC the intention is that it'll only be used for compiled implementation, unlike the main type factory
	/// which is intended for runtime configuration too.
	/// 
	/// This type also needs to be easily used from this assembly, and that reduces dependencies as it knows nothing about
	/// the types it's serving up.
	/// 
	/// Note the lack of garbage collection support due to the internal static storage; singletons created here are likely to survive 
	/// indefinitey unless manually unloaded
	/// </remarks>
	public sealed class SharedServiceLocator : IDependencyContainer
	{
		private SharedServiceLocator() //block creation manually
		{
		}

		private SharedServiceLocator(SharedServiceLocator ParentContainer) //block creation manually
		{
			this.ParentContainer = ParentContainer;
		}

		private SharedServiceLocator ParentContainer { get; set; }

		private static object lockObject = new object();
		private static SharedServiceLocator _current = null;

		/// <summary>
		/// Get an instance of the current Service Locator
		/// </summary>
		public static SharedServiceLocator Current
		{
			get
			{
				lock (lockObject)
				{
					if (_current == null)
					{
						_current = new SharedServiceLocator();
					}
					return _current;
				}
			}
		}

		/// <summary>
		/// Get a container instance
		/// </summary>
		/// <returns></returns>
		public static IDependencyContainer GetContainer()
		{
			return Current;
		}

		/// <summary>
		/// Create a new instance of the shared service locator which does not share other instance properties
		/// </summary>
		/// <returns>An isolated instance</returns>
		/// <remarks>Useful for unit testing or when two parts of a system must have different configuration</remarks>
		public static IFRMDependencyContainer GetIsolatedContainer()
		{
			return new SharedServiceLocator();
		}

		/// <summary>
		/// Create a new instance of the shared service locator which retrieves and stores items itself but does not update the parent container
		/// </summary>
		/// <returns>A child instance of the IOC container</returns>
		/// <remarks>Useful for storing state instances</remarks>
		public IFRMDependencyContainer CreateChildContainer()
		{
			return new SharedServiceLocator(this);
		}

		/// <summary>
		/// The types to just create, their constructors will be called each time they are retrieved
		/// </summary>
		private List<Type> typesToCreate = new List<Type>();
		/// <summary>
		/// The types to create that are sub-typed, despite the name this will handle inheritance too but it is intended to allow
		/// requesting an interface type and getting back a concrete instance that this class located
		/// </summary>
		private Dictionary<Type, Type> interfaceTypesToCreate = new Dictionary<Type, Type>();
		/// <summary>
		/// Singleton objects that we return; note that this lacks support for IDisposable so they are kept alive forever as a static instance
		/// </summary>
		/// <remarks>It was possible to easily implement disposable support, but then we risk it being used accidentally and it's not 
		/// currently needed/being used</remarks>
		private Dictionary<Type, object> singletonTypes = new Dictionary<Type, object>();
		/// <summary>
		/// Types created on demand
		/// </summary>
		private Dictionary<Type, Type> lazySingletonTypes = new Dictionary<Type, Type>();
		/// <summary>
		/// Parameters to supply to a constructor if they are found (based on their type)
		/// </summary>
		private Dictionary<Type, List<Type>> typeParameters = new Dictionary<Type, List<Type>>();
		/// <summary>
		/// Parameters to supply to a constructor (based on the parameter name)
		/// </summary>
		private Dictionary<Type, Dictionary<string, object>> valueParameters = new Dictionary<Type, Dictionary<string, object>>();
		private Dictionary<Type, Func<object>> lazyFunctions = new Dictionary<Type, Func<object>>();

		/// <summary>
		/// Returns a list of all registered types and their implementers
		/// </summary>
		/// <returns></returns>
		public RegisteredType[] GetAllRegisteredTypes()
		{
			List<RegisteredType> typesToReturn = new List<RegisteredType>();
			var sessionBased = false;
			if (ParentContainer != null)
			{
				sessionBased = true;
			}

			typesToReturn.AddRange(from a in typesToCreate
								   select new RegisteredType() { SessionBased = sessionBased, Singleton = false, IndexedType = a, InvokedType=a, InvokedTypeName=a.Name });
			typesToReturn.AddRange(from a in interfaceTypesToCreate
								   select new RegisteredType() { SessionBased = sessionBased, Singleton = false, IndexedType = a.Key, InvokedType = a.Value, InvokedTypeName = a.Value.Name });
			foreach (var a in singletonTypes)
			{
				typesToReturn.Add(new RegisteredType() { SessionBased = sessionBased, Singleton = true, IndexedType = a.Key, InvokedType = a.Value.GetType(), InvokedTypeName = a.Value.ToString() });
			}
			foreach (var a in lazySingletonTypes)
			{
				typesToReturn.Add(new RegisteredType() { SessionBased = sessionBased, Singleton = true, Lazy = true, IndexedType = a.Key, InvokedType = a.Value.GetType(), InvokedTypeName = a.Value.ToString() });
			}
			foreach (var a in lazyFunctions)
			{
				typesToReturn.Add(new RegisteredType() { SessionBased = sessionBased, Singleton = true, Lazy=true, IndexedType = a.Key, InvokedType = a.Value.GetType(), InvokedTypeName = a.Value.ToString() });
			}

			if (ParentContainer != null)
			{
				typesToReturn.AddRange(ParentContainer.GetAllRegisteredTypes());
			}
			return typesToReturn.ToArray();
		}

		/// <summary>
		/// Retrieve a type, based on how it was registered (either as a singleton or automatically create more instances)
		/// </summary>
		/// <typeparam name="T">The type to create</typeparam>
		/// <remarks>Note that this will throw an exception, use the overload to disable this if you are expecting to try to retrieve types that may not be configured yet.</remarks>
		/// <returns>An instance of T</returns>
		public T RetrieveInstanceOf<T>()
		{
			return RetrieveInstanceOf<T>(true);
		}

		/// <summary>
		/// Retrieve a type, based on how it was registered (either as a singleton or automatically create more instances)
		/// </summary>
		/// <typeparam name="T">The type to create</typeparam>
		/// <param name="ThrowException">Indicates if this method should throw an exception or return default(T)</param>
		/// <returns>An instance of T, or default(T) if the item is not found and exceptions are disabled</returns>
		public T RetrieveInstanceOf<T>(bool ThrowException)
		{
			Type genericType = typeof(T);

			var instance = retrieveInstanceWithoutGenerics(ThrowException, genericType);
			if (instance != null)
			{
				return (T)instance;
			}

			if (ThrowException)
			{
				throw new InvalidOperationException("The type " + genericType.Name + " is not available for creation by the factory.");
			}
			else
			{
				return default(T);
			}
		}

		private bool canRetrieveType(Type genericType)
		{
			lock (interfaceTypesToCreate)
			{
				if (interfaceTypesToCreate.ContainsKey(genericType))
				{
					return true;
				}
			}

			lock (typesToCreate)
			{
				if (typesToCreate.Contains(genericType))
				{
					return true;
				}
			}

			lock (singletonTypes)
			{
				if (singletonTypes.ContainsKey(genericType))
					return true;
			}

			//we do this after the singletone ones so we can return the success if it has already occurred
			lock (lazySingletonTypes)
			{
				if (lazySingletonTypes.ContainsKey(genericType))
				{
					return true;
				}
			}

			lock (lazyFunctions)
			{
				if (lazyFunctions.ContainsKey(genericType))
				{
					return true;
				}
			}

			//finally, if it's still completely unknown, is it us?
			if (genericType == typeof(IFRMDependencyContainer) || genericType == typeof(SharedServiceLocator))
				return true;

			//otherwise check the parent completely, this handles singleton types on the parent:-
			if (this.ParentContainer != null)
			{
				return this.ParentContainer.canRetrieveType(genericType);
			}

			return false;
		}

		/// <summary>
		/// Retrieve an instance of a type from storage, potentially constructing it if necessary
		/// </summary>
		/// <param name="ThrowException"></param>
		/// <param name="genericType"></param>
		/// <returns></returns>
		private object retrieveInstanceWithoutGenerics(bool ThrowException, Type genericType)
		{
			lock (interfaceTypesToCreate)
			{
				if (interfaceTypesToCreate.ContainsKey(genericType))
				{
					Type creationType = interfaceTypesToCreate[genericType];

					return constructInstance(creationType);
				}

				if (this.ParentContainer != null && this.ParentContainer.interfaceTypesToCreate.ContainsKey(genericType))
				{
					Type creationType = this.ParentContainer.interfaceTypesToCreate[genericType];
					return constructInstance(creationType);
				}
			}

			lock (typesToCreate)
			{
				if (typesToCreate.Contains(genericType))
				{
					return constructInstance(genericType);
				}

				if (this.ParentContainer != null && this.ParentContainer.typesToCreate.Contains(genericType))
				{
					return constructInstance(genericType);
				}
			}

			lock (singletonTypes)
			{
				if (singletonTypes.ContainsKey(genericType))
					return singletonTypes[genericType];
			}

			//we do this after the singletone ones so we can return the success if it has already occurred
			lock (lazySingletonTypes)
			{
				if (lazySingletonTypes.ContainsKey(genericType))
				{
					var created = constructInstance(lazySingletonTypes[genericType]);
					if (created != null)
					{
						lock (singletonTypes) //deadlock possibility = 0; but we can't allow the inverse to ever happen and the singleton lock to contain a lazy singleton lock
						{
							if (!singletonTypes.ContainsKey(genericType))
							{
								singletonTypes.Add(genericType, created);
							}
						}
					}
					return created;
				}
			}

			lock (lazyFunctions)
			{
				if (lazyFunctions.ContainsKey(genericType))
				{
					return lazyFunctions[genericType].Invoke();
				}
			}

			//finally, if it's still completely unknown, is it us?
			if (genericType == typeof(IFRMDependencyContainer) || genericType == typeof(SharedServiceLocator))
				return this;

			//otherwise check the parent completely, this handles singleton types on the parent:-
			if (this.ParentContainer != null)
			{
				return this.ParentContainer.retrieveInstanceWithoutGenerics(ThrowException, genericType);
			}

			return null;
		}

		/// <summary>
		/// Construct an instance of a type and return it as an object
		/// </summary>
		/// <param name="genericType">The type to construct</param>
		/// <returns></returns>
		private object constructInstance(Type genericType)
		{
			Type[] paramTypes = new Type[0];
			lock (typeParameters)
			{
				if (typeParameters.ContainsKey(genericType))
				{
					//build a parameter list
					paramTypes = typeParameters[genericType].ToArray();
				}

				if (ParentContainer!=null && ParentContainer.typeParameters.ContainsKey(genericType))
				{
					//build a parameter list
					paramTypes = ParentContainer.typeParameters[genericType].ToArray();
				}
			}

			if (paramTypes.Any(a=>a == genericType))
			{
				throw new Exception("Infinite recursion of type parameters detected for "+genericType.Name+" - a type parameter should not be registered of the same type as the item it's being used to create.");
			}

			Dictionary<string, object> fixedParams = new Dictionary<string, object>();
			lock (valueParameters)
			{
				if (valueParameters.ContainsKey(genericType))
				{
					fixedParams = valueParameters[genericType];
				}

				if (ParentContainer != null && ParentContainer.valueParameters.ContainsKey(genericType))
				{
					fixedParams = ParentContainer.valueParameters[genericType];
				}
			}

			var constructor = genericType.GetConstructor(paramTypes);

			if (constructor == null)
			{
				//try again supplying just the dependency container:-
				constructor = genericType.GetConstructor(new Type[] { typeof(IFRMDependencyContainer) });

				if (constructor == null)
				{
					//okay, lets try searching for a constructor we can fulfill instead:-
					var allConstructors = genericType.GetConstructors();
					var constructors = allConstructors.Where(a => a.GetParameters().All(p => canRetrieveType(p.ParameterType))).ToArray();

					constructor = constructors.FirstOrDefault();
				}
			}

			if (constructor!=null)
			{
				var constructorParametrs = constructor.GetParameters();
				List<object> parameters = new List<object>();
				foreach (var param in constructorParametrs)
				{
					if (fixedParams.ContainsKey(param.Name))
					{
						//we have a known value for this parameter, so we'll supply that specifically
						parameters.Add(fixedParams[param.Name]);
					}
					else
					{
						//we don't have a known value, but we might still be able to resolve the type itself so
						//lets use the container to try to resolve the entire parameter type; works best if it takes an interface or known 
						//instance of course but can recurse
						var resolved = retrieveInstanceWithoutGenerics(false, param.ParameterType);

						//otherwise we'll add null and likely get an error when trying to new() the object.
						parameters.Add(resolved);
					}
				}
				object o = constructor.Invoke(parameters.ToArray());
				return o;
			}
			else
			{
				//create the type using the old instance creation support
				object o = genericType.Assembly.CreateInstance(genericType.FullName); //TODO: Add support for supplying parameters from the config file
				return o;
			}
		}

		/// <summary>
		/// Remove a registration for a particular type from the system
		/// </summary>
		/// <typeparam name="T">The type to remove</typeparam>
		public void RemoveRegistration<T>()
		{
			Type genericType = typeof(T);
			lock (singletonTypes)
			{
				if (singletonTypes.ContainsKey(genericType))
				{
					singletonTypes.Remove(genericType);
				}
			}

			lock (typesToCreate)
			{
				while (typesToCreate.Contains(genericType))
				{
					typesToCreate.Remove(genericType);
				}
			}
			
		}

		/// <summary>
		/// Register a new singleton instance
		/// </summary>
		/// <typeparam name="T">The type to be requested, this can be an interface or base class</typeparam>
		/// <param name="TypeInstance">The type to create, this can be any implementor of an interface or child of a base class</param>
		public void RegisterSingletonInstanceOf<T>(T TypeInstance)
		{
			lock (singletonTypes)
			{
				if (singletonTypes.ContainsKey(typeof(T)))
				{
					singletonTypes.Remove(typeof(T));
				}

				singletonTypes.Add(typeof(T), TypeInstance);
			}
		}

		/// <summary>
		/// Register a new singleton instance
		/// </summary>
		/// <typeparam name="T">The type to be requested, this can be an interface or base class</typeparam>
		/// <param name="TypeInstance">The type to create, this can be any implementor of an interface or child of a base class</param>
		public void RegisterSingletonType<T, TCreation>()
		{
			lock (lazySingletonTypes)
			{
				if (lazySingletonTypes.ContainsKey(typeof(T)))
				{
					lazySingletonTypes.Remove(typeof(T));
				}

				lazySingletonTypes.Add(typeof(T), typeof(TCreation));
			}
		}

		/// <summary>
		/// Register a non singleton type
		/// </summary>
		/// <remarks>Will throw an exception if the type does not have a parameterless constructor (and does not have the parameters defined in its config)</remarks>
		/// <typeparam name="T">The type to register, this can be any type that can be created on demand and has a parameterless constructor</typeparam>
		public void RegisterNonSingletonType<T>()
		{
			//TODO: Check for lack of parameters in the constructor and also check if we have config options
			lock (typesToCreate)
			{
				if (typesToCreate.Contains(typeof(T)))
					typesToCreate.Remove(typeof(T));

				typesToCreate.Add(typeof(T));
			}
		}

		/// <summary>
		/// Register a non singleton type but use a passed in type to trigger creation
		/// </summary>
		/// <remarks>Will throw an exception if the type does not have a parameterless constructor (and does not have the parameters defined in its config)</remarks>
		/// <param name="typeToCreate">The type of object to actually create, it must implement T or an exception will be thrown</param>
		/// <typeparam name="T">The type the system will look for, this can be an interface or a base class</typeparam>
		public void RegisterNonSingletonType<T>(Type creationType)
		{
			//TODO: Check for lack of parameters in the constructor and also check if we have config options	
			lock (interfaceTypesToCreate)
			{
				if (interfaceTypesToCreate.ContainsKey(typeof(T)))
					interfaceTypesToCreate.Remove(typeof(T));

				interfaceTypesToCreate.Add(typeof(T), creationType);
			}
		}

		/// <summary>
		/// Register a type parameter for an object we're creating
		/// </summary>
		/// <typeparam name="CreationType">The type being created (note: not the interface type but the actual concrete type)</typeparam>
		/// <typeparam name="Parameter">The type to try to supply as a parameter; note that this will be resolved using the service locator itself</typeparam>
		public void AddConstructorParameterToTypeCreation<CreationType,Parameter>()
		{
			lock (typeParameters)
			{
				if (!typeParameters.ContainsKey(typeof(CreationType)))
				{
					typeParameters.Add(typeof(CreationType), new List<Type>());
				}
				typeParameters[typeof(CreationType)].Add(typeof(Parameter));
			}
		}

		/// <summary>
		/// Add a static value (for example a connection string) as a parameter to a constructor
		/// </summary>
		/// <remarks>
		/// The parameter name must be specified
		/// </remarks>
		/// <typeparam name="CreationType">The type being created (note: not the interface type but the actual concrete type)</typeparam>
		/// <param name="parameterName">The defined name of the parameter; ordering will be inferred from the parameter name.  Be 
		/// aware that there may be a problem with types that have been obfuscated here, however it's unlikely the constructor params are.</param>
		/// <param name="Value">The parameter value to pass when encountered</param>
		public void AddConstructorParameterToTypeCreation<CreationType>(string ParameterName, object Value)
		{
			lock (valueParameters)
			{
				if (!valueParameters.ContainsKey(typeof(CreationType)))
				{
					valueParameters.Add(typeof(CreationType), new Dictionary<string,object>());
				}

				if (!valueParameters[typeof(CreationType)].ContainsKey(ParameterName))
				{
					valueParameters[typeof(CreationType)].Add(ParameterName, Value);
				}
				else
				{
					valueParameters[typeof(CreationType)][ParameterName] = Value;
				}
				
			}
		}

		/// <summary>
		/// Remove a constructor parameter
		/// </summary>
		/// <typeparam name="CreationType">The type that it would be supplied to</typeparam>
		/// <typeparam name="Parameter">The type being supplied to remove</typeparam>
		public void RemoveConstructorParameterToTypeCreation<CreationType, Parameter>()
		{
			lock (typeParameters)
			{
				if (!typeParameters.ContainsKey(typeof(CreationType)))
				{
					typeParameters.Add(typeof(CreationType), new List<Type>());
				}
				typeParameters[typeof(CreationType)].Remove(typeof(Parameter));
			}
		}

		/// <summary>
		/// Remove a constructor parameter with a parameter name
		/// </summary>
		/// <typeparam name="CreationType"></typeparam>
		/// <param name="ParameterName"></param>
		public void RemoveConstructorParameterToTypeCreation<CreationType>(string ParameterName)
		{
			lock (valueParameters)
			{
				if (valueParameters.ContainsKey(typeof(CreationType)))
				{
					valueParameters.Add(typeof(CreationType), new Dictionary<string, object>());
				}
				valueParameters[typeof(CreationType)].Remove(ParameterName);
			}
		}

		/// <summary>
		/// Register a type to create when requested for a specific interface
		/// </summary>
		/// <typeparam name="TRequest"></typeparam>
		/// <typeparam name="TCreate"></typeparam>
		public void RegisterNonSingletonType<TRequest, TCreate>()
		{
			this.RegisterNonSingletonType<TRequest>(typeof(TCreate));
		}

		/// <summary>
		/// Register a lazy singleton function to execute and obtain an instance of something on demand
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="lazyFunction"></param>
		public void RegisterLazySingleton<T>(Func<object> lazyFunction)
		{
			this.lazyFunctions.Add(typeof(T), lazyFunction);
		}
	}