using System;
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;

public static void Example()
{
    // Subjects and functions for performing undos and redos
    Subject<Unit> undoSubject = new Subject<Unit>();
    Subject<Unit> redoSubject = new Subject<Unit>();
    Action undo = () => undoSubject.OnNext(Unit.Default);
    Action redo = () => redoSubject.OnNext(Unit.Default);
    
    // The undo recorder
    ReactiveUndoRedoRecorder undoRedo = 
        new ReactiveUndoRedoRecorder(undoSubject, redoSubject);
    
    // Data sources
    Subject<string> strSubject0 = new Subject<string>();
    Subject<string> strSubject1 = new Subject<string>();
    Subject<string> strSubject2 = new Subject<string>();
    Action<string> pushSrc0 = strSubject0.OnNext;
    Action<string> pushSrc1 = strSubject1.OnNext;
    Action<string> pushSrc2 = strSubject2.OnNext;
    
    /*
        The ReactiveUndoRedoRecorder.Record() method simply records
        the history of the given observable. When undone/redone,
        that history is played back through the downstream sequence
        to the observer.
    */
    
    // Embed Undo/Redo in streams, and print output to console
    IDisposable subscriptions = new CompositeDisposable(
        new[] {strSubject0, strSubject1}
            .Select(
                // For each source
                (source, i) =>
                    // Record it and print values
                    source.Record("init", undoRedo)
                        .Select(s => string.Format("{0}: {1}", i, s))
                        .Subscribe(Console.WriteLine)));
    
    // Simulate user interaction
    pushSrc0("a");
    pushSrc1("b");
    undo(); //Undo "b" on 1
    redo(); //Redo "b" on 1
    pushSrc0("d");
    redo(); //Does nothing, redo stack has been wiped-out
    undo(); //Undo "d" on 0
    undo(); //Undo "b" on 1
    undo(); //Undo "a" on 0
    undo(); //Does nothing, undo stack has been wiped-out
    
    // Console output:
    /*
        0: a
        1: b
        1: init
        1: b
        0: d
        0: a
        1: init
        0: init
    */
    
    /* 
        The ReactiveUndoRedoRecorder.RecordScan() method acts
        similarly to the built-in Observable.Scan() method for
        observable sequences. The recorder will record all
        intermediate states as they are passed through the
        output sequence so that they can be undone/redone.
        Performing an undo or redo will not only play back 
        those intermediate states, but will also reset the
        internal state of the scan operation to be the replayed
        value.
    */
    
    IDisposable recordScanSub =
        strSubject2.Select(int.Parse)
            .RecordScan(
                (state, i) => state + i), 
                0, 
                undoRedo)
            .Subscribe(Console.WriteLine);
            
    pushSrc2("1"); //0+1 = 1
    pushSrc2("1"); //1+1 = 2
    pushSrc2("2"); //2+2 = 4
    undo();        //Reset to 2
    pushSrc2("1"); //2+1 = 3
    
    // Console output:
    /*
        1
        2
        4
        2
        3
    */
}