/*
#************************************************************
#************************************************************
#
# Date:   8/15/08                Coded by:  Eddie Salinas
# Version: 1.0				     Module name:  PhyloNode.java
# Filename: PhyloNode.java   Source file:  PhyloNode.java
# Program description:
The purpose of this file is to take a newick string and make a
plain graph out of the string.   Here, graph is meant in the sense
of nodes being connected by vertices  see http://en.wikipedia.org/wiki/Graph_(mathematics)
Essentialy, the constructor is given a newick string and a graph is created.
Then, once a graph is created the graph is turned into a cluster 
by way of the PhyloCluster class.  It is the clusters that are 
ultimately drawn by the tree.  The PhyloNode is a sort of temporary
data structure.  Newick String -> PhyloNode graph (graph as in the sense above) -> PhyloCluster Graph (more like a binary tree) -> drawn on screen
#************************************************************
# Libraries and software support:
java version "1.5.0_11"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_11-b03)
BEA JRockit(R) (build R27.3.1-1_CR336511-87524-1.5.0_11-20070817-1356-linux-x86_64, compiled mode)
#************************************************************
# Development environment:
java version "1.5.0_11"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_11-b03)
BEA JRockit(R) (build R27.3.1-1_CR336511-87524-1.5.0_11-20070817-1356-linux-x86_64, compiled mode)

#************************************************************
# System requirements:
java version "1.5.0_11"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_11-b03)
BEA JRockit(R) (build R27.3.1-1_CR336511-87524-1.5.0_11-20070817-1356-linux-x86_64, compiled mode)
#************************************************************
# Start date:
# Update history:
#            DATE            MODIFICATION
#
#************************************************************
# Test history:
#  TEST PROTOCOL           DATE       TEST RESULTS
#
#************************************************************
# Programmer comments:
see purpose above
#************************************************************
*/

import javax.swing.*;
import pal.tree.*;
import java.io.*;
import java.net.*;
import java.util.*;
import pal.misc.*;
import pal.distance.*;
import java.awt.*;


/**
 * When a phylogenetic tree is being built, it is composed of a set
 * of PhyloNode objects with each node in the tree corresponding to
 * an instance of this class and each node having references
 * to each neighboring PhyloNode;  This is an undirected graph
 * with distances between nodes as weights and each node an 
 * instance of this class.
Newick String -> PhyloNode graph (graph as in the sense above) -> PhyloCluster Graph (more like a binary tree) -> drawn on screen
 */
