package net.beadsproject.touch.perform;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;




public abstract class PerformNode {

	/**
	 * A node in a PerformTree. The node points to one Obect and has one parent and any number of children.
	 * The node can be told to connect or disconnect.
	 */
	
//	public Object object;
	protected PerformNode parent;
	List<PerformNode> children;
	List<Performer> connectedPerformers;	
	private float[] suggestedPosition;
	
	public static boolean verbose = false;
	
	/**
	 * Each perform node gets assigned a unique float in the range [0,1].
	 * This float has the property that the closer two nodes are related the closer their uniqueValues are.
	 * Changing the tree will disrupt this and it may need to be recalculated.
	 * 
	 * uniqueValueRange determines the range of this nodes children's unique values 
	 * 
	 * Todo: add some way to map continuous values to the tree
	 */
	float uniqueValue;
	float[] uniqueValueRange;	
	
	public PerformNode() {
//		this.object = object;
		parent = null;
		children = new ArrayList<PerformNode>();
		connectedPerformers = new ArrayList<Performer>();
		uniqueValue = -1;
	}
	
	public final void connectRecursive(int depth, Performer performer) {
		if(!connectedPerformers.contains(performer)) {
			if(parent != null) parent.connectRecursive(depth + 1, performer);
			doConnect(performer);
			connectedPerformers.add(performer);
		}
	}
	
	public final void connect(Performer performer) {
		if(!connectedPerformers.contains(performer)) {
			doConnect(performer);
			connectedPerformers.add(performer);
		}
	}
	
	public final void disconnectRecursive(int depth, List<PerformNode> stopList, Performer performer) {
		if(connectedPerformers.contains(performer) && !stopList.contains(this)) {
			doDisconnect(performer);
			connectedPerformers.remove(performer);
			if(parent != null) parent.disconnectRecursive(depth + 1, stopList, performer);
		}
	}

	public final void disconnectRecursive(int depth, Performer performer) {
		if(connectedPerformers.contains(performer)) {
			doDisconnect(performer);
			connectedPerformers.remove(performer);
			if(parent != null) parent.disconnectRecursive(depth + 1, performer);
		}
	}
	
	public final void disconnect(Performer performer) {
		if(connectedPerformers.contains(performer)) {
			doDisconnect(performer);
			connectedPerformers.remove(performer);
		}
	}
	
	public void printState(int depth, String state) {
		for(int i = 0; i < depth ; i++) {
			System.out.print("-");
		}
		System.out.println(state);
	}
	
	public List<PerformNode> getListToRoot() {
		List<PerformNode> listToRoot = new ArrayList<PerformNode>();
		addToListToRoot(listToRoot);
		return listToRoot;
	}
	
	public PerformNode getRoot(){
		if (parent==null) return this;
		else return parent.getRoot();
	}
	
	public PerformNode getParent()
	{
		return parent;
	}
	
	public int getDepth()
	{
		int maxd = 0;
		for(PerformNode pn: children)
		{
			maxd = Math.max(pn.getDepth(),maxd);
		}
		return maxd + 1;		
	}
	
	public List<PerformNode> getSiblings()
	{
		if (parent!=null)
		{
			List<PerformNode> sibs = new LinkedList<PerformNode>();
			
			for(PerformNode ch: parent.getChildren())
			{
				if (ch!=this)
				{
					sibs.add(ch);					
				}
			}
			
			if (sibs.isEmpty())
				return null;
			else 
				return sibs;
		}
		
		return null;
	}
	
	private void addToListToRoot(List<PerformNode> listToRoot) {
		listToRoot.add(this);
		if(parent != null) parent.addToListToRoot(listToRoot);
	}


	public abstract void doConnect(Performer performer);
	public abstract void doDisconnect(Performer performer);

	public void addChild(PerformNode child) {
		child.parent = this;
		this.children.add(child);
	}
	
	public List<PerformNode> getChildren()
	{
		return children;
	}
	
