package com.jscape.util.collections;

import java.util.*;

/**
 * @author Alex Usun
 */
public class Tree<T> {

    private final T value;
    private Tree<T> parent;
    private final List<Tree<T>> leafs;
    private final Map<T, Tree<T>> trees;

    public static <T> Tree<T> treeFor(T value) {
        return new Tree<T>(value, null, new LinkedList<Tree<T>>(), new HashMap<T, Tree<T>>());
    }

    private Tree(T value, Tree<T> parent, List<Tree<T>> leafs, Map<T, Tree<T>> trees) {
        this.value = value;
        this.parent = parent;
        this.leafs = leafs;
        this.trees = trees;

        this.trees.put(this.value, this);
    }

    public T value() {
        return this.value;
    }

    public Tree<T> parent() {
        return this.parent;
    }

    public Tree<T> parent(T element) {
        Tree<T> node = newTree(element, null);
        node.leafs.add(this);
        this.parent = node;

        return node;
    }

    public Collection<Tree<T>> leafs() {
        return this.leafs;
    }

    public Tree<T> treeFrom(T element) {
        return this.trees.get(element);
    }

    public Tree<T> addLeaf(T value) {
        Tree<T> tree = newTree(value, this);
        this.leafs.add(tree);

        return tree;
    }

    public void addLeaf(T root, T leaf) {
        if (this.trees.containsKey(root)) {
            this.trees.get(root).addLeaf(leaf);
        } else {
            addLeaf(root).addLeaf(leaf);
        }
    }

    public Collection<T> successorsOf(T element) {
        Tree<T> tree = treeFrom(element);

        if (tree == null) {
            return Collections.emptyList();
        }

        Collection<T> values = new ArrayList<T>(tree.leafs.size());
        for (Tree<T> leaf : tree.leafs) {
            values.add(leaf.value);
        }

        return values;

    }

    @Override
    public String toString() {
        return "Tree {"
                + "value=" + this.value
                + ", leafs=" + this.leafs
                + '}';
    }

    protected Tree<T> newTree(T value, Tree<T> parent) {
        return new Tree<T>(value, parent, new LinkedList<Tree<T>>(), this.trees);
    }

}
