package undo.impl;

import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Consumer;

import javafx.beans.binding.BooleanBinding;
import javafx.beans.value.ObservableBooleanValue;
import reactfx.Source;
import reactfx.Subscription;
import undo.UndoManager;

public class UndoManagerImpl<C> implements UndoManager {

    private final ChangeQueue<C> queue = new UnlimitedChangeQueue<>();
    private final Consumer<C> apply;
    private final Consumer<C> undo;
    private final BiFunction<C, C, Optional<C>> merge;
    private final Subscription subscription;

    private final BooleanBinding undoAvailable = new BooleanBinding() {
        @Override
        protected boolean computeValue() {
            return isUndoAvailable();
        }
    };

    private final BooleanBinding redoAvailable = new BooleanBinding() {
        @Override
        protected boolean computeValue() {
            return isRedoAvailable();
        }
    };

    boolean canMerge;

    private boolean ignoreChanges;

    public UndoManagerImpl(Consumer<C> apply, Consumer<C> undo, BiFunction<C, C, Optional<C>> merge, Source<C> changeSource) {
        this.apply = apply;
        this.undo = undo;
        this.merge = merge;
        subscription = changeSource.subscribe(this::changeObserved);
    }

    @Override
    public void close() {
        subscription.unsubscribe();
    }

    @Override
    public boolean undo() {
        ignoreChanges = true;
        boolean undone = false;
        if(isUndoAvailable()) {
            undo.accept(queue.prev());
            canMerge = false;
            undoAvailable.invalidate();
            redoAvailable.invalidate();
            undone = true;
        }
        ignoreChanges = false;
        return undone;
    }

    @Override
    public boolean redo() {
        ignoreChanges = true;
        boolean redone = false;
        if(isRedoAvailable()) {
            apply.accept(queue.next());
            canMerge = false;
            undoAvailable.invalidate();
            redoAvailable.invalidate();
            redone = true;
        }
        ignoreChanges = false;
        return redone;
    }

    @Override
    public boolean isUndoAvailable() {
        return queue.hasPrev();
    }

    @Override
    public ObservableBooleanValue undoAvailableProperty() {
        return undoAvailable;
    }

    @Override
    public boolean isRedoAvailable() {
        return queue.hasNext();
    }

    @Override
    public ObservableBooleanValue redoAvailableProperty() {
        return redoAvailable;
    }

    @Override
    public void preventMerge() {
        canMerge = false;
    }

    private void changeObserved(C change) {
        if(!ignoreChanges) {
            addChange(change);
        }
    }

    @SuppressWarnings("unchecked")
    private void addChange(C change) {
        if(canMerge && queue.hasPrev()) {
            C prev = queue.prev();
            queue.push(merge(prev, change));
        } else {
            queue.push(change);
        }
        canMerge = true;
        undoAvailable.invalidate();
        redoAvailable.invalidate();
    }

    @SuppressWarnings("unchecked")
    private C[] merge(C c1, C c2) {
        Optional<C> merged = merge.apply(c1, c2);
        if(merged.isPresent()) {
            return (C[]) new Object[] { merged.get() };
        } else {
            return (C[]) new Object[] { c1, c2 };
        }
    }
}

@@ -13,16 +13,74 @@
 .impl;%0A%0A
+import inhibeans.Hold;%0Aimport inhibeans.value.Indicator;%0A%0A
 import j
@@ -1065,29 +1065,55 @@
 ate 
-boolean ignoreChanges
+final Indicator ignoreChanges = new Indicator()
 ;%0A%0A 
@@ -1534,93 +1534,82 @@
    i
-gnoreChanges = true;%0A        boolean undone = false;%0A        if(isUndoAvailable
+f(isUndoAvailable()) %7B%0A            try(Hold h = ignoreChanges.on
 ()) %7B%0A
+    
     
@@ -1639,24 +1639,38 @@
 ue.prev());%0A
+            %7D%0A
             
@@ -1783,16 +1783,14 @@
     
-undone =
+return
  tru
@@ -1793,32 +1793,39 @@
  true;%0A        %7D
+ else %7B
 %0A        ignoreC
@@ -1813,39 +1813,34 @@
 e %7B%0A        
-ignoreChanges =
+    return
  false;%0A    
@@ -1847,22 +1847,9 @@
     
-return undone;
+%7D
 %0A   
@@ -1907,93 +1907,82 @@
    i
-gnoreChanges = true;%0A        boolean redone = false;%0A        if(isRedoAvailable
+f(isRedoAvailable()) %7B%0A            try(Hold h = ignoreChanges.on
 ()) %7B%0A
+    
     
@@ -2013,24 +2013,38 @@
 ue.next());%0A
+            %7D%0A
             
@@ -2159,14 +2159,12 @@
   re
-done =
+turn
  tru
@@ -2167,32 +2167,39 @@
  true;%0A        %7D
+ else %7B
 %0A        ignoreC
@@ -2191,31 +2191,26 @@
         
-ignoreChanges =
+    return
  false;%0A
@@ -2221,22 +2221,9 @@
     
-return redone;
+%7D
 %0A   
@@ -2781,16 +2781,23 @@
 eChanges
+.isOn()
 ) %7B%0A    

Use Indicator for ignoreChanges in UndoManagerImpl