import 'package:quill_delta/quill_delta.dart';
import 'package:tuple/tuple.dart';

import 'document.dart';

class History {
  final HistoryStack stack = HistoryStack.empty();

  /// used for disable redo or undo function
  bool ignoreChange;

  int lastRecorded;

  /// Collaborative editing's conditions should be true
  final bool userOnly;

  ///max operation count for undo
  final int maxStack;

  ///record delay
  final int interval;

  History(
      {this.ignoreChange = false,
      this.interval = 400,
      this.maxStack = 100,
      this.userOnly = false,
      this.lastRecorded = 0});

  void handleDocChange(Tuple3<Delta, Delta, ChangeSource> change) {
    if (ignoreChange) return;
    if (!userOnly || change.item3 == ChangeSource.LOCAL) {
      record(change.item2, change.item1);
    } else {
      transform(change.item2);
    }
  }

  void clear() {
    stack.clear();
  }

  void record(Delta change, Delta before) {
    if (change.isEmpty) return;
    stack.redo.clear();
    Delta undoDelta = change.invert(before);
    final timeStamp = DateTime.now().millisecondsSinceEpoch;

    if (lastRecorded + interval > timeStamp && stack.undo.isNotEmpty) {
      final lastDelta = stack.undo.removeLast();
      undoDelta = undoDelta.compose(lastDelta);
    } else {
      lastRecorded = timeStamp;
    }

    if (undoDelta.isEmpty) return;
    stack.undo.add(undoDelta);

    if (stack.undo.length > maxStack) {
      stack.undo.removeAt(0);
    }
  }

  ///
  ///It will override pre local undo delta,replaced by remote change
  ///
  void transform(Delta delta) {
    transformStack(this.stack.undo, delta);
    transformStack(this.stack.redo, delta);
  }

  void transformStack(List<Delta> stack, Delta delta) {
    for (int i = stack.length - 1; i >= 0; i -= 1) {
      final oldDelta = stack[i];
      stack[i] = delta.transform(oldDelta, true);
      delta = oldDelta.transform(delta, false);
      if (stack[i].length == 0) {
        stack.removeAt(i);
      }
    }
  }

  void _change(Document doc, List<Delta> source, List<Delta> dest) {
    if (source.length == 0) return;
    Delta delta = source.removeLast();
    Delta base = doc.toDelta();
    Delta inverseDelta = delta.invert(base);
    dest.add(inverseDelta);
    this.lastRecorded = 0;
    this.ignoreChange = true;
    doc.compose(delta, ChangeSource.LOCAL, history: true);
    this.ignoreChange = false;
  }

  void undo(Document doc) {
    _change(doc, stack.undo, stack.redo);
  }

  void redo(Document doc) {
    _change(doc, stack.redo, stack.undo);
  }
}

class HistoryStack {
  final List<Delta> undo;
  final List<Delta> redo;

  HistoryStack.empty()
      : undo = [],
        redo = [];

  void clear() {
    undo.clear();
    redo.clear();
  }
}