using Comal.Classes; using Expressive; using InABox.Core; using InABox.Database; using Org.BouncyCastle.Asn1.X509.Qualified; using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace PRS.Shared.Events; public interface IEventData : ISerializeBinary { IEvent Event { get; } } public class EventData : IEventData where T : IEvent where TDataModel : IEventDataModel { public T Event { get; set; } /// /// A list of triggers for this event. If any of the triggers match, the event runs. /// public List> Triggers { get; set; } public List> Actions { get; set; } IEvent IEventData.Event => Event; public EventData(T eventData) { Event = eventData; Triggers = new List>(); Actions = new List>(); } public Notification GenerateNotification(TDataModel model) => Event.GenerateNotification(model); public void SerializeBinary(CoreBinaryWriter writer) { Event.SerializeBinary(writer); writer.Write(Triggers.Count); foreach(var trigger in Triggers) { EventUtils.SerializeObject(trigger, writer); } writer.Write(Actions.Count); foreach(var action in Actions) { EventUtils.SerializeObject(action, writer); } } public void DeserializeBinary(CoreBinaryReader reader) { Event.DeserializeBinary(reader); var nTriggers = reader.ReadInt32(); for(int i = 0; i < nTriggers; ++i) { var trigger = EventUtils.DeserializeObject>(EventUtils.GetTriggerType, reader); Triggers.Add(trigger); } var nActions = reader.ReadInt32(); for(int i = 0; i < nActions; ++i) { var action = EventUtils.DeserializeObject>(EventUtils.GetTriggerType, reader); Actions.Add(action); } } } public static class EventUtils { #region Serialisation public static void SerializeObject(ISerializeBinary obj, CoreBinaryWriter writer) { writer.Write(obj.GetType().Name); foreach(var arg in obj.GetType().GenericTypeArguments) { writer.Write(CoreUtils.EntityName(arg)); } obj.SerializeBinary(writer); } public static T DeserializeObject(Func typeSource, CoreBinaryReader reader) where T : class, ISerializeBinary { var eventTypeName = reader.ReadString(); var objType = typeSource(eventTypeName); var typeParams = objType.GetTypeInfo().GenericTypeParameters; var typeArgs = new Type[typeParams.Length]; for(int i = 0; i < typeParams.Length; ++i) { var entityType = CoreUtils.GetEntity(reader.ReadString()); typeArgs[i] = entityType; } if(typeArgs.Length > 0) { objType = objType.MakeGenericType(typeArgs); } var obj = (Activator.CreateInstance(objType) as T)!; obj.DeserializeBinary(reader); return obj; } public static void Serialize(IEventData data, CoreBinaryWriter writer) { writer.Write(data.Event.GetType().Name); foreach(var arg in data.Event.GetType().GenericTypeArguments) { writer.Write(CoreUtils.EntityName(arg)); } data.SerializeBinary(writer); } public static IEventData Deserialize(CoreBinaryReader reader) { var eventTypeName = reader.ReadString(); var eventType = EventUtils.GetEventType(eventTypeName); var typeParams = eventType.GetTypeInfo().GenericTypeParameters; var typeArgs = new Type[typeParams.Length]; for(int i = 0; i < typeParams.Length; ++i) { var entityType = CoreUtils.GetEntity(reader.ReadString()); typeArgs[i] = entityType; } if(typeArgs.Length > 0) { eventType = eventType.MakeGenericType(typeArgs); } var ev = (Activator.CreateInstance(eventType) as IEvent)!; var dataModelType = ev.GetType().GetInterfaceDefinition(typeof(IEvent<>))!.GenericTypeArguments[0]; var eventDataType = typeof(EventData<,>).MakeGenericType(ev.GetType(), dataModelType); var eventData = (Activator.CreateInstance(eventDataType, ev) as IEventData)!; eventData.DeserializeBinary(reader); return eventData; } #endregion #region Event Types private static Dictionary? _eventTypes; private static Dictionary? _triggerTypes; private static Dictionary? _actionTypes; private static Dictionary>? _eventTriggerTypes; private static Dictionary>? _eventActionTypes; [MemberNotNullWhen(true, nameof(_eventTypes), nameof(_triggerTypes), nameof(_actionTypes), nameof(_eventTriggerTypes), nameof(_eventActionTypes))] private static bool _loadedTypes { get; set; } [MemberNotNull(nameof(_eventTypes), nameof(_triggerTypes), nameof(_actionTypes), nameof(_eventTriggerTypes), nameof(_eventActionTypes))] private static void LoadTypes() { if (_loadedTypes) return; _loadedTypes = true; _eventTypes = new(); _triggerTypes = new(); _actionTypes = new(); _eventTriggerTypes = new(); _eventActionTypes = new(); foreach(var type in CoreUtils.TypeList(x => true)) { if (type.HasInterface(typeof(IEvent<>))) { if (type.GetConstructor([]) is not null) { _eventTypes[type.Name] = type; } } else if (type.GetInterfaceDefinition(typeof(IEventTrigger<,>)) is Type eventTriggerInterface) { if (type.GetConstructor([]) is not null) { _triggerTypes[type.Name] = type; var eventType = eventTriggerInterface.GenericTypeArguments[0]; eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType; _eventTriggerTypes.GetValueOrAdd(eventType).Add(type); } } else if (type.GetInterfaceDefinition(typeof(IEventAction<>)) is Type eventActionInterface) { if (type.GetConstructor([]) is not null) { _actionTypes[type.Name] = type; var eventType = eventActionInterface.GenericTypeArguments[0]; eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType; _eventActionTypes.GetValueOrAdd(eventType).Add(type); } } } } public static IEnumerable GetEventTriggerTypes(Type eventType) { LoadTypes(); eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType; return _eventTriggerTypes.GetValueOrDefault(eventType) ?? Enumerable.Empty(); } public static IEnumerable GetEventActionTypes(Type eventType) { LoadTypes(); eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType; return _eventActionTypes.GetValueOrDefault(eventType) ?? Enumerable.Empty(); } public static Type GetEventType(string type) { LoadTypes(); return _eventTypes[type]; // Force exception if not a valid type name. } public static Type GetTriggerType(string type) { LoadTypes(); return _triggerTypes[type]; // Force exception if not a valid type name. } public static Type GetActionType(string type) { LoadTypes(); return _actionTypes[type]; // Force exception if not a valid type name. } #endregion #region Execution private static bool Check(EventData ev, TDataModel dataModel) where T : IEvent where TDataModel : IEventDataModel { return ev.Triggers.Any(x => x.Check(dataModel)); } public static void Run(IStore store, Event ev, EventData evData, TDataModel dataModel) where T : IEvent where TDataModel : IEventDataModel { if (!Check(evData, dataModel)) return; foreach(var action in evData.Actions) { IEvent.RunAction(evData.Event, dataModel, action); } NotifySubscribers(store, ev, evData, dataModel); } private static void NotifySubscribers(IStore store, Event ev, EventData evData, TDataModel dataModel) where T : IEvent where TDataModel : IEventDataModel { string? description; if (ev.NotificationExpression.IsNullOrWhiteSpace()) { description = null; } else { var descriptionExpr = new CoreExpression(ev.NotificationExpression, typeof(string)); if(!descriptionExpr.TryEvaluate(dataModel).Get(out description, out var error)) { CoreUtils.LogException(store.UserID, error, extra: "Error notifying subscribers", store.Logger); return; } } var subscribers = store.Provider.Query( new Filter(x => x.Event.ID).IsEqualTo(ev.ID), Columns.None().Add(x => x.Employee.ID)) .ToArray(); var notifications = new List(); foreach(var subscriber in subscribers) { var notification = evData.GenerateNotification(dataModel); notification.Employee.CopyFrom(subscriber.Employee); if(description is not null) { notification.Description = description; } notifications.Add(notification); } store.Provider.Save(notifications); } #endregion } #region DataModel Definition public interface IEventVariable { string Name { get; set; } Type Type { get; set; } } public class StandardEventVariable : IEventVariable { public string Name { get; set; } public Type Type { get; set; } public StandardEventVariable(string name, Type type) { Name = name; Type = type; } } public class ListEventVariable : IEventVariable { public string Name { get; set; } public Type Type { get; set; } public ListEventVariable(string name, Type type) { Name = name; Type = type; } } public interface IEventDataModelDefinition { IEnumerable GetVariables(); IEventVariable? GetVariable(string name); } #endregion #region DataModel public interface IEventDataModel : IVariableProvider { bool TryGetVariable(string name, out object? value); bool IVariableProvider.TryGetValue(string variableName, out object? value) { return TryGetVariable(variableName, out value); } T RootModel() where T : IEventDataModel { if(this is IChildEventDataModel child) { return child.Parent.RootModel(); } else if(this is T root) { return root; } else { throw new Exception($"Root model of wrong type; expected {typeof(T).FullName}; got {GetType().FullName}"); } } } public interface ITypedEventDataModel : IEventDataModel { public Type EntityType { get; } } public interface IChildEventDataModel : IEventDataModel { IEventDataModel Parent { get; } } public class ChildEventDataModel : IChildEventDataModel { public IEventDataModel Parent { get; set; } public Dictionary Values { get; set; } = new Dictionary(); public ChildEventDataModel(IEventDataModel parent) { Parent = parent; } public bool TryGetVariable(string name, out object? value) { return Values.TryGetValue(name, out value) || Parent.TryGetVariable(name, out value); } } #endregion public interface IEvent : ISerializeBinary { IEventDataModelDefinition DataModelDefinition(); public static void RunAction(IEvent ev, IEventDataModel model, IEventAction action) { var dataModelDef = ev.DataModelDefinition(); var values = new List<(string name, object? value)[]>() { Array.Empty<(string, object?)>() }; var vars = action.ReferencedVariables(); foreach(var variable in vars) { var varDef = dataModelDef.GetVariable(variable); if(varDef is ListEventVariable) { if (model.TryGetVariable(varDef.Name, out var list) && list is IEnumerable enumerable) { var oldValues = values; values = new List<(string name, object? value)[]>(); foreach(var item in enumerable) { foreach(var valueList in oldValues) { values.Add(valueList.Concatenate(new (string name, object? value)[] { (varDef.Name, item) })); } } } else { values.Clear(); break; } } } if(values.Count > 0 && (values.Count > 1 || values[0].Length > 0)) { var subModel = new ChildEventDataModel(model); foreach(var valueSet in values) { subModel.Values.Clear(); foreach(var (name, value) in valueSet) { subModel.Values[name] = value; } action.Execute(subModel); } } else { action.Execute(model); } } } public interface IEvent : IEvent { Notification GenerateNotification(TDataModel model); } public interface IEventTrigger { string GetDescription(); } public interface IEventTrigger : ISerializeBinary, IEventTrigger where TEvent : IEvent where TDataModel : IEventDataModel { bool Check(TDataModel dataModel); } public interface IEventAction : ISerializeBinary { IEnumerable ReferencedVariables(); object? Execute(IEventDataModel dataModel); string GetDescription(); } public interface IEventAction : IEventAction where TEvent : IEvent { }