import 'dart:collection';

import 'package:flutter_quill/models/documents/style.dart';
import 'package:quill_delta/quill_delta.dart';

import '../attribute.dart';
import 'container.dart';
import 'line.dart';

/* node in a document tree */
abstract class Node extends LinkedListEntry<Node> {
  Container parent;
  Style _style = Style();

  Style get style => _style;

  void applyAttribute(Attribute attribute) {
    _style = _style.merge(attribute);
  }

  void applyStyle(Style value) {
    if (value == null) {
      throw ArgumentError('null value');
    }
    _style = _style.mergeAll(value);
  }

  void clearStyle() {
    _style = Style();
  }

  bool get isFirst => list.first == this;

  bool get isLast => list.last == this;

  int get length;

  Node clone() {
    Node node = newInstance();
    node.applyStyle(style);
    return node;
  }

  int getOffset() {
    int offset = 0;

    if (isFirst) {
      return offset;
    }

    Node cur = this;
    do {
      cur = cur.previous;
      offset += cur.length;
    } while (!cur.isFirst);
    return offset;
  }

  int getDocumentOffset() {
    final parentOffset = (parent is! Root) ? parent.getDocumentOffset() : 0;
    return parentOffset + getOffset();
  }

  bool containsOffset(int offset) {
    final o = getDocumentOffset();
    return o <= offset && offset < o + length;
  }

  @override
  void insertBefore(Node entry) {
    assert(entry.parent == null && parent != null);
    entry.parent = parent;
    super.insertBefore(entry);
  }

  @override
  void insertAfter(Node entry) {
    assert(entry.parent == null && parent != null);
    entry.parent = parent;
    super.insertAfter(entry);
  }

  @override
  void unlink() {
    assert(parent != null);
    parent = null;
    super.unlink();
  }

  adjust() {
    // do nothing
  }

  /// abstract methods begin

  Node newInstance();

  String toPlainText();

  Delta toDelta();

  insert(int index, Object data, Style style);

  retain(int index, int len, Style style);

  delete(int index, int len);

  /// abstract methods end

}

/* Root node of document tree */
class Root extends Container<Container<Node>> {
  @override
  Container<Node> get defaultChild => Line();

  @override
  Delta toDelta() => children
      .map((child) => child.toDelta())
      .fold(Delta(), (a, b) => a.concat(b));

  @override
  Node newInstance() {
    return Root();
  }
}