public class PhyloNode
	{

	/**
	*Holds the name of the Phylonode.
	*/
	private String name=null;

	/**
	*Returns the name of the PhyloNode
	*@return the name of the PhyloNode
	*/
	public String getName()
		{
		return new String(name);
		}


	/**
	*Holds distances to corresponding neighbors.
	*/
	double[] distances=null;
	/**
	*A list of refernces to neigbhors.
	*/
	PhyloNode[] neighbors=null;
	/**
	*This node's color
	*/
	Color nodeColor=Color.BLACK;


	public PhyloNode[] getNeighbors()
		{
		return neighbors;
		}



	/**
	*Given a reference to a PhyloNode entry-point to a graph representing an unrooted phylogenetic tree, this method
	*returns a newick string representing the tree.
	*@param source a PhyloNode used an an entrypoint to a graph representing a phylogenetic tree
	*@return a newick string of the graph which the argument is an entrypoint into.
	*/
	public String makeNewickString(PhyloNode source)
		{

		if(neighbors==null)
			{
			return new String("("+name+":0.0);");
			}
		else if(neighbors.length==0)
			{
			return new String("("+name+":0.0);");
			}
		else
			{

			/*
			For each node in the neighbors list,
			print its tree and the distance to its tree.
			If the node is terminal print any of its 
			surrounding parentheses (if needed)
			and also the distance to it.
			*/
			String s=new String("(");
			for(int n=0;n<neighbors.length;n++)
				{
				if(neighbors[n]!=source)
					{
					if(isTerminalFrom(neighbors[n]))
						{
						if(n<=neighbors.length-1 && n>0)
							{
							s=s+",";
							}							
						s=s+neighbors[n].name+":"+neighbors[n].distances[0];
						}//is a terminal node from 
					else
						{						
						s=s+"(";
						s=s+neighbors[n].makeNewickString(this);
						s=s+"):"+distances[n];
						}//is a nonterminal node from					
					if(n==neighbors.length-1)
						{
						s=s+")";
						}
					}
				}
			if(!isANeighbor(source))
				{
				s=s+";";
				}
			return s;
			}//else
		}


	/**
	*Given a reference to a node, return true if and only if they're connected
	*and if this node is not connected to any other nodes.
	*@param p the node in question.
	*@return true if and only if the condition specified above is true.
	*/
	public boolean isTerminalFrom(PhyloNode p)
		{
		if(neighbors[0]==p && neighbors.length==1)
			{
			return true;
			}
		else
			{
			return false;
			}
		}



	/**
	*Create a new phyloNode with no neihbors.
	*@param iName the name of the new PhyloNode
	*/
	public PhyloNode(String iName)
		{
		name=new String(iName);
		}


	/**
	*Set she color of the node and recursively do the 
	*same to all neighbors.
	*@param source when called recursively should be 'this' to avoid
	*stack overflow.  Otherwise, the initial call should be null.
	*@param c the color to apply.
	*/
	public void setColorRec(PhyloNode source, Color c)
		{
		nodeColor=c;
		if(neighbors!=null)
			{
			for(int n=0;n<neighbors.length;n++)
				{
				if(neighbors[n]!=source)
					{
					neighbors[n].setColorRec(this,c);
					}
				}
			}
		}


	/**
	*Set the color of the node
	*@param c the new color of the node.
	*/
	public void setColor(Color c)
		{
		nodeColor=c;
		}


	/**
	*Set the name of the node.
	*@param nn the new name of the node
	*/
	public void setName(String nn)
		{
		name=new String(nn);
		}


	/**
	*Given a reference to a node, this method tells finds the length of the longest point-source path where this node is the point source.
	*If the node has no neighbors then 1 is returned.  If the node has neighbors, then the method is called recursively and the maximum
	*value is returned.
	*@param source a reference to a node whose value, as described above, is desired
	*@return the length of the longest point-source path where this node is the point source (see above)
	*/
	public int getMaxNumDepth(PhyloNode source)
		{
		if(neighbors==null)
			{
			return 1;
			}
		else if(neighbors.length==0)
			{
			return 1;
			}
		else
			{
			int max=1;		
			for(int n=0;n<neighbors.length;n++)
				{
				if(neighbors[n]!=source)
					{
					max=Math.max(max,neighbors[n].getMaxNumDepth(this));
					}
				}
			return max;
			}		
		}



	/**
	*Given a node, find the number of neighbors other than the
	*given node.  If there are no neighbors return 0.
	*If n is the number of neighbors and the argument
	*is one of them, return n-1.  Otherwise, return n.
	*@param p the neighbor in question
	*@return the number of neighbors of this node not counting p
	*/
	public int numNeighborsOtherThan(PhyloNode p)
		{
		int others=0;		
		if(neighbors==null)
			{
			return others;
			}
		else
			{
			for(int n=0;n<neighbors.length;n++)
				{
				if(neighbors[n]!=p)
					{
					others++;
					}//if
				}//for
			}//else
		return others;
		}


	/**
	*Let N={n1,n2,n3,o...} bet the set of neigbhors, and {o}=O be the argument, return N-O={n1,n2,n3,...}.
	*@param other the neighbor to be excluded.
	*@return an array of N-O as described above.
	*/
	public PhyloNode[] getNeighborsOtherThan(PhyloNode other)
		{
		if(!isAnImmediateNeighborOf(other))
			{
			return neighbors;
			}
		else
			{
			//public static PhyloNode[] removePhyloFromArray(PhyloNode[] a,int index)
			int remIndex=getIndexToNeighbor(other);
			return removePhyloFromArray(neighbors,remIndex);
			}
		}



	/**
	*Given a reference to a node, return the index i such that neighbors[i] is the node.
	*If no such i exists, then return (-1).
	*@param searched the PhyloNode to search for.
	*@return the index i as described above.
	*/
	public int getIndexToNeighbor(PhyloNode searched)
		{

		for(int n=0;n<neighbors.length;n++)
			{
			if(neighbors[n]==searched)
				{
				return n;
				}
			}
		return (-1);
		}


	/**
	*Used in parsing a newick string!
	*/
	private static Object[] getFrontDouble(String data)
		{

		int doubleLen=1;
		if(data.startsWith("-"))
			{
			doubleLen++;
			}
		boolean flag=true;
		double v;

		while(flag)
			{
			try
				{
				//System.out.println("Seeing if "+data.substring(0,doubleLen)+" passes muster as a double...");
				v=Double.parseDouble(data.substring(0,doubleLen));
				doubleLen++;
				}
			catch(Exception e)
				{
				//System.out.println("IT DOESN'T!");
				flag=false;
				}
			}

		if(doubleLen>=data.length())
			{
			throw new RuntimeException("Error when trying to extract a front double couldn't do it : "+data);
			}


		doubleLen--;
		//System.out.println("data is "+data);
		//System.out.println("doubleLen is "+doubleLen);
		//System.out.println("The data to be considered as a double is "+data.substring(0,doubleLen)+".");
		v=Double.parseDouble(data.substring(0,doubleLen));

		Double r=new Double(v);
		data=data.substring(doubleLen);

		Object[] re=new Object[2];
		re[0]=r;
		re[1]=data;

		return re;
		}


	private static String concatFrom(int start,String[] l)
		{
		String c=new String("");

		for(int x=start;x<l.length;x++)
			{
			c+=l[x];
			}
		return c;
		}

	/**
	*Given a string return the string less the first n characters.  This is used for parsing newick strings.
	*@param s the string to chop off the front of.
	*@param n the number of characters to chop off the front of the string
	*@return the string less the front n characters.
	*/
	public static String chopFront(String s,int n)
		{
		return s.substring(n);
		}


	/**
	*Given a string s, and a parameter l return s.substring(0,l).  If the stgring has length less than l, then return s.
	*@param s a string to get the front part of
	*@param l the nubmer of characters to get
	*@return a string as described above.
	*/
	public static String getFirst(String s,int l)
		{
		if(s.length()<l)
			{
			return new String(s);
			}
		else
			{
			return s.substring(0,l);
			}
		}

	/**
	*used in parsing a newick string
	*/
	private static String removeSimpleLeaf(String d)
		{
		//assumes given [NAME][:][DIST]....[COMMA/RPAREN]

		//the distance is followed by a comma or a ")")
		int cindex=d.indexOf(",");
		int rpindex=d.indexOf(")");

		if(cindex==(-1))
			{
			return d.substring(rpindex);
			}
		else
			{
			if(cindex<rpindex)
				{
				return d.substring(cindex);
				}
			else
				{
				return d.substring(rpindex);
				}
			}
		}

	
	private static  Object[] parseSimpleLeaf(String d)
		{
		//[NAME][COLON][DISTANCE][COMMA, RPARENS]

		//the first in the object is the node
		//the second is the distance to it
		//System.out.println("Parse simple leaf called with argument : "+d);

		int rpindex=d.indexOf(")");
		int cindex=d.indexOf(",");

		String name=new String("");
		String distString=new String("");
		double dist=0.0;
		int endspot=0;

		if(rpindex==(-1) && cindex==(-1))
			{
			throw new IllegalArgumentException("Error - leaf followed by neither a comma nor a )");
			}

		if(rpindex==(-1))
			{
			throw new IllegalArgumentException("Error - leaf never followed by a )");
			}

		Object[] returnPair=new Object[2];

		if(cindex==(-1))
			{
			//if comma is neg one, but rpindex isn't then we're at the last leaf			
			//name is before :, and distance after it			
			name=d.split(":")[0];
			distString=d.split(":")[1];
			if(distString.indexOf(")")!=(-1))
				{
				distString=distString.substring(0,distString.indexOf(")"));
				}
			returnPair[0]=new PhyloNode(name);
			returnPair[1]=new Double(distString);			
			}
		else
			{
			//get the stuff using the colon and either the first of the comma or )
			name=d.split(":")[0];
			distString=concatFrom(1,d.split(":"));
			//System.out.println("distString is "+distString);
			if(distString.indexOf(",")!=(-1) && distString.indexOf(",")<distString.indexOf(")"))
				{
				//if there's a comma and it comes before a ) if there is a ), then use the comma
				//System.out.println("endspopt assigned from '");
				endspot=distString.indexOf(",");
				}
			else if(distString.indexOf(")")!=(-1) && distString.indexOf(")")<distString.indexOf(","))
				{
				//if there's a ) and it comes before any comma then use it
				//System.out.println("endspot assigned from )");
				endspot=distString.indexOf(")");
				}
			else if(distString.indexOf(")")!=(-1) && distString.indexOf(",")==(-1))
				{
				//if there's a ) but no comma, use the )
				//System.out.println("endspot assigned from ) and no comma in sight!");
				endspot=distString.indexOf(")");
				}
			else
				{
				System.out.println("endspot left alone....");
				}
			//System.out.println("The value of endspot is "+endspot);
			distString=distString.substring(0,endspot);
			//System.out.println("distString is now : "+distString);
			returnPair[0]=new PhyloNode(name);
			returnPair[1]=new Double(distString);
			}

		PhyloNode t=(PhyloNode)(returnPair[0]);
		//System.out.println(t.tellAbout(0));
		//System.out.println(returnPair[1].toString());				
		return returnPair;
		}


	/**
	*Given a newick string, return a PhyloNode that's an entrypoint into the phylogenetic tree represented by the newick string.
	*The returned array has two items.  If o is the returned object, then o[0] is a PhyloNode that's an entry point and o[1]
	*is a string representing the 'consumed' newick string.
	*@param data the newick string
	*@param first should be set to true if called non-recursively
	*@return an array of objects o as described above.
	*/
	public static Object[] makeTreeRec(String data,boolean first) throws IllegalArgumentException
		{
		Object[] toBeReturned=null;
		Object[] returned=null;
		Object[] doubleRet=null;
		String returnedData=null;
		PhyloNode returnedNode=null;
		Double returnedDoubleObject=null;
		double length;
		PhyloNode myRoot=null;

		/*
		After removing the initial (, what follows is either
			1) a recursive call
			2) a call to nonrec
			3) a leaf
		*/

		//System.out.println("Just entered a call into maketreerec...");
		//System.out.println("The argument passed is "+getFirst(data,200)+"...");


		//set up the root and note the returned objects...
		//in returned, 0 will be a node, and 1 will be data
		if(!first)
			{
			myRoot=new PhyloNode("");
			}
		else
			{
			myRoot=new PhyloNode("ROOT");
			}

		if(!data.startsWith("("))
			{
			throw new IllegalArgumentException("Error - maketreerec expects a string starting with (!!!!");
			}
		else
			{
			data=chopFront(data,1);
			}

		while(!data.startsWith(")"))
			{
			//whether it's a leaf or a subtree, parse whatever it is
			//then if it's a subtree consume the ), the : and the length
			//and add it to myRoot.  Consume the , if there is one.
			//If it's a leaf, consume the name,
			//consume the : and the length and it it to root.  Also consume a comma if there is one.

			if(data.startsWith("("))
				{
				//System.out.println("In MTR, doing th recursive case...");
				returned=makeTreeRec(data,false);
				returnedNode=(PhyloNode)(returned[0]);
				returnedData=(String)(returned[1]);
				//the returned data should start with )
				if(!returnedData.startsWith(")"))
					{
					throw new IllegalArgumentException("Error - a recursive call to maketree returned back data that didn't start with a ) - "+getFirst(returnedData,10)+"...!");
					}
				else
					{
					//System.out.println("After returning from a rec call, MTR gave "+returnedData+" back as the data.");
					}
				data=returnedData;
				data=chopFront(data,1);
				//there should be a : followed by a length
				if(!data.startsWith(":"))
					{
					//throw new IllegalArgumentException("Error - a recursive call to maketree consumed a closing ) but didn't find a : after it - "+getFirst(data,10)+"...!");
					returnedNode.name=new String(data.split(":")[0]);
					data=data.substring(data.indexOf(":")+1);
					}
				else
					{
					data=chopFront(data,1);
					}

				//now that the : is removed, get the length...			
				doubleRet=getFrontDouble(data);
				returnedDoubleObject=(Double)(doubleRet[0]);
				data=(String)(doubleRet[1]);
				length=returnedDoubleObject.doubleValue();
				}//complete the recursive case
			else
				{
				//System.out.println("In MTR, doing the plain case...");
				//if it doesn't start with a (, then assume it starts with a name
				//if it starts with a name then it must be a leaf!
				returned=parseSimpleLeaf(data);
				returnedNode=(PhyloNode)(returned[0]);
				returnedDoubleObject=(Double)(returned[1]);
				length=returnedDoubleObject.doubleValue();				
				data=removeSimpleLeaf(data);
				}//complete the leaf case
			//given the returned node and length, add them to myRoot
			myRoot.addNeighbor(returnedNode,length);
			//what follows should either be a comma or a )
			if(!data.startsWith(",") && !data.startsWith(")"))
				{
				throw new IllegalArgumentException("Error - after adding a node in maketree rec, expected a , or a ), but got "+getFirst(data,10)+"... instead!");
				}
			//if there is a comma, then consume it
			if(data.startsWith(","))
				{
				data=chopFront(data,1);
				}
			}//while(!data.startsWith(")"))
		
		toBeReturned=new Object[2];
		toBeReturned[0]=myRoot;
		toBeReturned[1]=data;

		return toBeReturned;
		}




	/**
	*Adds a PhyloNode as a neighbor to the neighbor array.
	*/
	private void addNeighbor(PhyloNode neighbor,double ndist,boolean allowRec)
		{

		if(isANeighbor(neighbor))
			{
			if(allowRec)
				{
				neighbor.addNeighbor(this,ndist,false);
				}
			return;
			}//ALREADY A NEIGHBOR
		else
			{
			//NOT ALREADY A NEIGHBOR
			if(allowRec)
				{
				neighbor.addNeighbor(this,ndist,false);
				}
			if(neighbors==null)
				{
				neighbors=new PhyloNode[1];
				neighbors[0]=neighbor;
				distances=new double[1];
				distances[0]=ndist;
				}
			else
				{
				PhyloNode[] newNeighbors=new PhyloNode[neighbors.length+1];
				for(int nc=0;nc<neighbors.length;nc++)
					{
					newNeighbors[nc]=neighbors[nc];
					}
				newNeighbors[newNeighbors.length-1]=neighbor;
				neighbors=newNeighbors;

				double[] newDistances=new double[distances.length+1];
				for(int dc=0;dc<newDistances.length-1;dc++)
					{
					newDistances[dc]=distances[dc];
					}
				newDistances[newDistances.length-1]=ndist;
				distances=newDistances;
				}
			}//NOT ALREDY A NEIGHBOR
		}





	/**
	*Adds a neighbor to the array of neighbors of this PhyloNode.
	*@param neighbor the new neighbor
	*@param ndist the distance to the neighbor
	*/
	public void addNeighbor(PhyloNode neighbor,double ndist)
		{
		addNeighbor(neighbor,ndist,true);
		}//addneighbor




	/**
	*Returns true if the given phylonode is a neighbor of this phylonode, false otherwise
	*@param other the phylonode in question
	*@return true if the given phylonode is a neighbor of this phylonode, false otherwise
	*/
	public boolean isANeighbor(PhyloNode other)
		{
		if(neighbors==null)
			{
			return false;
			}
		else
			{
			for(int n=0;n<neighbors.length;n++)
				{
				if(other==neighbors[n])
					{
					return true;
					}
				}
			}
		return false;
		}//isANeighbor


	/**
	*Given an array of PhyloNodes, print out each of their names in the given order
	*@param b an array of phylonodes whose names are to be printed
	*/
	public static void printBranchPath(PhyloNode[] b)
		{
		for(int p=0;p<b.length;p++)
			{
			System.out.println("\t#"+p+" "+b[p].name);
			}
		}


	/**
	*Given an entrypoint PhyloNode into a phylogenetic tree, get the longest path in the tree.
	*@param p a PhyloNode used as an entrypoint into a phylogenetic tree.
	*@return an array of PhyloNodes each connected representing the longest path in the tree
	*/
	public static PhyloNode[] getLongestPath(PhyloNode p)
		{
		//get a list of nodes
		PhyloNode[] b=p.getArray(p,null);
		PhyloNode[][] paths=null;
		PhyloNode[] longestPath=null;
		double longpath=0.0;
		double max=Double.NEGATIVE_INFINITY;
		//go through each node and enumerate the paths starting with that node
		for(int thisroot=0;thisroot<b.length;thisroot++)
			{
			paths=b[thisroot].getAllPathsStartingWith();
			for(int start=0;start<paths.length;start++)
				{
				longpath=getPathTotalLength(paths[start]);
				//System.out.println("Now considering a path to see how long it is.");
				//System.out.println("The path underconsideration is ");
				//printBranchPath(paths[start]);
				//System.out.println("The length of this path is "+longpath+".");
				if(longpath>max)
					{
					longestPath=paths[start];
					max=longpath;
					}
				}
			}
		return longestPath;
		}




	/**
	*Given a path of phylonodes (an array of them that are each connected serially), 
	*insert a phylonode in the modpoint of the array and return the newly inserted node
	*@param longPath an array of phylonodes representing a path
	*@return a PhyloNode inserted at the midpoint of the path
	*/
	public static PhyloNode insertRootGivenLongestPath(PhyloNode[] longPath)
		{
	
		if(!isValidPath(longPath))
			{
			throw new IllegalArgumentException("Error - a longest path isn't a valid path!!!");
			}
		if(longPath.length==1)
			{
			return longPath[0];
			}

		PhyloNode newRoot=null;
		double halfDist=(-1.0);

		if(longPath.length==2)
			{
			halfDist=longPath[0].getDisanceToImmediateNeighbor(longPath[1])/2.0;
			newRoot=new PhyloNode("midPointRoot");
			longPath[0].removeNeighbor(longPath[1]);
			longPath[1].removeNeighbor(longPath[0]);
			newRoot.addNeighbor(longPath[0],halfDist);
			newRoot.addNeighbor(longPath[1],halfDist);
			return newRoot;
			}
		else if(longPath.length>2)
			{		
			PhyloNode startNode=longPath[0];
			PhyloNode endNode=longPath[longPath.length-1];
			PhyloNode midNode=longPath[0];
			double pathTotalDistance=getPathTotalLength(longPath);
			double accumulatedDistance=0.0;
			halfDist=pathTotalDistance/2.0;
			int pathPlace=0;
			while(accumulatedDistance<halfDist)
				{
				accumulatedDistance=accumulatedDistance+longPath[pathPlace].getDisanceToImmediateNeighbor(longPath[pathPlace+1]);
				pathPlace++;
				midNode=longPath[pathPlace];				
				}
			if(accumulatedDistance==halfDist)
				{
				return midNode;
				}
			//System.out.println("The distance between pathplace and pathplace-1 nodes is "+longPath[pathPlace].getDisanceToImmediateNeighbor(longPath[pathPlace-1]));
			//System.out.println("The accumuated distance is "+accumulatedDistance+".");
			//Now, the root node belongs somewhere between longPath[pathPlace] and longPath[pathPlace-1];
			double rootToPathPlaceDist=accumulatedDistance-halfDist;
			double rootToPathPlaceMinusOneDist=longPath[pathPlace].getDisanceToImmediateNeighbor(longPath[pathPlace-1])-rootToPathPlaceDist;
			//System.out.println("rootToPathPlaceDist value is "+rootToPathPlaceDist+".");
			//System.out.println("rootToPathPlaceMinusOneDist value is "+rootToPathPlaceMinusOneDist+".");
			longPath[pathPlace].removeNeighbor(longPath[pathPlace-1]);
			PhyloNode newMidRoot=new PhyloNode("rootpoint");
			newMidRoot.addNeighbor(longPath[pathPlace],rootToPathPlaceDist);
			newMidRoot.addNeighbor(longPath[pathPlace-1],rootToPathPlaceMinusOneDist);
			//System.out.println("Newmidroot's dist to pathplace is "+newMidRoot.getDisanceToImmediateNeighbor(longPath[pathPlace])+".");
			//System.out.println("Newmidroot's dist to pathplace-1 is "+newMidRoot.getDisanceToImmediateNeighbor(longPath[pathPlace-1])+".");
			//newRoot=new PhyloNode("MidPointRoot");
			//newRoot.addNeighbor(newMidRoot,Math.min(rootToPathPlaceDist,rootToPathPlaceMinusOneDist));
			return newMidRoot;
			}
		throw new RuntimeException("Error in insertRootGivenLongestPath method!!!");
		}


	/**
	*Given a path return true if the nodes are serially connected.
	*@param path an array of phylondes
	*@return true if all consecutive nodes are neighbors
	*/
	public static boolean isValidPath(PhyloNode[] path)
		{

		if(path==null)
			{
			return false;
			}
		if(path.length==0)
			{
			return false;
			}
		if(path.length==1)
			{
			return true;
			}
		for(int p=0;p<path.length-1;p++)
			{
			if(!path[p].isAnImmediateNeighborOf(path[p+1]) || !path[p+1].isAnImmediateNeighborOf(path[p]))
				{
				return false;
				}
			}	
		return true;
		}


	/**
	*Given a reference to a neighboring node, return the distance to that node.
	*@param other a neighboring node
	*@return the distance to the given node
	*@throws IllegalArgumentException if the given argument, other, is not a neighbor of <i>this</i> node.
	*/
	public double getDisanceToImmediateNeighbor(PhyloNode other) throws IllegalArgumentException
		{
		if(neighbors==null)
			{
			throw new NullPointerException("Error - thsi node has NULL neighbros so getting a distance to anything is impossible!");
			}
		else if(other==this)
			{
			return 0.0;
			}
		else
			{
			for(int n=0;n<neighbors.length;n++)
				{
				if(neighbors[n]==other)
					{
					return distances[n];
					}
				}
			}
		throw new IllegalArgumentException("Error - trying to get the distance to an immediate neighbor, but this node doesn't have the given node as an immediate neighbor!!!");
		}



	/**
	*Given a PhyloNode return true if this node and the given node are neighbors.
	*@param other a phylonode
	*@return true if this node and the given node are neighbors
	*/
	public boolean isAnImmediateNeighborOf(PhyloNode other)
		{
		if(neighbors==null || neighbors.length==0)
			{
			return false;
			}
		for(int n=0;n<neighbors.length;n++)
			{
			if(neighbors[n]==other)
				{
				return true;
				}
			}
		return false;
		}


	/**
	*remove a neighbor
	*@param other the neighbor to remove
	*/
	public boolean removeNeighbor(PhyloNode other)
		{
		return removeNeighbor(other,true);
		}


	private boolean removeNeighbor(PhyloNode other,boolean allowRec)
		{
		if(!isAnImmediateNeighborOf(other))
			{
			return false;
			}
		else
			{
			int t=(-1);
			for(int i=0;i<neighbors.length;i++)
				{
				if(neighbors[i]==other)
					{
					if(allowRec)
						{
						neighbors[i].removeNeighbor(this,false);
						}
					t=i;
					i=neighbors.length;
					}
				}//for
			if(t==(-1))
				{
				return false;
				}			
			neighbors=removePhyloFromArray(neighbors,t);
			removeDoubleFromArray(distances,t);
			if(allowRec)
				{
				other.removeNeighbor(this,false);
				}
			return true;
			}
		}


	/**
	*Given a path, return its total length
	*@param p a path
	*@return the total length along the given path of phylonodes
	*/
	public static double getPathTotalLength(PhyloNode[] p)
		{
		if(p==null || p.length==1)
			{
			return 0;
			}
		else
			{
			double l=0.0;
			for(int e=0;e<p.length-1;e++)
				{
				if(!p[e].isAnImmediateNeighborOf(p[e+1]))
					{
					throw new IllegalArgumentException("Error - in computing a branch length in  a path, consecutive nodes aren't neighbors!!!");
					}
				l=l+p[e].getDisanceToImmediateNeighbor(p[e+1]);
				}
			return l;
			}
		}


	/**
	*Given an array of phylondes and an index into the array,
	*return the array less the indexed item in the same, stable, order
	*@param a an array of phylondes
	*@param index the index of the node to be removed
	*@return an array of phylonodes with the indexed item removed.
	*@throws IllegalArgumentException if the index is out of bounds
	*/	
	public static PhyloNode[] removePhyloFromArray(PhyloNode[] a,int index) throws IllegalArgumentException
		{
		if(index>=a.length || index<0)
			{
			throw new IllegalArgumentException("Error - trying to remove an index too high from an array!");
			}
		else
			{
			PhyloNode[] r=new PhyloNode[a.length-1];
			int ri=0;
			for(int i=0;i<a.length;i++)
				{
				if(i!=index)
					{
					r[ri]=a[i];
					ri++;
					}
				}
			return r;
			}
		}




	/**
	*Given an array of doubles, remove the indexed double from the array
	*@param a an array of doubles
	*@param index an index into the array of doubles
	*@return the array less the indexed double
	*@throws IllegalArgumentException  if the index is out of bounds
	*/
	public static double[] removeDoubleFromArray(double[] a,int index) throws IllegalArgumentException
		{
		if(index>=a.length || index<0)
			{
			throw new IllegalArgumentException("Error - trying to remove an index too high from an array!");
			}
		else
			{
			double[] c=new double[a.length-1];
			int cr=0;

			for(int l=0;l<a.length;l++)
				{
				if(l!=index)
					{
					c[cr]=a[l];
					cr++;
					}
				}
			return c;
			}
		}






	
	private PhyloNode[][] getBranches(PhyloNode caller)
		{
		PhyloNode[][] branches=null;
		if(neighbors==null)
			{
			branches=new PhyloNode[1][];
			branches[0]=new PhyloNode[1];
			branches[0][0]=this;
			return branches;
			}
		else if(neighbors!=null && neighbors.length==1)
			{
			if(caller==neighbors[0])
				{
				branches=new PhyloNode[1][];
				branches[0]=new PhyloNode[1];
				branches[0][0]=this;
				return branches;
				}
			else
				{
				branches=concatBranches(branches,neighbors[0].getBranches(this));
				for(int ab=0;ab<branches.length;ab++)
					{
					branches[ab]=addBranchFront(branches[ab],this);
					}
				return branches;
				}
			}
		else
			{
			for(int n=0;n<neighbors.length;n++)
				{
				if(neighbors[n]!=caller)
					{
					branches=concatBranches(branches,neighbors[n].getBranches(this));				
					}
				}
			//System.out.println("The name of the node is "+name);
			//System.out.println("The value of branches is "+branches+".");
			//System.out.println("The value of branahces.length is "+branches.length+".");
			for(int ab=0;ab<branches.length;ab++)
				{
				branches[ab]=addBranchFront(branches[ab],this);
				}
			return branches;
			}
		}




	private static PhyloNode[] addBranchFront(PhyloNode[] e,PhyloNode n)
		{
		PhyloNode[] r;

		if(e==null)
			{
			r=new PhyloNode[1];
			r[0]=n;
			return r;
			}
		else
			{
			r=new PhyloNode[e.length+1];
			for(int a=1;a<r.length;a++)
				{
				r[a]=e[a-1];
				}
			r[0]=n;
			return r;
			}
		}




	private static PhyloNode[] addBranch(PhyloNode[] e,PhyloNode n)
		{
		PhyloNode[] r;

		if(e==null)
			{
			r=new PhyloNode[1];
			r[0]=n;
			return r;
			}
		else
			{
			r=new PhyloNode[e.length+1];
			for(int a=0;a<e.length;a++)
				{
				r[a]=e[a];
				}
			r[r.length-1]=n;
			return r;
			}
		}


	private static PhyloNode[][] concatBranches(PhyloNode[][] b1,PhyloNode[][] b2)
		{
		if(b1==null && b2==null)
			{
			return null;
			}
		else if(b1==null && b2!=null)
			{
			return b2;
			}
		else if(b1!=null && b2==null)
			{
			return b1;
			}

		PhyloNode[][] b=new PhyloNode[b1.length+b2.length][];

		for(int i=0;i<b1.length;i++)
			{
			b[i]=b1[i];
			}
		for(int j=b1.length;j<b1.length+b2.length;j++)
			{
			b[j]=b2[j-b1.length];
			}

		return b;

		}

	/**
	*Given Given a phylonode, get an array of arrays of phylondes with each array being a point source path
	*so that all point source paths starting at this phylonode are returned.
	*@returns an array of paths as described above
	*/
	public PhyloNode[][] getAllPathsStartingWith()
		{
		return getBranches(null);
		}


	public PhyloNode[] getNodesAwayFrom(PhyloNode n)
		{
		PhyloNode[][] branches=getBranches(n);
		PhyloNode[] nlist=null;
		for(int x=0;x<branches.length;x++)
			{
			for(int y=0;y<branches[x].length;y++)
				{
				nlist=addBranch(nlist,branches[x][y]);
				}
			}
		nlist=removeDups(nlist);
		return nlist;
		}

	
	private PhyloNode[] getArray(PhyloNode caller,PhyloNode[] e)
		{
		e=addBranch(e,this);
		if(neighbors!=null)
			{
			for(int n=0;n<neighbors.length;n++)
				{
				if(neighbors[n]!=caller)
					{
					e=concatPhyloNodeLists(e,neighbors[n].getArray(this,e));
					}
				}
			}
		e=removeDups(e);
		return e;
		}




	private static PhyloNode[] removeDups(PhyloNode[] e)
		{
		PhyloNode[] n=null;
		for(int p=0;p<e.length;p++)
			{
			if(!isInArray(n,e[p]))
				{
				n=addBranch(n,e[p]);
				}
			}
		return n;
		}


	private static PhyloNode[] concatPhyloNodeLists(PhyloNode[] b1,PhyloNode[] b2)
		{

		PhyloNode[] b=new PhyloNode[b1.length+b2.length];
		System.arraycopy(b1,0,b,0,b1.length);
		System.arraycopy(b2,0,b,b1.length,b2.length);

		//System.out.println("CC given:");
		//printBranchPath(b1);
		//printBranchPath(b2);
		//System.out.println("CC gives:");
		//printBranchPath(b);


		return b;
		/*public static void arraycopy(Object src,
                             int srcPos,
                             Object dest,
                             int destPos,
                             int length)*/
		}


	/**
	*Given an array of phylonodes return true if the given node is in the array.
	*@param q the node in question
	*@param a an array of phylonodes
	*@returns true if the phylonode q is one of items in the array.
	*/
	public static boolean isInArray(PhyloNode[] a,PhyloNode q)
		{
		if(a==null)
			{
			return false;
			}
		for(int c=0;c<a.length;c++)
			{
			if(q==a[c])
				{
				return true;
				}
			}
		return false;
		}



	/**
	*Given a node that's an entry point into a tree, traverse the tree and return a list of the SerialIDs from the tree.
	*@param entryPoint a PhyloNode entrypoint into the tree.
	*@return a list of SerialIDs from the tree
	*/
	public static String[] getSerialsOfNodes(PhyloNode entryPoint)
		{
		//get a list of nodes
		PhyloNode[] phyloList=entryPoint.getArray(entryPoint,null);		

		String[] serialList=null;
		int spaceIndex;
		for(int nodenum=0;nodenum<phyloList.length;nodenum++)
			{
			//the serial is before the first space or just the entire string
			spaceIndex=phyloList[nodenum].name.indexOf(" ");
			if(spaceIndex>0)
				{
				serialList=TreeUtils.addLine(phyloList[nodenum].name.substring(0,spaceIndex),serialList);
				System.out.println("Added "+phyloList[nodenum].name.substring(0,spaceIndex)+" to a list of Serials.\n");
				}
			}
		
		return serialList;
		}



	/**
	*Using this phylonode as an entrypoint into a tree, return a node
	*with the same name as the argument
	*@param n the name of the desired node.
	*@return the first phylonode encountered in a search with the same name as the argument.
	*/
	public PhyloNode findNodeByName(String n)
		{

		PhyloNode[] nodeArray=getArray(this,null);
		for(int nn=0;nn<nodeArray.length;nn++)
			{
			if(nodeArray[nn].getName().compareTo(n)==0)
				{
				return nodeArray[nn];
				}
			}
		return null;
		}



	/**
	*Returns true if a node needs to be 'binarified'.  A node is said to be binarified if it has 4 or more neighbors.
	*@return true if and only if a node needs to be 'binarified', false otherwise.
	*/
	public boolean needsToBeBinarified()
		{
		if(neighbors==null)
			{
			return false;
			}
		if(neighbors.length<=3)
			{
			return false;
			}
		else
			{
			return true;
			}
		}


	/**
	*Returns an int index of the node nearest to this node.  If the minimal distance is shared by multiple neighbors the index of the 
	*first such encountered node is returned.
	*@return an index to a node <i>n</i> such that the distance to it is less than or equal to all other distances to neighbors of this node.
	*/
	public int getIndexOfNearestNeighbor()
		{
		if(neighbors==null)
			{
			return (-1);
			}
		if(neighbors.length==0)
			{
			return (-1);
			}
		int nearest=0;
		for(int n=0;n<neighbors.length;n++)
			{
			if(distances[n]<distances[nearest])
				{
				nearest=n;
				}
			}
		return nearest;
		}



	private boolean areNeighborsInSortedOrder()
		{
		if(neighbors==null)
			{
			return true;
			}
		if(neighbors.length<=1)
			{
			return true;
			}
		else
			{
			for(int n=0;n<neighbors.length-1;n++)
				{
				if(distances[n]>distances[n+1])
					{
					return false;
					}
				}
			return true;
			}
		}



	private void swapDistances(int i,int j)
		{
		double temp;
		temp=distances[i];
		distances[i]=distances[j];
		distances[j]=temp;
		}


	private void swapNeighbors(int i,int j)
		{
		PhyloNode temp=neighbors[i];
		neighbors[i]=neighbors[j];
		neighbors[j]=temp;
		}

	private void swapNodes(int i,int j)
		{
		swapDistances(i,j);
		swapNeighbors(i,j);
		}
	

	private void sortNeighborsByDistance()
		{
		
		if(areNeighborsInSortedOrder())
			{
			return;
			}

		if(neighbors==null)
			{
			return;
			}
		else
			{
			if(neighbors.length<=1)
				{
				return;
				}
			else
				{
				for(int i=0;i<neighbors.length-1;i++)
					{
					for(int j=i;j<neighbors.length;j++)
						{
						//swap the values at indices i and j
						if(distances[i]>distances[j])
							{
							swapNodes(i,j);
							}
						}
					}
				}
			}
		}


	private void mergeUnder(int i,int j,int k)
		{
		PhyloNode addition=new PhyloNode("");
		PhyloNode ineighbor=neighbors[i];
		double idist=getDisanceToImmediateNeighbor(ineighbor);
		PhyloNode jneighbor=neighbors[j];
		double jdist=getDisanceToImmediateNeighbor(jneighbor);
		PhyloNode under=neighbors[k];

		removeNeighbor(ineighbor);
		removeNeighbor(jneighbor);

		addition.addNeighbor(ineighbor,idist);
		addition.addNeighbor(jneighbor,jdist);

		under.addNeighbor(addition,0.0);		
		}




	/*public void makeBinaryWhereNeeded(PhyloNode root)
		{

		if(needsToBeBinarified())
			{

			System.out.println("***********************************************************");
			System.out.println("ATTENTION : MERGING UNDER !");
			System.out.println("***********************************************************");

			//has 4 or more neighbors!
			//fix binarified, then make recursive calls to neighbors
			while(needsToBeBinarified())
				{
				if(root==neighbors[0])
					{
					mergeUnder(1,2,3);
					}
				else if(root==neighbors[1])
					{
					mergeUnder(0,2,3);
					}
				else if(root==neighbors[2])
					{
					mergeUnder(0,1,3);
					}
				else
					{
					mergeUnder(0,1,2);
					}
				}
			}
		else
			{
			//just make recursive calls to children
			if(neighbors!=null)
				{
				for(int n=0;n<neighbors.length;n++)
					{
					if(root!=neighbors[n])
						{
						neighbors[n].makeBinaryWhereNeeded(this);
						}
					}
				}
			}
		}*/


	




	/**
	*Get a list of all the nodes of this tree as an array.
	*@return an array of PhyloNodes, each of which is a node in this tree.
	*/
	public PhyloNode[] getListOfAllNodes()
		{

		PhyloNode[] folks=getArray(this,null);
		if(!isInArray(folks,this))
			{
			PhyloNode[] addition=new PhyloNode[1];
			addition[0]=this;
			folks=concatPhyloNodeLists(addition,folks);
			}
		folks=removeDups(folks);
		return folks;
		}


	public PhyloNode[][] getListOfAllAdjacentPairs()
		{
		
		PhyloNode[] alist=getListOfAllNodes();
		PhyloNode[][] plist=new PhyloNode[0][];
		
		for(int a=1;a<alist.length-1;a++)
			{
			for(int b=0;b<alist.length;b++)
				{
				if(a!=b && alist[a].isAnImmediateNeighborOf(alist[b]))
					{
					plist=addPairToPairList(alist[a],alist[b],plist);
					}
				}
			}
		
		return plist;
		}




	public static PhyloNode[][] addPairToPairList(PhyloNode n1,PhyloNode n2,PhyloNode[][] elist)
		{
		if(elist==null)
			{
			elist=new PhyloNode[1][];
			elist[0]=new PhyloNode[2];
			elist[0][0]=n1;
			elist[0][1]=n2;
			return elist;
			}
		else
			{
			PhyloNode[][] nelist=new PhyloNode[elist.length+1][];

			for(int c=0;c<elist.length;c++)
				{
				nelist[c]=new PhyloNode[2];
				nelist[c][0]=elist[c][0];
				nelist[c][1]=elist[c][1];				
				}

			nelist[nelist.length-1]=new PhyloNode[2];
			nelist[nelist.length-1][0]=n1;
			nelist[nelist.length-1][1]=n2;

			return nelist;
			}
		}





	/**
	*Given an entry-point PhyloNode and a string representing a SerialID, insert a new node in the
	*tree and return this new node (which can later be used another entrypoint into the tree)
	*@param entryPoint an entrypoint into a phylotree graph
	*@param serial a String of the SerialID of the node to insert a new node by.  Note that 
	*the first string(which ends after the first whitespace) in any node's name is interpreted
	*as the node's SerialID.
	*@return a PhyloNode that is the newly inserted node.
	*@throws a RuntimeException if no node with the given SerialID is found!
	*/
	public static PhyloNode insertRootNearNodeWithSerial(PhyloNode entryPoint,String serial) throws RuntimeException
		{

		PhyloNode[] PhyloList=entryPoint.getArray(entryPoint,null);
		PhyloNode targetNode=null;

		if(PhyloList!=null)
			{
			for(int sl=0;sl<PhyloList.length;sl++)
				{
				System.out.println("Item in Array #"+sl+" : "+PhyloList[sl].name+".");
				if(PhyloList[sl].name.indexOf(serial)!=(-1))
					{
					targetNode=PhyloList[sl];
					}
				}
			}
		else
			{
			System.out.println("No items in phylo list!");
			System.exit(0);
			}

		if(targetNode==null)
			{
			throw new RuntimeException("Searching for oldest SerialNode, but it doesn't exist!!!!");
			}
		else
			{
			PhyloNode targetNeighbor=targetNode.neighbors[0];
			double dist=targetNeighbor.getDisanceToImmediateNeighbor(targetNode);
			dist=dist/2.0;

			PhyloNode nearTarget=new PhyloNode("nearoldroot");
			targetNode.removeNeighbor(targetNeighbor);
			targetNeighbor.removeNeighbor(targetNode);
			nearTarget.addNeighbor(targetNode,dist);
			nearTarget.addNeighbor(targetNeighbor,dist);			
			System.out.println("Adding new node as root :"+nearTarget.name+".");
			return nearTarget;
			}
		}
	}
