using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace InABox.Core { public interface ILookupDefinition where TLookup : BaseObject, new() { Filter? DefineFilter(); Columns DefineColumns(); Columns RequiredColumns(); SortOrder DefineSortOrder(); string FormatLookup(Dictionary values, IEnumerable exclude); } public interface ILookupDefinition where TLookup : Entity { Filter DefineFilter(TEntity[] items); /// /// Define the columns required for the items parameter of . /// /// Columns DefineFilterColumns(); } public interface IStaticLookupDefinition { Dictionary DefineLookups(PropertyInfo property); } public static class LookupFactory { #region LookupCache private class LookupCacheEntry { public LookupCacheEntry(object? factory) { DefaultFactory = factory; CustomFactories = new Dictionary(); StaticFactory = null; } public object? DefaultFactory { get; } public Dictionary CustomFactories { get; } public object? StaticFactory { get; set; } public object? GetFactory(Type? type = null) { if (type == null) return DefaultFactory; if (CustomFactories.ContainsKey(type)) return CustomFactories[type]; return DefaultFactory; } } private class LookupCache { private static Dictionary _cache; public LookupCache() { _cache = new Dictionary(); // Load up the default types var defaulttypes = CoreUtils.TypeList( AppDomain.CurrentDomain.GetAssemblies(), x => !x.IsAbstract && x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition().Equals(typeof(ILookupDefinition<>)) ) ); foreach (var type in defaulttypes) try { var intfs = type.GetInterfaces().Where(i => i.IsGenericType && i.GetGenericTypeDefinition().Equals(typeof(ILookupDefinition<>)) ); foreach (var intf in intfs) _cache[intf.GenericTypeArguments.First()] = new LookupCacheEntry(Activator.CreateInstance(type)); } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } // Now load up the specific types var specifictypes = CoreUtils.TypeList( AppDomain.CurrentDomain.GetAssemblies(), x => !x.IsAbstract && x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition().Equals(typeof(ILookupDefinition<,>)) ) ); foreach (var type in specifictypes) { var intfs = type.GetInterfaces().Where(i => i.IsGenericType && i.GetGenericTypeDefinition().Equals(typeof(ILookupDefinition<,>)) ); foreach (var intf in intfs) { var lookuptype = intf.GenericTypeArguments.First(); var entitytype = intf.GenericTypeArguments.Last(); if (!_cache.ContainsKey(lookuptype)) _cache[lookuptype] = new LookupCacheEntry(null); _cache[lookuptype].CustomFactories[entitytype] = Activator.CreateInstance(type); } } // Load up the static lookups var staticlookups = CoreUtils.TypeList( AppDomain.CurrentDomain.GetAssemblies(), x => !x.IsAbstract && x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition().Equals(typeof(IStaticLookupDefinition<>)) ) ); foreach (var type in staticlookups) { var intf = type.GetInterfaces().First(i => i.IsGenericType && i.GetGenericTypeDefinition().Equals(typeof(IStaticLookupDefinition<>)) ); var iType = intf.GenericTypeArguments.First(); if (!_cache.ContainsKey(iType)) _cache[iType] = new LookupCacheEntry(null); _cache[iType].StaticFactory = Activator.CreateInstance(type); } } public object? GetFactory(Type lookup, Type? entity = null) { if (!_cache.ContainsKey(lookup)) return null; if (entity == null) return _cache[lookup].DefaultFactory; if (_cache[lookup].CustomFactories.ContainsKey(entity)) return _cache[lookup].CustomFactories[entity]; return null; } public object? GetStaticFactory(Type lookup) { if (!_cache.ContainsKey(lookup)) return null; return _cache[lookup].StaticFactory; } } private static readonly LookupCache _cache = new LookupCache(); #endregion #region General Factory Methods private static object? DoInvoke(Type TLookup, Type TResult, string Method) { var factory = _cache.GetFactory(TLookup); if (factory != null) { var filtertype = TResult.MakeGenericType(TLookup); var method = factory.GetType().GetMethods().FirstOrDefault(m => m.Name.Equals(Method) && !m.GetParameters().Any() && m.ReturnType == filtertype ); if (method != null) try { object[] parameters = { }; return method.Invoke(factory, parameters); } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } } return null; } // If a specific Entity/Lookup match can't be found, // this will fall back to the global Lookup filter (if there is one) // Otherwise, it will return null private static object? DoInvoke(Type TLookup, Type TEntity, IEnumerable items, Type TResult, string Method) { var factory = _cache.GetFactory(TLookup, TEntity); if (factory != null) { var filtertype = TResult.MakeGenericType(TLookup); var method = factory.GetType().GetMethods().FirstOrDefault(m => m.Name.Equals(Method) && m.GetParameters().Any() && m.ReturnType == filtertype && m.GetParameters().First().ParameterType == items.GetType() ); if (method != null) try { object[] parameters = { items }; return method.Invoke(factory, parameters); } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } } return DoInvoke(TLookup, TResult, Method); } private static object? DoInvoke(Type TLookup, Type TEntity, Type TResult, string Method) { var factory = _cache.GetFactory(TLookup, TEntity); if (factory != null) { var method = factory.GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault(m => m.Name.Split('.').Last().Equals(Method) // For explicit implementations, whose name is ILookupDefinition.Method && !m.GetParameters().Any() && m.ReturnType == TResult ); if (method != null) try { return method.Invoke(factory, Array.Empty()); } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } } return null; } private static Dictionary? DoInvoke(Type TLookup, PropertyInfo property, string Method) { var factory = _cache.GetStaticFactory(TLookup); if (factory != null) { var method = factory.GetType().GetMethods().Where(m => m.Name.Equals(Method) && m.GetParameters().SingleOrDefault() != null && m.ReturnType.Equals(typeof(Dictionary)) ).FirstOrDefault(); if (method != null) try { object[] parameters = { property }; return method.Invoke(factory, parameters) as Dictionary; } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } } return null; } private static object? DoInvoke(Type TLookup, Type TReturn, string Method, params object[] args) { var factory = _cache.GetFactory(TLookup); if (factory != null) { var method = factory.GetType().GetMethods().Where(m => m.Name.Equals(Method) && m.GetParameters().Length == args.Length && m.ReturnType.Equals(TReturn) ).FirstOrDefault(); if (method != null) try { return method.Invoke(factory, args); } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } } return null; } #endregion #region Formats public static string DefaultFormatLookup(Dictionary values, IEnumerable exclude) { return string.Join(": ", values.Where(x => x.Value != null && x.Value.GetType() != typeof(Guid) && (exclude == null || !exclude.Contains(x.Key))) .Select(p => p.Value)); } public static string FormatLookup(Type TLookup, Dictionary values, IEnumerable exclude) { var result = DoInvoke(TLookup, typeof(string), "FormatLookup", values, exclude) as string; if(result is null) { return DefaultFormatLookup(values, exclude); } return result; } public static string FormatLookup(Dictionary values, IEnumerable exclude) { return FormatLookup(typeof(TLookup), values, exclude); } #endregion #region Filters public static IFilter? DefineFilter(Type TLookup) { return DoInvoke(TLookup, typeof(Filter<>), "DefineFilter") as IFilter; //var factory =_cache.GetFactory(TLookup); //if (factory != null) //{ // Type filtertype = typeof(Filter<>).MakeGenericType(TLookup); // var method = factory.GetType().GetMethods().Where(m => // m.Name.Equals("DefineFilter") && // m.GetParameters().Any() && // m.GetParameters().First().ParameterType.IsByRef && // m.GetParameters().First().ParameterType.GetElementType().Equals(filtertype)).FirstOrDefault(); // if (method != null) // { // try // { // object[] parameters = new object[] { null }; // method.Invoke(factory, parameters); // return parameters[0]; // } // catch (Exception e) // { // } // } //} //return null; } public static Filter? DefineFilter() { return DefineFilter(typeof(TLookup)) as Filter; } public static IFilter? DefineFilter(Type TLookup, Type TEntity, IEnumerable items) { return DoInvoke(TLookup, TEntity, items, typeof(Filter<>), "DefineFilter") as IFilter; } public static IFilter? DefineFilter(IEnumerable items, Type TLookup) { return DoInvoke(TLookup, typeof(TEntity), items, typeof(Filter<>), "DefineFilter") as IFilter; } public static Filter? DefineFilter(TEntity[] items) where TEntity : Entity where TLookup : Entity { return DefineFilter(items, typeof(TLookup)) as Filter; } #endregion #region Columns public static IColumns DefineColumns(Type TLookup) { var result = DoInvoke(TLookup, typeof(Columns<>), "DefineColumns") as IColumns; if (result == null) { result = Columns.Create(TLookup).DefaultColumns(); } return result; } public static Columns DefineColumns() { return (DefineColumns(typeof(TLookup)) as Columns)!; } public static IColumns DefineFilterColumns(Type TLookup, Type TEntity, IEnumerable items) { var result = DoInvoke(TLookup, TEntity, typeof(Columns<>).MakeGenericType(TEntity), "DefineFilterColumns") as IColumns; result ??= Columns.Create(TLookup).DefaultColumns(); return result; } public static IColumns DefineFilterColumns(Type TLookup) { var result = DoInvoke(TLookup, typeof(TEntity), typeof(Columns<>).MakeGenericType(typeof(TEntity)), "DefineFilterColumns") as IColumns; if (result == null) { result = Columns.Create(TLookup); } return result; } public static Columns DefineFilterColumns() where TEntity : Entity where TLookup : Entity { return DefineFilterColumns(typeof(TLookup)) as Columns; } #endregion #region RequiredColumns public static IColumns DefaultRequiredColumns(Type TLookup) { var result = Columns.Create(TLookup); var props = DatabaseSchema.Properties(TLookup).Where(x => x.Required); foreach (var prop in props) result.Add(prop.Name); return result; } public static Columns DefaultRequiredColumns() => (DefaultRequiredColumns(typeof(TLookup)) as Columns)!; public static IColumns RequiredColumns(Type TLookup) { var result = DoInvoke(TLookup, typeof(Columns<>), "RequiredColumns") as IColumns; if (result == null) { result = DefaultRequiredColumns(TLookup); } return result; } public static Columns RequiredColumns() { return RequiredColumns(typeof(TLookup)) as Columns; } #endregion #region SortOrder public static ISortOrder DefineSort(Type TLookup) { return DoInvoke(TLookup, typeof(SortOrder<>), "DefineSortOrder") as ISortOrder; } public static SortOrder DefineSort() { return DefineSort(typeof(TLookup)) as SortOrder; } #endregion #region Static Lookups public static Dictionary DefineLookups(Type type, PropertyInfo property) { return DoInvoke(type, property, "DefineLookups"); } public static Dictionary DefineLookups(Expression> property) { return DefineLookups(typeof(TLookup), CoreUtils.GetPropertyFromExpression(property)); } #endregion } public abstract class BaseObjectLookup : ILookupDefinition where TLookup : BaseObject, new() { public virtual string FormatLookup(Dictionary values, IEnumerable exclude) { var filtered = new Dictionary(); var cols = DefineColumns(); foreach (var col in cols.Items) if (values.ContainsKey(col.Property) && values[col.Property] != null && values[col.Property]?.GetType() != typeof(Guid)) filtered[col.Property] = values[col.Property]; return LookupFactory.DefaultFormatLookup(filtered, exclude); } public abstract Columns DefineColumns(); public abstract Columns RequiredColumns(); public abstract Filter? DefineFilter(); public abstract SortOrder DefineSortOrder(); } public abstract class EntityLookup : BaseObjectLookup where TLookup : Entity, new() { public override Columns DefineColumns() { return new Columns(x => x.ID); } public override Columns RequiredColumns() { return LookupFactory.DefaultRequiredColumns(); } } }