package de.dhbwstuttgart.typeinference.unify.model;

import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * A node of a directed graph.
 * @author Florian Steurer
 *
 * @param <T> The type of the content of the node.
 */
class Node<T> {
	
	/**
	 * The content of the node.
	 */
	private T content;
	
	/**
	 * The set of predecessors
	 */
	private HashSet<Node<T>> predecessors = new HashSet<>();
	
	/**
	 * The set of descendants
	 */
	private HashSet<Node<T>> descendants = new HashSet<>();
	
	/**
	 * Creates a node containing the specified content.
	 */
	public Node(T content) {
		this.content = content;
	}
	
	/**
	 * Adds a directed edge from this node to the descendant (this -> descendant)
	 */
	public void addDescendant(Node<T> descendant) {
		if(descendants.contains(descendant))
			return;
		
		descendants.add(descendant);
		descendant.addPredecessor(this);
	}
	
	/**
	 * Adds some directed edges from this node to the descendant (this -> descendant)
	 */
	public void addAllDescendant(Set<Node<T>> allDescendants) {
		for(Node<T> descendant: allDescendants) {
			addDescendant(descendant);
		}
	}

	/**
	 * Adds a directed edge from the predecessor to this node (predecessor -> this)
	 */
	public void addPredecessor(Node<T> predecessor) {
		if(predecessors.contains(predecessor))
			return;
		
		predecessors.add(predecessor);
		predecessor.addDescendant(this);
	}
	
	/**
	 * Adds some directed edges from the predecessor to this node (predecessor -> this)
	 */
	public void addAllPredecessor(Set<Node<T>> allPredecessors) {
		for(Node<T> predecessor: allPredecessors) {
			addPredecessor(predecessor);
		}
	}

	/**
	 * The content of this node.
	 */
	public T getContent() {
		return content;
	}
	
	/**
	 * Returns all predecessors (nodes that have a directed edge to this node)
	 */
	public Set<Node<T>> getPredecessors() {
		return predecessors;
	}
	
	/**
	 * Returns all descendants. All nodes M, where there is a edge from this node to the node M.
	 * @return
	 */
	public Set<Node<T>> getDescendants() {
		return descendants;
	}
	
	/**
	 * Retrieves the content of all descendants.
	 */
	public Set<T> getContentOfDescendants() {
		return descendants.stream().map(x -> x.getContent()).collect(Collectors.toSet());
	}
	
	/**
	 * Retrieves the content of all predecessors.
	 */
	public Set<T> getContentOfPredecessors() {
		return predecessors.stream().map(x -> x.getContent()).collect(Collectors.toSet());
	}
	
	@Override
	public String toString() {
		return "Elem: Node(" + content.toString() + ")\nPrec: " + getContentOfPredecessors().toString() 
				+ "\nDesc: " + getContentOfDescendants().toString() + "\n\n";
	}
}
