using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; // A spot for immutable helpers // TODO // TODO // TODO // x) Wrap metadata into its own struct namespace imm; static public class Util { //This can handle both Timed and Recorded static public T Process( ref T obj, Func fn, string reason = "", [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, [CallerArgumentExpression("fn")] string expression = default ) where T : Recorded { obj = obj.Process( fn, reason, memberName, filePath, lineNumber, expression ); return obj; } //This can handle both Timed and Recorded static public T Versioned( ref T obj, Func fn, string reason = "", [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, [CallerArgumentExpression("fn")] string expression = default ) where T : Versioned { obj = obj.Process( fn, reason ); return obj; } } public record class Versioned where T : Versioned { public delegate void ChangeDelegate( T ZZOld, T next ); public record class MetaData { public uint Version { get; internal set; } = 0; public string Reason { get; internal set; } = ""; public MetaData() { } } internal Versioned( MetaData meta ) { MetaStorage = meta; } internal MetaData MetaStorage = new(); public MetaData Meta => MetaStorage; public ChangeDelegate OnChange; public T Process( Func fn, string reason = "" ) { var newT = fn( ( T )this ); return newT with { MetaStorage = Meta with { Version = newT.Meta.Version + 1, Reason = reason, } }; } } public record class Recorded : Versioned where T : Recorded { new public record class MetaData : Versioned.MetaData { public T? ZZOld { get; internal set; } public T? Old => ZZOld; public string Expression { get; internal set; } = ""; public string MemberName { get; internal set; } = ""; public string FilePath { get; internal set; } = ""; public int LineNumber { get; internal set; } = -1; public MetaData() { } } public Recorded() : this( new MetaData() ) { } public Recorded(MetaData meta) : base( meta ) { } new public MetaData Meta => MetaStorage as MetaData; virtual public T Record( string reason = "", [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0 ) { return Process( t => t, reason, memberName, filePath, lineNumber ); } virtual public T Process( T next, string reason = "", [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, [CallerArgumentExpression("next")] string expression = default ) { return Process( ( old ) => next, reason, memberName, filePath, lineNumber, expression ); } virtual public T Process( Func fn, string reason = "", [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, [CallerArgumentExpression("fn")] string expression = default ) { var orig = ( T )this; var next = fn( orig ); var ret = next with { //Do the Versioned code here MetaStorage = Meta with { Version = orig.Meta.Version + 1, Reason = reason, ZZOld = orig, MemberName = memberName, FilePath = filePath, LineNumber = lineNumber, } }; OnChange( orig, ret ); return ret; } } public record class Timed : Recorded where T : Timed { new public record class MetaData : Recorded.MetaData { public readonly DateTime CreatedAt = DateTime.Now; public DateTime TouchedAt { get; internal set; } = DateTime.Now; } public Timed() : this( new MetaData() ) { } public Timed( MetaData meta ) : base( meta ) { } new public MetaData Meta => MetaStorage as MetaData; public TimeSpan Since => Meta.TouchedAt - Meta.Old?.Meta.TouchedAt ?? TimeSpan.MaxValue; override public T Record( string reason = "", [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0 ) { return Process( t => t, reason, memberName, filePath, lineNumber ); } override public T Process( T next, string reason = "", [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, [CallerArgumentExpression("next")] string expression = default ) { return Process( ( old ) => next, reason, memberName, filePath, lineNumber, expression ); } override public T Process( Func fn, string reason = "", [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, [CallerArgumentExpression("fn")] string expression = default ) { var orig = ( T )this; var next = fn( orig ); var ret = next with { MetaStorage = Meta with { //Versioned Version = orig.Meta.Version + 1, Reason = reason, //Recorded MemberName = memberName, FilePath = filePath, LineNumber = lineNumber, ZZOld = orig, //Timed TouchedAt = DateTime.Now, } }; if( OnChange != null) OnChange( orig, ret ); return ret; } }