using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using Kaleida.ServiceMonitor.Framework;
using System.Linq;
namespace Kaleida.ServiceMonitor.Model.Runtime
{
public class ReflectionOperationFactory : IPreparedRequestFactory, IResponseHandlerFactory
{
private readonly List<Assembly> searchAssemblies;
public ReflectionOperationFactory(IEnumerable<Assembly> searchAssemblies)
{
this.searchAssemblies = searchAssemblies.ToList();
}
PreparedRequest IPreparedRequestFactory.Build(string name, IList<string> arguments)
{
var type = GetOperationType(name, arguments, typeof (PreparedRequest));
return (PreparedRequest) CreateInstance(type, arguments);
}
ResponseHandler IResponseHandlerFactory.Build(string name, IList<string> arguments)
{
var type = GetOperationType(name, arguments, typeof(ResponseHandler));
return (ResponseHandler)CreateInstance(type, arguments);
}
public RuntimeEnvironment GetRuntimeEnvironment()
{
return new RuntimeEnvironment(GetOperationTypes(typeof (PreparedRequest)), GetOperationTypes(typeof (ResponseHandler)));
}
private IList<Type> GetOperationTypes(Type baseType)
{
var searchTypes = searchAssemblies.SelectMany(i => i.GetTypes()).ToList();
var withCorrectBase = new HashSet<Type>(searchTypes.Where(i => i.DerivesFrom(baseType)));
var withPublicVisibility = new HashSet<Type>(searchTypes.Where(i => i.IsVisible));
var withMatchingConstructor = new HashSet<Type>(searchTypes.Where(i => i.HasPublicFullyStringParameterisedConstructor()));
return withCorrectBase.Intersect(withPublicVisibility).Intersect(withMatchingConstructor).ToList();
}
private Type GetOperationType(string name, IEnumerable<string> arguments, Type baseType)
{
if(!searchAssemblies.Any())
throw new InvalidOperationException(string.Format("Cannot find a {0} implementation for '{1}'. There are no operation definition assemblies available", baseType.Name, name));
var searchTypes = searchAssemblies.SelectMany(i => i.GetTypes()).ToList();
var withMatchingName = new HashSet<Type>(searchTypes.Where(i => i.Name == name.ToPascalCase()));
var withCorrectBase = new HashSet<Type>(searchTypes.Where(i => i.DerivesFrom(baseType)));
var withPublicVisibility = new HashSet<Type>(searchTypes.Where(i => i.IsVisible));
var withMatchingConstructor = new HashSet<Type>(searchTypes.Where(i => i.HasPublicFullyStringParameterisedConstructor(arguments.Count())));
var candidateTypes = withMatchingName.Intersect(withCorrectBase).Intersect(withPublicVisibility).Intersect(withMatchingConstructor);
var type = candidateTypes.FirstOrDefault();
if (type == null)
{
var message = new StringBuilder();
message.AppendFormat("Cannot find a {0} implementation for '{1}' within these search assemblies:\r\n", baseType.Name, name);
message.Append(searchAssemblies.Select(i => i.FullName).ToBulletedList());
message.Append("\r\n\r\n");
message.AppendFormat("The .NET implementation must be named '{0}', ", name.ToPascalCase());
message.AppendFormat("derive from {0}, ", baseType.Name);
message.AppendFormat("have public visibility and a public constructor containing all string parameters.\r\n\r\n");
if (withMatchingName.Any())
{
message.AppendFormat("The following type(s) have the right name but are not quite right:\r\n");
message.Append(withMatchingName.Select(i => GetCriteriaAnalysis(i, name, arguments.ToList(), baseType)).Join("\r\n"));
}
throw new InvalidOperationException(message.ToString());
}
return type;
}
internal static string GetCriteriaAnalysis(Type type, string name, IList<string> arguments, Type baseType)
{
var message = new StringBuilder();
message.AppendFormat(" {0}:\r\n", type.FullName);
message.AppendFormat(" Has correct name? {0}\r\n", (type.Name == name.ToPascalCase()).ToBallotBox());
message.AppendFormat(" Derives from {0}? {1}\r\n", baseType, type.DerivesFrom(baseType).ToBallotBox());
message.AppendFormat(" Publicly visibile? {0}\r\n", type.IsVisible.ToBallotBox());
message.AppendFormat(" Public constructor with {0} string parameter(s)? {1}\r\n", arguments.Count(), type.HasPublicFullyStringParameterisedConstructor(arguments.Count()).ToBallotBox());
return message.ToString();
}
private static object CreateInstance(Type type, IEnumerable<string> arguments)
{
try
{
return Activator.CreateInstance(type, arguments.Select(i => (object)i).ToArray());
}
catch (TargetInvocationException exception)
{
throw exception.InnerException;
}
}
}
}