import * as plugins from './classes.plugins.js';

export class Tree<T> {
  symbolTree: any;
  constructor() {
    this.symbolTree = new plugins.symbolTree();
  }

  // =======================================
  // Functions that map to the functionality of symbol-tree
  // =======================================

  /**
   *
   * @param objectArg
   */
  initialize(objectArg: T): T {
    return this.symbolTree.initialize(objectArg);
  }

  hasChildren(objectArg: T): boolean {
    return this.symbolTree.hasChildren(objectArg);
  }

  firstChild(objectArg: T): T {
    return this.symbolTree.firstChild(objectArg);
  }

  lastChild(objectArg: T): T {
    return this.symbolTree.lastChild(objectArg);
  }

  previousSibling(objectArg: T): T {
    return this.symbolTree.previousSibling(objectArg);
  }

  nextSibling(objectArg: T): T {
    return this.symbolTree.nextSibling(objectArg);
  }

  parent(objectArg: T): T {
    return this.symbolTree.parent(objectArg);
  }

  lastInclusiveDescendant(objectArg: T): T {
    return this.symbolTree.lastInclusiveDescendant(objectArg);
  }

  preceding(objectArg: T, optionsArg?: any): T {
    return this.symbolTree.preceding(objectArg, optionsArg);
  }

  following(object: T, optionsArg: any) {
    return this.symbolTree.following(object, optionsArg);
  }

  childrenToArray(parentArg: T, optionsArg: any): T[] {
    return this.symbolTree.childrenToArray(parentArg, optionsArg);
  }

  ancestorsToArray(objectArg: T, optionsArg: any): T[] {
    return this.symbolTree.ancestorsToArray(objectArg, optionsArg);
  }

  treeToArray(rootArg: T, optionsArg: any): T[] {
    return this.symbolTree.treeToArray(rootArg, optionsArg);
  }

  childrenIterator(parentArg: T, optionsArg: any): T {
    return this.symbolTree.childrenIterator(parentArg, optionsArg);
  }

  previousSiblingsIterator(objectArg: T): T {
    return this.symbolTree.previousSiblingsIterator(objectArg);
  }

  nextSiblingsIterator(objectArg: T) {
    return this.symbolTree.nextSiblingsIterator(objectArg);
  }

  ancestorsIterator(objectArg: T): Iterable<T> {
    return this.symbolTree.ancestorsIterator(objectArg);
  }

  treeIterator(rootArg: T, optionsArg?: any): Iterable<T> {
    return this.symbolTree.treeIterator(rootArg, optionsArg);
  }

  index(childArg: T): number {
    return this.symbolTree.index(childArg);
  }

  childrenCount(parentArg: T): number {
    return this.symbolTree.childrenCount(parentArg);
  }

  compareTreePosition(leftArg: T, rightArg: T): number {
    return this.symbolTree.compareTreePosition(leftArg, rightArg);
  }

  remove(removeObjectArg: T): T {
    return this.symbolTree.remove(removeObjectArg);
  }

  insertBefore(referenceObjectArg: T, newObjectArg: T): T {
    return this.symbolTree.insertBefore(referenceObjectArg, newObjectArg);
  }

  insertAfter(referenceObject: T, newObjectArg: T) {
    return this.symbolTree.insertAfter(referenceObject, newObjectArg);
  }

  prependChild(referenceObjectArg: T, newObjectArg: T): T {
    return this.symbolTree.prependChild(referenceObjectArg, newObjectArg);
  }

  appendChild(referenceObjectArg: T, newObjectArg: T) {
    return this.symbolTree.appendChild(referenceObjectArg, newObjectArg);
  }

  // ===========================================
  // Functionality that extends symbol-tree
  // ===========================================

  /**
   * returns a branch of the tree as a recursive JSON structure
   */
  toJsonWithHierachy(rootElement: T): ITreeNode<T> {
    const buildNode = (element: T): ITreeNode<T> => {
      const children: ITreeNode<T>[] = [];
      if (this.hasChildren(element)) {
        const childrenArray = this.childrenToArray(element, {});
        for (const child of childrenArray) {
          children.push(buildNode(child));
        }
      }
      return { data: element, children };
    };
    return buildNode(rootElement);
  }

  /**
   * builds a tree from a recursive JSON structure
   * @param jsonRoot the root node in ITreeNode format
   * @param reviver optional function to reconstruct T from serialized data
   */
  fromJsonWithHierachy(jsonRoot: ITreeNode<T>, reviver?: (data: any) => T): T {
    const buildTree = (node: ITreeNode<T>, parentElement?: T): T => {
      const element = reviver ? reviver(node.data) : node.data;
      this.initialize(element);
      if (parentElement) {
        this.appendChild(parentElement, element);
      }
      for (const childNode of node.children) {
        buildTree(childNode, element);
      }
      return element;
    };
    return buildTree(jsonRoot);
  }
}

export interface ITreeNode<T> {
  data: T;
  children: ITreeNode<T>[];
}
