using System;
namespace OpenNest.Undo
{
///
/// Records one undoable edit. Call once the nest is (about to be)
/// changed and when the edit succeeded; disposing then commits one
/// history entry. Disposing without (an exception, a cancellation)
/// rolls a changed nest back to the last recorded state. A scope opened while another is
/// open merges into the outermost one: its changes join that entry, and leaving it
/// incomplete rolls back the whole entry. A scope with nothing marked changed records and
/// restores nothing.
///
public sealed class ChangeScope : IDisposable
{
private readonly ChangeScope root;
private readonly Action end;
private bool completed;
private bool disposed;
private bool changed;
private bool failed;
private ChangeScope(string label, object coalesceKey, ChangeScope root, Action end)
{
Label = label;
CoalesceKey = coalesceKey;
this.root = root;
this.end = end;
}
/// A scope with no history behind it, for views that are not a nest document.
public static ChangeScope Detached() => new(null, null, null, null);
/// An outermost scope; runs once when it is disposed.
internal static ChangeScope Root(string label, object coalesceKey, Action end) =>
new(label, coalesceKey, null, end);
/// A scope inside ; it merges into the outermost one.
internal static ChangeScope Nested(ChangeScope outer) => new(outer.Label, null, outer.Outermost, null);
public string Label { get; }
/// Repeated edits with an equal key shortly after each other undo as one.
public object CoalesceKey { get; }
internal ChangeScope Outermost => root ?? this;
/// True once this scope or any scope merged into it marked the nest changed.
public bool IsChanged => Outermost.changed;
/// True when the outermost scope completed and no merged scope was left incomplete.
internal bool Succeeded => completed && !failed;
public void MarkChanged() => Outermost.changed = true;
public void Complete() => completed = true;
public void Dispose()
{
if (disposed)
return;
disposed = true;
if (root != null)
{
if (!completed)
root.failed = true;
return;
}
end?.Invoke(this);
}
}
}