package com.github.mproberts.rxtools.list;

import difflib.Delta;
import difflib.DiffAlgorithm;
import difflib.Patch;
import difflib.myers.MyersDiff;
import io.reactivex.*;
import io.reactivex.functions.BiFunction;
import io.reactivex.functions.Function;

import java.util.ArrayList;
import java.util.List;

class DifferentialFlowableList<T> extends FlowableList<T>
{
    private final Flowable<Update<T>> _diffTransform;
    private List<T> _previousList;
    private DiffAlgorithm<T> _diffAlgorithm;

    private List<Change> computeDiff(List<T> before, List<T> after)
    {
        Patch<T> patch = _diffAlgorithm.diff(before, after);
        List<Delta<T>> deltas = patch.getDeltas();
        List<Change> changes = new ArrayList<>(deltas.size());

        for (Delta<T> delta : deltas) {
            switch (delta.getType()) {
                case CHANGE:
                    changes.add(Change.reloaded());
                    break;
                case DELETE:
                    changes.add(Change.removed(delta.getOriginal().getPosition()));
                    break;
                case INSERT:
                    changes.add(Change.inserted(delta.getRevised().getPosition()));
                    break;
            }
        }

        return changes;
    }

    DifferentialFlowableList(Flowable<List<T>> list)
    {
        _diffAlgorithm = new MyersDiff<>();

        _diffTransform = list
                .map(new Function<List<T>, Update<T>>() {
                    @Override
                    public Update<T> apply(List<T> ts) {
                        return new Update<>(ts, Change.reloaded());
                    }
                })
                .scan(new BiFunction<Update<T>, Update<T>, Update<T>>() {
                    @Override
                    public Update<T> apply(Update<T> previous, Update<T> next) {
                        if (previous == null) {
                            return next;
                        }

                        List<Change> changes = computeDiff(previous.list, next.list);

                        _previousList = next.list;

                        return new Update<>(next.list, changes);
                    }
                });
    }

    @Override
    public Flowable<Update<T>> updates()
    {
        return _diffTransform
                .startWith(Flowable.create(new FlowableOnSubscribe<Update<T>>() {
                    @Override
                    public void subscribe(FlowableEmitter<Update<T>> updateEmitter) throws Exception {
                        if (_previousList != null) {
                            Update<T> update = new Update<>(new ArrayList<>(_previousList), Change.reloaded());
                            updateEmitter.onNext(update);
                        }

                        updateEmitter.onComplete();
                    }
                }, BackpressureStrategy.LATEST));
    }
}

@@ -37,16 +37,38 @@
 .list;%0A%0A
+import difflib.Chunk;%0A
 import d
@@ -875,16 +875,18 @@
  CHANGE:
+ %7B
 %0A       
@@ -902,179 +902,669 @@
     
-changes.add(Change.reloaded());%0A                    break;%0A                case DELETE:%0A                    changes.add(Change.removed(delta.getOriginal().getPosition()));
+Chunk%3CT%3E original = delta.getOriginal();%0A                    List%3CT%3E originalLines = original.getLines();%0A                    int originalPosition = original.getPosition();%0A%0A                    Chunk%3CT%3E revised = delta.getRevised();%0A                    List%3CT%3E lines = revised.getLines();%0A                    int position = revised.getPosition();%0A%0A                    for (int i = 0; i %3C originalLines.size(); ++i) %7B%0A                        changes.add(Change.removed(originalPosition));%0A                    %7D%0A%0A                    for (int i = 0; i %3C lines.size(); ++i) %7B%0A                        changes.add(Change.inserted(position + i));%0A                    %7D
 %0A   
@@ -1579,32 +1579,50 @@
          break;%0A
+                %7D%0A
                 
@@ -1633,16 +1633,18 @@
  INSERT:
+ %7B
 %0A       
@@ -1660,98 +1660,757 @@
     
-changes.add(Change.inserted(delta.getRevised().getPosition()));%0A                    break;
+Chunk%3CT%3E revised = delta.getRevised();%0A                    List%3CT%3E lines = revised.getLines();%0A                    int position = revised.getPosition();%0A%0A                    for (int i = 0; i %3C lines.size(); ++i) %7B%0A                        changes.add(Change.inserted(position + i));%0A                    %7D%0A                    break;%0A                %7D%0A                case DELETE: %7B%0A                    Chunk%3CT%3E original = delta.getOriginal();%0A                    List%3CT%3E lines = original.getLines();%0A                    int position = original.getPosition();%0A%0A                    for (int i = 0; i %3C lines.size(); ++i) %7B%0A                        changes.add(Change.removed(position));%0A                    %7D%0A                    break;%0A                %7D
 %0A   

Switch change implementation to stack insertions and deletions.