	public void removeChild(PerformNode child) {
		child.parent = null;
		this.children.remove(child);
	}
	
	public float[] getSuggestedPosition() {
		return suggestedPosition;
	}
	
	public void setSuggestedPosition(float x, float y) {
		suggestedPosition = new float[2];
		suggestedPosition[0] = x;
		suggestedPosition[1] = y;
	}
	
	/**
	 * Compute the distance between two nodes.
	 * Two sibling nodes are at distance 0.
	 * A parent node is at distance 1.
	 * Anything else is computed recursively.
	 * 
	 * @param a
	 * @param b
	 * @return
	 */
	public static int getTreeDistance(PerformNode a, PerformNode b)
	{
		if (a==b) return 0;
		// locate common ancestor
		ArrayList<PerformNode> aToRoot = (ArrayList<PerformNode>)a.getListToRoot();
		ArrayList<PerformNode> bToRoot = (ArrayList<PerformNode>)b.getListToRoot();
		
		// step through each of these to find the first uncommon node
		int i = 0;
		int m = Math.min(aToRoot.size(),bToRoot.size());
		
		for(;i<m;i++)
		{
			if (aToRoot.get(aToRoot.size()-1-i)!=bToRoot.get(bToRoot.size()-1-i)) break;
		}
		
		//System.out.printf("[%f %f]\n", a.uniqueValue(), b.uniqueValue());		
		
		return Math.max((aToRoot.size()-1-i),(bToRoot.size()-1-i) + 1);
	}
	
	/*
	// old tree distance version
	public static int getTreeDistance(PerformNode a, PerformNode b)
	{
		if (a==b) return 0;
		// locate common ancestor
		ArrayList<PerformNode> aToRoot = (ArrayList<PerformNode>)a.getListToRoot();
		ArrayList<PerformNode> bToRoot = (ArrayList<PerformNode>)b.getListToRoot();
		
		// step through each of these to find the first uncommon node
		int i = 0;
		int m = Math.min(aToRoot.size(),bToRoot.size());
		
		for(;i<m;i++)
		{
			if (aToRoot.get(aToRoot.size()-1-i)!=bToRoot.get(bToRoot.size()-1-i)) break;
		}
		
		//System.out.printf("[%f %f]\n", a.uniqueValue(), b.uniqueValue());		
		
		return (aToRoot.size()-1-i) + (bToRoot.size()-1-i) + 1;
	}
	*/
	
	public String toString(int depth) 
	{
		String s = new String();
		String spaces = new String();
		for(int i=0;i<(depth+1);i++)
			spaces += "     ";
		s += String.format("%.2f ",uniqueValue());
		for(PerformNode c: children)
		{
			s += c.toString(depth+1);
			if (children.size()>1)
				s += spaces;
		}
		if (!s.endsWith("\n"))
			s += "\n";
		return s;
	}
	
	public float uniqueValue()
	{
		return uniqueValue;
	}
	
	public int totalNumDescendents()
	{
		int count = children.size();
		for(PerformNode child: children)
			count += child.totalNumDescendents();
		return count;
	}
	
	/**
	 * Recursively assign unique values to the perform tree.
	 * @param min
	 * @param max
	 */
	public void computeUniqueValue(float min, float max)
	{
		uniqueValueRange = new float[]{min,max};
		// for each child, split the range up, and assign self a unique value
		int numChildren = children.size();
		if (numChildren>0)
		{
			int tnd = totalNumDescendents();
			float mypiece = (max-min)/(tnd+1);
			uniqueValue = max-mypiece/2.0f;
			max -= mypiece;			
			
			// divide the range up into equal parts
			float dv = (max-min)/(numChildren+1);
			int i = 0;
			for(PerformNode c: children)
			{
				c.computeUniqueValue(min+i*dv, min+(i+1)*dv);
				i++;
			}
			
		}
		else
			uniqueValue = (max+min)/2;
	}
}
