/**
 * Title:        ProAlign<p>
 * Description:  <p>
 * Copyright:    Copyright (c) Ari Loytynoja<p>
 * License:      GNU GENERAL PUBLIC LICENSE<p>
 * @see          http://www.gnu.org/copyleft/gpl.html
 * Company:      ULB<p>
 * @author Ari Loytynoja
 * @version 1.0
 */
package proalign;

import java.io.IOException;
import java.lang.ClassNotFoundException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class AlignmentNode implements Serializable {
    
    String sequence;
    String alphabet;
    String equateAlphabet;
    String name;
    String tree;
    float distance;
    int nodeNumber;

    String minimumProbNode = new String();
    int minimumProbNumber;

    int treeX;
    int treeY;

    boolean isUnique = true;
    int sampleTimes = 0;

    boolean isBandWarning = false;

    boolean hasTrailers = false;
    int start0=0;
    int start1=0;
    int end0=0;
    int end1=0;

    double viterbiEnd;
    double forwardEnd;

    double[][] charProb; 
    boolean isTerminal;
    int numChild;

    double[] postProb;
    int[][] cellPath;

    AlignmentNode[] child;
    AlignmentNode parent;
    ProAlign pa;

    AlignmentNode(ProAlign pa, String tree, float distance) {

	this.pa = pa;
	this.tree = tree;
	this.distance = Math.abs(distance);
	parent = this;

	sequence = new String();

	// If this is internal node..
	//
	if(tree.indexOf(",")>0) { 

	    nodeNumber = pa.getNodeNumber(); // set unique number 
	    child = new AlignmentNode[2];
	    
	    String[] trees;
	    float[] distances;
	    TreeReader tr = new TreeReader();

	    if(nodeNumber==0) { // root is trifurcating: correction.

		ProAlign.log("AlignmentNode: "+tree);

		name = "root";
		
		trees = tr.divideTree(tree);
		distances = tr.distance;

	    } else {            // other internal nodes bifurcating

		ProAlign.log("AlignmentNode: "+tree);

		name = "node"+nodeNumber;
		trees = tr.divideTree(tree);
		distances = tr.distance;
	    }

	    child[0] = new AlignmentNode(pa, trees[0], distances[0]);
	    child[1] = new AlignmentNode(pa, trees[1], distances[1]);

	    numChild = getNumChild();
	    
	}
	
	// ..else this is terminal node.
	//
	else { 
	    
	    name = tree;
	    alphabet = pa.sm.alphabet;
	    equateAlphabet = pa.sm.equateAlphabet;
	    sequence = (String) pa.seqs.get(name);
	    isTerminal = true;
	    numChild = 0;

	    ProAlign.log("AlignmentNode: "+name+":"+distance+", "+sequence.length());
	
	    // Observed charcter has probability of 1, others 0.
	    //
	    charProb = new double[sequence.length()][alphabet.length()];
	    
	    for(int i=0; i<sequence.length();i++) {
		if(ProAlign.isDna) {
		    if(equateAlphabet.indexOf(sequence.charAt(i)) < 4) {
			charProb[i][equateAlphabet.indexOf(sequence.charAt(i))] = 1d;
		    } else { // uses equate characters 
			if(sequence.charAt(i)=='R') {
			    charProb[i][0] = 0.5d;
			    charProb[i][2] = 0.5d;
			} else if(sequence.charAt(i)=='Y') {
			    charProb[i][1] = 0.5d;
			    charProb[i][3] = 0.5d;
			} else if(sequence.charAt(i)=='M') {
			    charProb[i][0] = 0.5d;
			    charProb[i][1] = 0.5d;
			} else if(sequence.charAt(i)=='K') {
			    charProb[i][2] = 0.5d;
			    charProb[i][3] = 0.5d;
			} else if(sequence.charAt(i)=='S') {
			    charProb[i][1] = 0.5d;
			    charProb[i][2] = 0.5d;
			} else if(sequence.charAt(i)=='W') {
			    charProb[i][0] = 0.5d;
			    charProb[i][3] = 0.5d;
			} else if(sequence.charAt(i)=='H') {
			    charProb[i][0] = (double)(1d/3d);
			    charProb[i][1] = (double)(1d/3d);
			    charProb[i][3] = (double)(1d/3d);
			} else if(sequence.charAt(i)=='B') {
			    charProb[i][1] = (double)(1d/3d);
			    charProb[i][2] = (double)(1d/3d);
			    charProb[i][3] = (double)(1d/3d);
			} else if(sequence.charAt(i)=='V') {
			    charProb[i][0] = (double)(1d/3d);
			    charProb[i][1] = (double)(1d/3d);
			    charProb[i][2] = (double)(1d/3d);
			} else if(sequence.charAt(i)=='D') {
			    charProb[i][0] = (double)(1d/3d);
			    charProb[i][2] = (double)(1d/3d);
			    charProb[i][3] = (double)(1d/3d);
			} else if(sequence.charAt(i)=='N') {
			    charProb[i][0] = 0.25d;
			    charProb[i][1] = 0.25d;
			    charProb[i][2] = 0.25d;
			    charProb[i][3] = 0.25d;
			} else if(sequence.charAt(i)=='U') {
			    charProb[i][3] = 1d;
			}
		    }
		} else {
		    if(equateAlphabet.indexOf(sequence.charAt(i)) < 20) {
			charProb[i][equateAlphabet.indexOf(sequence.charAt(i))] = 1d;
		    } else { // uses equate characters 
			for(int j=0; j<20; j++) {
			    charProb[i][j] = pa.sm.aaFreqs[j];
			}
		    }
		}
	    }	
	    
	}
    }

    void align() throws TraceBackException, Exception {

	// If there are childs, align them first.
	//
	try {
	    if(child[0].getNumChild()>1) {
		child[0].align();
	    }

	    if(child[1].getNumChild()>1) {
		child[1].align();
	    }
	} catch(TraceBackException tbe) { // throw exception if close to the edge
	    throw tbe;
	}

	// NEW TRAILING >>

	int[] trail = new int[2];
	double[][] midseq0 = new double[0][0];
	double[][] midseq1 = new double[0][0];

	if(ProAlign.removeTrailing) {
	    
	    CheckTrailing ct = new CheckTrailing(pa);
	    trail = ct.trailing(child[0].sequence.trim(),child[1].sequence.trim());

	    if(Math.abs(trail[0])>ProAlign.offset || Math.abs(trail[1])>ProAlign.offset) {

		if(trail[0]>ProAlign.offset) {
		    start0=trail[0]-ProAlign.offset;
		}
		if(trail[1]>ProAlign.offset) {
		    end0 = trail[1]-ProAlign.offset;
		}

		midseq0 = new double[child[0].charProb.length-end0-start0][child[0].charProb[0].length];

		int k=0;
		for(int i=start0; i<child[0].charProb.length-end0;i++) {
		    int l=0;
		    for(int j=0; j<child[0].charProb[i].length; j++) {
			midseq0[k][l++] = child[0].charProb[i][j];	
		    }
		    k++;
		}

	
		if(-1*trail[0]>ProAlign.offset) {
		    start1=Math.abs(trail[0])-ProAlign.offset;
		}
		if(-1*trail[1]>ProAlign.offset) {
		    end1 = Math.abs(trail[1])-ProAlign.offset;
		}

		midseq1 = new double[child[1].charProb.length-end1-start1][child[1].charProb[0].length];

	        k=0;
		for(int i=start1; i<child[1].charProb.length-end1;i++) {
		    int l=0;
		    for(int j=0; j<midseq1[0].length; j++) {
			midseq1[k][l++] = child[1].charProb[i][j];	
		    }
		    k++;
		}
		hasTrailers=true;
		
	    }
	}

	// << NEW TRAILING

	// Child's nodes are aligned, align childs.
	//
	AlignmentLoop loop = new AlignmentLoop(parent.pa,AlignmentNode.this);

	ProAlign.log(" "+name+ ": aligning "+child[0].name+" and "+child[1].name);
	if(ProAlign.isResultWindow) {
	    ResultWindow.updateInfo(" Creating multiple alignment: aligning '"+name+"'.");
	}

	try {
	    if(hasTrailers) {
		loop.align(midseq0,midseq1,child[0].distance,child[1].distance);
	    } else {
		loop.align(child[0].charProb,child[1].charProb,child[0].distance,child[1].distance);
	    }
	} catch(Exception e) { 
//	    System.out.println("AlignmentNode: throws an exception");
	    throw e; 
	}

	viterbiEnd = loop.vitEnd;
	forwardEnd = loop.fwdEnd;

	TraceBackPath path = new TraceBackPath(parent,loop);
	try {
	    charProb = path.getNode(ProAlign.trackBest); // get best or sample.
	    postProb = path.postProb;
	    cellPath = path.cellPath;

	    if(hasTrailers) {
		viterbiEnd += path.trailProb;
		forwardEnd += path.trailProb;
	    }

	} catch(TraceBackException tbe) {
	    String error = tbe.getMessage();
	    throw new TraceBackException(" Aligning "+name+":\n  "+error);
	}

/*
	System.out.print("\n0: ");
	for(int x=0; x<cellPath.length; x++) {
	    System.out.print(cellPath[x][0]+",");
	}
	System.out.print("\n1: ");
	for(int x=0; x<cellPath.length; x++) {
	    System.out.print(cellPath[x][1]+",");
	}
//*/
	isUnique = path.isUnique;
	sampleTimes = path.sampleTimes;
	isBandWarning = path.isBandWarning;

	createSequence();
    }

    // Create most likely sequence to be used for pw alignment
    //
    void createSequence() {

	alphabet = pa.sm.alphabet;
	sequence = new String();
	for(int i=0; i<charProb.length; i++) {
	    double best = 0d;
	    char site = ' ';
	    for(int j=0; j<charProb[0].length-1; j++) {
		if(charProb[i][j]>best) {
		    best = charProb[i][j];
		    site = alphabet.charAt(j);
		}
	    }
	    sequence += site;
	}
    }

    // Get maximum depth (#internal nodes) of the tree.
    //
    int getTreeDepth() { 
	if(isTerminal) {
	    return 0;
	}
	if(child[0].getTreeDepth()>child[1].getTreeDepth()){
	    return child[0].getTreeDepth()+1;
	}else {
	    return child[1].getTreeDepth()+1; 
	}
    }

    // Get #terminal nodes below this node.
    //
    int getNumChild() {
	if(isTerminal) {
	    return 1;
	}
	return (child[0].getNumChild() + child[1].getNumChild());
    }

    // Get #times the path was sampled.
    //
    int getSampleTimes() {
	int times = parent.sampleTimes;
	if(!child[0].isTerminal) {
	    times += child[0].getSampleTimes();
	}
	if(!child[1].isTerminal) {
	    times += child[1].getSampleTimes();
	}
	return times;
    }

    // Was the path sampled.
    //
    boolean isUnique() {
	boolean unique0 = true;
	boolean unique1 = true;
	if(!child[0].isTerminal) {
	    unique0 = child[0].isUnique();
	}
	if(!child[1].isTerminal) {
	    unique1 = child[1].isUnique();
	}
	if(parent.isUnique && unique0 && unique1) {
	    return true;
	} else {
	    return false;
	}
    }

    
    // Was the path close to the edge.
    //
    boolean isBandWarning() {
	boolean warning0 = false;
	boolean warning1 = false;
	if(!child[0].isTerminal) {
	    warning0 = child[0].isBandWarning();
	}
	if(!child[1].isTerminal) {
	    warning1 = child[1].isBandWarning();
	}
	if(parent.isBandWarning || warning0 || warning1) {
	    return true;
	} else {
	    return false;
	}
    }

    // Get alphabet.
    //
    String getAlphabet() {
	if(isTerminal) {
	    return alphabet;
	}
	return child[0].getAlphabet();
    }

    // Get real site number at node 'str'.
    //
    int getSiteAt(int site, String str) {

	if(isTerminal) {
	    if(site > sequence.length()-1){ // correct for values outside of range.
		return -1; 
	    } else if(site<0) {
		return -1;
	    } else if(name.equals(str)) {   // match - return!
		return site;
	    } else {
		return -1;
	    }

	} else {
	    if(site > cellPath.length-1){
		return -1;
	    } else if(site<0) {
		return -1;
	    } else if(name.equals(str)) {
		return site;
	    } else if(child[0].getSiteAt(cellPath[site][0]-2, str)>=0) { // correct
		return child[0].getSiteAt(cellPath[site][0]-2, str);     // for the
	    } else if(child[1].getSiteAt(cellPath[site][1]-2, str)>=0) { // difference
		return child[1].getSiteAt(cellPath[site][1]-2, str);     // on numbers.
	    } else {
		return -1;
	    }
	}
    }

    // Get characters at 'site'; returns an alignment column.
    //
    char[] getCharacterAt(int site) {
	
	char[] chars;

	// Terminal node returns a character or a gap
	//
	if(isTerminal) {
	    chars = new char[1];
	    if(site<0) {
		chars[0] = '-';		
	    } else {
		chars[0] = sequence.charAt(site);
	    }
	    return chars;
	}

	// Internal node returns a vector of..
	//
	chars = new char[numChild];
	if(site<0) {                       // ..gaps..
	    for(int i=0; i<numChild; i++) {
		chars[i] = '-';
	    }
	} else {                           // ..or characters on nodes below.
	    
	    char[] chars0 = child[0].getCharacterAt(cellPath[site][0]-2);
	    char[] chars1 = child[1].getCharacterAt(cellPath[site][1]-2);

	    int c=0;
	    for(int i=0; i<chars0.length; i++) {
		chars[c++] = chars0[i];
	    }
	    for(int i=0; i<chars1.length; i++) {
		chars[c++] = chars1[i];
	    }	    
	}
	return chars;
    }

    // Get sum of viterbiEnds.
    //
    double sumViterbiEnd() {
	if(isTerminal) {
	    return 0d;
	} else {
	    double sum = child[0].sumViterbiEnd() + child[1].sumViterbiEnd() + viterbiEnd;
	    return sum;
	}
    }

    // Get sum of forwardEnds.
    //
    double sumForwardEnd() {
	if(isTerminal) {
	    return 0d;
	} else {
	    double sum = child[0].sumForwardEnd() + child[1].sumForwardEnd() + forwardEnd;
	    return sum;
	}
    }

    // Get posterior probability at 'site' for full column. This uses log's!
    //
    double[] getPostProbAt(int site) {
	
	double[] post;

	// Terminal node returns 0 (log(0) = 1) or -oo (log(-oo) = 0)
	//
	if(isTerminal) {
	    post = new double[1];
	    if(site<0) {
		post[0] = Double.NEGATIVE_INFINITY;		
	    } else {
		post[0] = 0d;
	    }
	    return post;
	}
	
	// Note: #all nodes = 2*#terminal nodes-1.
	//
	post = new double[2*numChild-1];

	// Outside of range.
	//
	if(site<0) {
	    for(int i=0; i<post.length; i++) {
		post[i] = Double.NEGATIVE_INFINITY;
	    }
	}

	// Order: child[0], parent, child[1]
	//
	else {

	    double[] post0 = child[0].getPostProbAt(cellPath[site][0]-2);
	    double[] post1 = child[1].getPostProbAt(cellPath[site][1]-2);

	    int c=0;
	    for(int i=0; i<post0.length; i++) {
		post[c++] = post0[i];
	    }
	    post[c++] = postProb[site];
	    for(int i=0; i<post1.length; i++) {
		post[c++] = post1[i];
	    }	    
	}
	return post;
	    
    }

    // Get posterior probability at 'site' for int. nodes. This uses log's!
    //
    double[] getInternalPostProbAt(int site) {
	
	double[] post = new double[numChild-1];
	
	// Outside of range.
	//
	if(site<0) {
	    for(int i=0; i<post.length; i++) {
		post[i] = Double.NEGATIVE_INFINITY;
	    }
	}

	// Order: child[0], parent, child[1]
	//
	else {
	    double[] post0 = new double[0];
	    double[] post1 = new double[0];

	    if(!child[0].isTerminal) {
		post0 = child[0].getInternalPostProbAt(cellPath[site][0]-2);
	    }
	    if(!child[1].isTerminal) {
		post1 = child[1].getInternalPostProbAt(cellPath[site][1]-2);
	    }

	    int c=0;
	    for(int i=0; i<post0.length; i++) {
		post[c++] = post0[i];
	    }
	    post[c++] = postProb[site];
	    for(int i=0; i<post1.length; i++) {
		post[c++] = post1[i];
	    }	    
	}
	return post;
	    
    }

    // Get minimum posterior probability at 'site' for int. nodes. This uses log's!
    //
    double getMinimumInternalPostProbAt(int site) {
	
	minimumProbNode = name;
	minimumProbNumber = nodeNumber;

	double minPost;
	// Outside of range.
	//
	if(site<0) {

	    minPost = 0d; 

	} else {

	    minPost = postProb[site];
	    double tmp = 0d;

	    if(!child[0].isTerminal) {
		tmp = child[0].getMinimumInternalPostProbAt(cellPath[site][0]-2);
	    }
	    if(tmp < minPost) {
		minPost = tmp;
		minimumProbNode = child[0].getMinimumInternalPostProbNode();
		minimumProbNumber = child[0].getMinimumInternalPostProbNumber();
	    } else if(tmp == minPost) {
		minimumProbNode += ", "+child[0].getMinimumInternalPostProbNode();
		minimumProbNumber = child[0].getMinimumInternalPostProbNumber();
	    }
	    if(!child[1].isTerminal) {
		tmp = child[1].getMinimumInternalPostProbAt(cellPath[site][1]-2);
	    }
	    if(tmp < minPost) {
		minPost = tmp;
		minimumProbNode = child[1].getMinimumInternalPostProbNode();
		minimumProbNumber = child[1].getMinimumInternalPostProbNumber();
	    } else if(tmp == minPost) {
		minimumProbNode += ", "+child[1].getMinimumInternalPostProbNode();
		minimumProbNumber = child[1].getMinimumInternalPostProbNumber();
	    }

	}
	return minPost;
	    
    }

    // Get minimum posterior probability at 'site' for int. nodes. This uses log's!
    //
    String getMinimumInternalPostProbNode() {
	return minimumProbNode;
    }
    int getMinimumInternalPostProbNumber() {
	return minimumProbNumber;
    }

    // Get posterior probability at 'site' for sequence 'seq'. This uses log's!
    //
    double getOnePostProbAt(int site, String seq) {

	// Outside of range.
	//
	if(site<0) {
	    return Double.NEGATIVE_INFINITY; 
	}
	if(name.equals(seq)) {
	    return postProb[site];
	}

	if(!child[0].isTerminal) {
	    double post = child[0].getOnePostProbAt(cellPath[site][0]-2,seq);
	    if(post!=Double.NEGATIVE_INFINITY) {
		return post;
	    }	
	}
	if(!child[1].isTerminal) {
	    double post = child[1].getOnePostProbAt(cellPath[site][1]-2,seq);
	    if(post!=Double.NEGATIVE_INFINITY) {
		return post;
	    }  
	}
	
	return Double.NEGATIVE_INFINITY;
    }


    // Get viterbiEnd for int. nodes. This uses log's!
    //
    double[] getInternalViterbiEnd() {
	
	double[] vit = new double[numChild-1];
	
	// Order: child[0], parent, child[1]
	//
	double[] vit0 = new double[0];
	double[] vit1 = new double[0];
	
	if(!child[0].isTerminal) {
	    vit0 = child[0].getInternalViterbiEnd();
	}
	if(!child[1].isTerminal) {
	    vit1 = child[1].getInternalViterbiEnd();
	}
	
	int c=0;
	for(int i=0; i<vit0.length; i++) {
	    vit[c++] = vit0[i];
	}
	vit[c++] = viterbiEnd;
	for(int i=0; i<vit1.length; i++) {
	    vit[c++] = vit1[i];
	}	    
	
	return vit;
	    
    }


    // Get terminal sequences.
    //
    String[] getTerminalSequences() {
	
	String[] sequences;

	if(isTerminal) {
	    sequences = new String[1];
	    sequences[0] = sequence;
	} else {
	    sequences = new String[numChild]; 
	    String[] seqs0 = child[0].getTerminalSequences();
	    String[] seqs1 = child[1].getTerminalSequences();

	    int c=0;
	    for(int i=0; i<seqs0.length; i++) {
		sequences[c++] = seqs0[i];
	    }
	    for(int i=0; i<seqs1.length; i++) {
		sequences[c++] = seqs1[i];
	    }	    
	}
	return sequences;
    }

    // Get terminal names only.
    //
    String[] getTerminalNames() {
	
	String[] names;

	if(isTerminal) {
	    names = new String[1];
	    names[0] = name;
	} else {
	    names = new String[numChild]; 
	    String[] names0 = child[0].getTerminalNames();
	    String[] names1 = child[1].getTerminalNames();

	    int c=0;
	    for(int i=0; i<names0.length; i++) {
		names[c++] = names0[i];
	    }
	    for(int i=0; i<names1.length; i++) {
		names[c++] = names1[i];
	    }	    
	}
	return names;
    }

    // Get terminal pair names only.
    //
    String[][] getTerminalPairNames() {
	
	String[][] names;

	if(child[0].isTerminal && child[1].isTerminal) {
	     names = new String[1][2];
	     names[0][0] = child[0].name;
	     names[0][1] = child[1].name;
	} else if(child[0].isTerminal) {
	    names = child[1].getTerminalPairNames();
	} else if(child[1].isTerminal) {
	    names = child[0].getTerminalPairNames();
	} else {
	    String[][] names0 = child[0].getTerminalPairNames();
	    String[][] names1 = child[1].getTerminalPairNames();
	    names = new String[names0.length+names1.length][2]; 
	    int c=0;
	    for(int i=0; i<names0.length; i++) {
		names[c][0] = names0[i][0];
		names[c++][1] = names0[i][1];		
	    }
	    for(int i=0; i<names1.length; i++) {
		names[c][0] = names1[i][0];
		names[c++][1] = names1[i][1];
	    }	    
	}
	return names;
    }

    // Get internal names only.
    //
    String[] getInternalNames() {
	
	String[] names = new String[numChild-1];
	if(numChild<=2) { // no internal nodes below.
	    names[0] = name;
	}
	
	else {

	    String[] names0 = new String[0];
	    String[] names1 = new String[0];

	    if(!child[0].isTerminal) {
		names0 = child[0].getInternalNames();
	    }
	    if(!child[1].isTerminal) {
		names1 = child[1].getInternalNames();
	    }
    
	    int c=0;
	    for(int i=0; i<names0.length; i++) {
		names[c++] = names0[i];
	    }
	    names[c++] = name;
	    for(int i=0; i<names1.length; i++) {
		names[c++] = names1[i];
	    }	    
	}
	return names;
    }

    // Get all names
    //
    String[] getAllNames() {
	
	String[] names;

	if(isTerminal) {
	    names = new String[1];
	    names[0] = name;	    
	} 

	// Order: child[0], parent, child[1]
	//
	else {

	    names = new String[2*numChild-1]; 
	    String[] names0 = child[0].getAllNames();
	    String[] names1 = child[1].getAllNames();

	    int c=0;
	    for(int i=0; i<names0.length; i++) {
		names[c++] = names0[i];
	    }
	    names[c++] = name;
	    for(int i=0; i<names1.length; i++) {
		names[c++] = names1[i];  
	    }	    
	}
	return names;
    }
    
    // Needed for graphics (used by getNodeNameAtXY).
    //
    void setTreeXY(int x, int y) {
	treeX = x;
	treeY = y;
    }

    // Get node that was clicked.
    //
    String getNodeNameAtXY(int x, int y) {
	int prec = 4; // precision
	if(Math.abs(treeX-x)<prec && Math.abs(treeY-y)<prec) {
	    return parent.name;
	}
	if(!child[1].isTerminal) {
	    String nodeName = child[1].getNodeNameAtXY(x,y);
	    if(!nodeName.equals("")){
		return nodeName;
	    }
	}
	if(!child[0].isTerminal) {
	    String nodeName = child[0].getNodeNameAtXY(x,y);
	    if(!nodeName.equals("")){
		return nodeName;
	    }
	}
	return new String();
    }

    // Get node by name.
    //
    AlignmentNode getNodeNamed(String node) {
	if(name.equals(node)) {
	    return parent;
	}
	if(!child[0].isTerminal) {
	    AlignmentNode kid = child[0].getNodeNamed(node);
	    if(kid.name.equals(node)){
		return kid;
	    }
	}
	if(!child[1].isTerminal) {
	    AlignmentNode kid = child[1].getNodeNamed(node);
	    if(kid.name.equals(node)){
		return kid;
	    }
	}
	return parent;
    }
    
    private void writeObject(ObjectOutputStream s)
	throws IOException {

	s.writeObject("0.2");
	s.writeBoolean(ProAlign.isDna);
	s.writeBoolean(isTerminal);
	s.writeObject(name);
	s.writeObject(tree);
	s.writeObject(parent);
	s.writeInt(numChild);
	
	s.writeInt(charProb.length);
	s.writeInt(charProb[0].length);
	for(int i=0; i<charProb.length; i++) {
	    for(int j=0; j<charProb[0].length; j++) {
		s.writeDouble(charProb[i][j]);
	    }	
	}

	if(isTerminal) {
	    s.writeObject(sequence);
	    s.writeObject(alphabet);

	} else {
	    s.writeInt(nodeNumber);
	    s.writeInt(treeX);
	    s.writeInt(treeY);
	    s.writeObject(child[0]);
	    s.writeObject(child[1]);

	    s.writeDouble(viterbiEnd);
	    s.writeDouble(forwardEnd);

	    s.writeInt(postProb.length);
	    for(int i=0; i<postProb.length; i++) {
		s.writeDouble(postProb[i]);
	    }
	 
	    s.writeInt(cellPath.length);
	    s.writeInt(cellPath[0].length);
	    for(int i=0; i<cellPath.length; i++) {
		for(int j=0; j<cellPath[0].length; j++) {
		    s.writeInt(cellPath[i][j]);
		}	
	    }
	}
    }

     private void readObject(ObjectInputStream s)
	 throws IOException, ClassNotFoundException  {

	 // version number can be used for backward compatibility! 
	 String version = (String) s.readObject(); 
	 ProAlign.isDna = s.readBoolean();
	 isTerminal = s.readBoolean();
	 name = (String) s.readObject();
	 tree = (String) s.readObject();
	 parent = (AlignmentNode) s.readObject();
	 numChild = s.readInt();
	
	 int k = s.readInt();
	 int l = s.readInt();
	 charProb = new double[k][l];
	 for(int i=0; i<charProb.length; i++) {
	     for(int j=0; j<charProb[0].length; j++) {
		 charProb[i][j] = s.readDouble();
	     }	
	 }

	if(isTerminal) {
	    sequence = (String) s.readObject();
	    alphabet = (String) s.readObject();

	} else {
	    nodeNumber = s.readInt();
	    treeX = s.readInt();
	    treeY = s.readInt();
	    child = new AlignmentNode[2];
	    child[0] = (AlignmentNode) s.readObject();
	    child[1] = (AlignmentNode) s.readObject();

	    viterbiEnd = s.readDouble();
	    forwardEnd = s.readDouble();

	    k = s.readInt(); 
	    postProb = new double[k];
	    for(int i=0; i<postProb.length; i++) {
		postProb[i] = s.readDouble();
	    }
	 
	    k = s.readInt();
	    l = s.readInt();
	    cellPath = new int[k][l];
	    for(int i=0; i<cellPath.length; i++) {
		for(int j=0; j<cellPath[0].length; j++) {
		    cellPath[i][j] = s.readInt();
		}	
	    }
	}
     }
}
















