#nullable enable using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Text; using System.Threading; using System.Threading.Tasks; using lib; namespace imm; /* T O D O : T O D O : T O D O : x) Add unit tests for all this. This will definitely benefit from them */ static public class Util { //This can handle both Timed and Recorded static public T Process( ref T obj, Func fn, string reason = "", [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0, [CallerArgumentExpression("fn")] string dbgExp = "" ) where T : Recorded { obj = obj.Process( fn, reason, dbgName, dbgPath, dbgLine, dbgExp ); return obj; } static public T LightProcess( ref T obj, Func fn, string reason = "", [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0, [CallerArgumentExpression("fn")] string dbgExp = "" ) where T : Versioned { obj = obj.Process( fn, reason ); return obj; } } public interface Meta { public uint Version => 0; public string Reason => ""; public string Expression => ""; public string MemberName => ""; public string FilePath => ""; public int LineNumber => -1; public DateTime CreatedAt => DateTime.MinValue; public DateTime TouchedAt => DateTime.MinValue; } public interface Imm { public Meta Meta { get; } public object Record( string reason = "", [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ); public Imm Process( Imm next, string reason = "", [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0, [CallerArgumentExpression("next")] string dbgExp = "" ) { return next; } } //[lib.Ser( Types = lib.Types.None )] public record class Versioned : Imm where T : Versioned { public delegate void ChangeDelegate( T ZZOld, T next ); public record class MetaData : Meta { public uint Version { get; internal set; } = 0; public string Reason { get; internal set; } = ""; public MetaData() { } } protected Versioned( ) : this( new MetaData { Version = 1, Reason = $"Versioned.cons" } ) { } internal Versioned( MetaData meta ) { MetaStorage = meta; } virtual public T Record( string reason = "", [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ) { return Process( t => t, reason ); } protected MetaData MetaStorage = new(); [DebuggerBrowsable(DebuggerBrowsableState.Never)] public MetaData Meta => MetaStorage; Meta Imm.Meta => MetaStorage; [lib.Dont] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public ChangeDelegate OnChange = (old, cur) => {}; /* public void AddOnChange( ChangeDelegate fn, string reason, [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0` ) { log.debug( $"ADD {log.whatFile(dbgPath)}({dbgLine}): {dbgName} added OnChange bcs {reason}" ); OnChange += fn; } public void RemOnChange( ChangeDelegate fn, [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ) { log.debug( $"REM {log.whatFile(dbgPath)}({dbgLine}): {dbgName} removing OnChange" ); OnChange -= fn; } */ 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, } }; } object Imm.Record( string reason, string dbgName, string dbgPath, int dbgLine ) => Record( reason, dbgName, dbgPath, dbgLine ); //public object Record( string reason = "", [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ) => Recorded( ); } //[lib.Ser( Types = lib.Types.None )] public record class Recorded : Versioned, imm.Imm where T : Recorded { new public record class MetaData : Versioned.MetaData { [lib.Dont] public T? ZZOld { get; internal set; } public T? Old => ZZOld; public string DbgName { get; internal set; } = ""; public string DbgPath { get; internal set; } = ""; public int DbgLine { get; internal set; } = -1; public string DbgExp { get; internal set; } = ""; public MetaData() { } } public Recorded() : this( new MetaData() ) { } public Recorded(MetaData meta) : base( meta ) { } [DebuggerBrowsable(DebuggerBrowsableState.Never)] new public MetaData Meta => MetaStorage as MetaData ?? new MetaData(); override public T Record( string reason = "", [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ) { return Process( t => t, reason, dbgName, dbgPath, dbgLine ); } virtual public T Process( T next, string reason = "", [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0, [CallerArgumentExpression("next")] string dbgExp = "" ) { return ProcessWork( ( old ) => next, reason, dbgName, dbgPath, dbgLine, dbgExp ); } virtual public T Process( Func fn, string reason = "", [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0, [CallerArgumentExpression("fn")] string dbgExp = "" ) { return ProcessWork( fn, reason, dbgName, dbgPath, dbgLine, dbgExp ); } virtual public T ProcessWork( Func fn, string reason, string dbgName, string dbgPath, int dbgLine, string dbgExp ) { var orig = ( T )this; var next = fn( orig ); if( object.ReferenceEquals( orig, next) ) return next; var ret = next with { //Do the Versioned code here MetaStorage = Meta with { Version = orig.Meta.Version + 1, Reason = !string.IsNullOrWhiteSpace( reason ) ? reason : next.Meta.Reason, ZZOld = orig, DbgName = dbgName, DbgPath = dbgPath, DbgLine = dbgLine, DbgExp = dbgExp } }; OnChange( orig, ret ); return ret; } } public record class Timed : Recorded, imm.Imm 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 ) { } [DebuggerBrowsable(DebuggerBrowsableState.Never)] new public MetaData Meta => MetaStorage as MetaData ?? new MetaData(); public TimeSpan Since => Meta.TouchedAt - Meta.Old?.Meta.TouchedAt ?? TimeSpan.MaxValue; public void CallOnChange() { OnChange( Meta.Old, (T)this ); } override public T Record( string reason = "", [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ) { return Process( t => t with { MetaStorage = t.Meta with { Reason = $"Record {reason}" }}, reason, dbgName, dbgPath, dbgLine ); } override public T Process( T next, string reason = "", [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0, [CallerArgumentExpression("next")] string dbgExp = "" ) { return ProcessWork( ( old ) => next, reason, dbgName, dbgPath, dbgLine, dbgExp ); } public U ProcessFn( Func fn, string reason = "", [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0, [CallerArgumentExpression("next")] string dbgExp = "" ) where U : T { return (U)ProcessWork( fn as Func, reason, dbgName, dbgPath, dbgLine, dbgExp ); } override public T Process( Func fn, string reason = "", [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0, [CallerArgumentExpression("fn")] string dbgExp = "" ) => ProcessWork( fn, reason, dbgName, dbgPath, dbgLine, dbgExp ); virtual public T ProcessWork( Func fn, string reason, string dbgName, string dbgPath, int dbgLine, string dbgExp ) { var orig = ( T )this; var next = fn( orig ); if( object.ReferenceEquals( orig, next) ) return next; var ret = next with { MetaStorage = Meta with { //Versioned Version = orig.Meta.Version + 1, Reason = !string.IsNullOrWhiteSpace( reason ) ? reason : next.Meta.Reason, //Recorded DbgName = dbgName, DbgPath = dbgPath, DbgLine = dbgLine, DbgExp = dbgExp, ZZOld = orig, //Timed TouchedAt = DateTime.Now, } }; if( OnChange != null) OnChange( orig, ret ); return ret; } }