/*  
 *  src/org/metaqtl/graph/QtlTreeLayer.java
 * 
 *  $Author: veyrieras $, $Date: 2006-10-17 15:41:55 $, $Version$
 *
 *  MetaQTL : a Java package for QTL Meta-Analysis
 * 
 *  Copyright (C) 2003-2006  Jean-Baptiste Veyrieras, INRA, France.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA * 
 */
package org.metaqtl.graph;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;

import org.metaqtl.QtlPartition;
import org.metaqtl.Tree;
import org.metaqtl.TreeNode;
import org.metaqtl.numrec.NumericalUtilities;

/**
 * 
 */
public class QtlTreeLayer  extends Layer {
	
	/**
	 * The tree attached to the layer.
	 */
	private QtlPartition qtlPart;
	
	private double point_width, point_height;
	
	private double   ci_max;
	
	private double x_ci_qtl;
	
	private boolean[] label_status;
	
	private int[]    qtl_idx;
	
	private int[]    rqtl_idx;
	
	private double[] x_qtls;
	
	private double[] x_label_qtls;
	
	private double[] y_qtls;
	
	private double[] y_label_qtls;
	
	private double[] ci_qtls;
	
	private double[][] pr_qtls;
	
	private TextLayout[] label_qtls;

	private ChromAxe chromAxe;

	private double x_pr_qtl;
	
	private double x_tree;
	/**
	 * @param x
	 * @param y
	 */
	public QtlTreeLayer(double x, double y) {
		super(x, y);
	}
	/* (non-Javadoc)
	 * @see org.thalia.metaqtl.graph.Layer#attach(java.lang.Object)
	 */
	public void attach(Object object) {
		if (object instanceof QtlPartition) {
			qtlPart = (QtlPartition) object;
		}
	}
	/* (non-Javadoc)
	 * @see org.thalia.metaqtl.graph.Layer#build(java.awt.Graphics2D)
	 */
	public void build(Graphics2D graph) {
		int i;
		double max;
		double[] tmp;
		int[] idx;
		// First get the positions of the QTL along the chromosome
		x_qtls = new double[qtlPart.nqtl];
		y_qtls = new double[qtlPart.nqtl];
		y_label_qtls = new double[qtlPart.nqtl];
		x_label_qtls = new double[qtlPart.nqtl];
		tmp      = new double[qtlPart.nqtl+1];
		qtl_idx  = new int[qtlPart.nqtl+1];
		rqtl_idx = new int[qtlPart.nqtl];
		for(i=0;i<qtlPart.nqtl;i++) {
			tmp[i+1]=chromAxe.transformY(qtlPart.qtlPos[i]);
		}
		NumericalUtilities.indexx(qtlPart.nqtl, tmp, qtl_idx);
		for(i=0;i<qtlPart.nqtl;i++) {
			y_qtls[i]            = tmp[qtl_idx[i+1]];
			x_qtls[i]            = MetaGraphPar.QTL_PCH_SIZE/2;
			y_label_qtls[i]      = tmp[qtl_idx[i+1]];
			qtl_idx[i]           = qtl_idx[i+1] - 1;
			rqtl_idx[qtl_idx[i]] = i;
		}
		// Then the names of the qtl
		label_qtls = new TextLayout[qtlPart.nqtl];
		for(i=0;i<qtlPart.nqtl;i++) {
			label_qtls[i]    = new TextLayout(qtlPart.qtlNames[qtl_idx[i]], MetaGraphPar.QTL_NAME_FONT, graph.getFontRenderContext());
			y_label_qtls[i] -= getLabelHeight(i)/2;
		}
		// Then the CI and its maximum value 
		ci_max=0;
		ci_qtls = new double[qtlPart.nqtl];
		for(i=0;i<qtlPart.nqtl;i++) {
			ci_qtls[i] = chromAxe.scaleHeight(qtlPart.qtlCI[qtl_idx[i]]);
			if (ci_qtls[i] > ci_max) ci_max = ci_qtls[i];
		}
		// Rescale the ci relatively to the max
		for(i=0;i<qtlPart.nqtl;i++) ci_qtls[i] /= ci_max;
		// Now, if defined get the memberships of the QTL
		if (qtlPart.proba != null) {
			for(i=0;i<qtlPart.nqtl;i++)
				if (qtlPart.proba[qtl_idx[i]]==null || qtlPart.proba[qtl_idx[i]].length<1) break;
			if (i == qtlPart.nqtl) {
				pr_qtls = new double[qtlPart.nqtl][0];
				for(i=0;i<qtlPart.nqtl;i++)
					pr_qtls[i]=qtlPart.proba[qtl_idx[i]];
			}
		}
		updateLayerDimension();
	}
	
	private void updateLayerDimension() {
		int i;
		double w,max;
		
		// First add the width of the QTL locations.
		width += MetaGraphPar.QTL_PCH_SIZE;
		// Then the width between the QTL locations and their labels
		width += MetaGraphPar.QTL_NAME_SPACE;
		// Then get the max width of the qtl labels
		max=0;
		for(i=0;i<x_label_qtls.length;i++) {
			w=getLabelWidth(i);
			if (w>max) max=w;
		}
		// Now, set the x values of the labels
		for(i=0;i<x_label_qtls.length;i++) {
			x_label_qtls[i]  = width;
			//x_label_qtls[i] += (max-getLabelWidth(i))/2; Uncomment for center justified.
			//x_label_qtls[i] += max-getLabelWidth(i); Uncomment for right justified.
		}
		width+=max;
		// Set the y values of the labels
		checkLabelLocation();
		resolveLabelLocation();
		// Then set the height
		height=getYMax()-getYMin();
		// Then set the x and y values of the CI
		// Then the width between the QTL locations and their labels
		width += MetaGraphPar.QTL_NAME_SPACE;	
		x_ci_qtl = width;
		width   += MetaGraphPar.QTL_CI_WIDTH_CEX * ci_max;
		// If defined add the probabilities
		if (this.pr_qtls != null) {
			width    += MetaGraphPar.QTL_CI_WIDTH_CEX * ci_max *0.1;
			x_pr_qtl  = width; 
			width    += MetaGraphPar.QTL_CI_WIDTH_CEX * ci_max;
			width    += MetaGraphPar.QTL_CI_WIDTH_CEX * ci_max*0.1;
		}
		// Finally the tree 
		Tree tree = qtlPart.tree;
		if (tree != null) {
			// Get the distance from the leaves to the root of the tree.
			double[] hleaf = tree.getLeafHeights();
			// Get the max
			max=0.0;
			for(i=0;i<hleaf.length;i++) {
				if (max < hleaf[i]) max=hleaf[i];				
			}
			// Init the coordonates
			tree.updateLeafIdx(rqtl_idx);
			tree.initNodeIdx();
			TreeNode root = tree.getRoot();
			x_tree=width;
			width+=MetaGraphPar.QTL_TREE_SCALE*max*1.05;
			root.setX(width);
			root.setY(root.getCentroid(y_label_qtls, ci_qtls));
			initTreeXY(root, getYMin(), height);
		}
	}
	
	
	/**
	 * @return
	 */
	private double getLabelTotHeight() {
		return y_label_qtls[y_label_qtls.length-1]-y_label_qtls[0];
	}
	
	private void checkLabelLocation() {
		double y1,y2;
		
		label_status = new boolean[label_qtls.length];
		for(int i=0;i<label_qtls.length;i++) label_status[i]=true;
		
		for(int i=0;i<label_qtls.length-1;i++) {
			y1  =  y_label_qtls[i];
			y1 += getLabelHeight(i);
			y1 += y1*0.1;
			y2  = y_label_qtls[i+1];
			label_status[i]&=(y1<=y2);
			label_status[i+1]&=(y1<=y2);			
		}
	}
	
	
	private double getLabelHeight(int i) {
		return label_qtls[i].getBounds().getHeight();
	}
	
	private double getLabelWidth(int i) {
		return label_qtls[i].getBounds().getWidth();
	}
	/**
	 * 
	 * @return
	 */
	private void resolveLabelLocation() {
		int i,j,nl;
		double y_top, y_bottom,space,require,right,left,y_box;
		boolean ok=true;
		
		// Go along the chromosome, find the conflicts and
		// try to solve them.
		for(i=0;i<label_qtls.length;) {
			nl=1;
			if (!label_status[i]) {
				if (i>0) y_top=y_label_qtls[i-1]+getLabelHeight(i-1);
				else y_top=y_label_qtls[i]; 
				// How many label are implied in the conflict ?
				for(j=i+1;j<label_qtls.length;j++,nl++)
					if (label_status[j]) break;
				if (i+nl<label_qtls.length)
					y_bottom=y_label_qtls[i+nl];
				else y_bottom=y_label_qtls[i+nl-1]+getLabelHeight(i+nl-1);
				space=y_bottom-y_top;
				// What is the amout of space necessary to solve
				// this conflict ?
				require=0.0; // a slight shift...
				for(j=i;j<i+nl;j++)
					require+=getLabelHeight(j);
				if (space < require) {
					// shift the rigth and left label_qtls in order
					// to obtain as many space as require.
					if (i+nl<label_qtls.length-1) {
						for(j=i-1;j>=0;j--) {
							y_label_qtls[j]-=(require-space)/2.0;
							if (j>0 && y_label_qtls[j-1]+getLabelHeight(j-1) < y_label_qtls[j])
								break;
						}
					} else {
						for(j=i;j<label_qtls.length;j++) 
							y_label_qtls[j]+=(require-space);
					}
					if (i>0) {
						for(j=i+nl;j<label_qtls.length;j++) {
							y_label_qtls[j]+=(require-space)/2.0;
							if (j+1<label_qtls.length && y_label_qtls[j+1] > y_label_qtls[j]+getLabelHeight(j))
								break;
						}
					} else {
						for(j=0;j<nl;j++)
							y_label_qtls[j]-=(require-space);
					}
					// Now re-position the label_qtls
					if (i>0) y_top=y_label_qtls[i-1] + getLabelHeight(i-1);
					else y_top=y_label_qtls[i]; 
					for(j=0;j<nl;j++) {
						y_label_qtls[i+j] = y_top;
						y_top        += getLabelHeight(i+j);
					}
					y_bottom=y_top;
					if (i>0) y_top=y_label_qtls[i-1] + getLabelHeight(i-1);
					else y_top=y_label_qtls[i];
				} else {
					// Then divide the space in as many boxes as the number
					// of label_qtls to position.
					y_box = space/(double)nl;
					for(j=0;j<nl;j++) {
						y_label_qtls[i+j] = y_top + (y_box-getLabelHeight(i+j))/2.0;
						y_top        += y_box;
					}
				}
			}
			i+=nl;
		}
	}
	
	
	/**
	 * 
	 * @param root
	 * @param xroot
	 * @param h
	 */
	private void initTreeXY(TreeNode root, double ymin, double h) {
		if (root.leaf) return;
		TreeNode[] children = root.getChildren();
		double[]   tmp      = new double[children.length+1];
		int[]      idx      = new int[children.length+1];
		for(int i=0;i<children.length;i++) {
			tmp[i+1]=children[i].getCentroid(y_label_qtls, ci_qtls);
		}
		NumericalUtilities.indexx(children.length, tmp, idx);
		double yold=ymin;
		for(int i=1;i<=children.length;i++) {
			children[idx[i]-1].setX(root.getX() - MetaGraphPar.QTL_TREE_SCALE*children[idx[i]-1].getDist());
			/*double w = (double)children[idx[i]-1].card/(double)root.card;
			w *= h;*/
			children[idx[i]-1].setY(tmp[idx[i]]);
			//initTreeXY(children[idx[i]-1], yold, w);
			initTreeXY(children[idx[i]-1], 0, 0);
			//yold += w;
		}
	}
	/**
	 * 
	 * @param graph
	 * @param root
	 */
	private void drawTree(Graphics2D graph, TreeNode root) {
		graph.setColor(Color.BLACK);
		double xx;
		// The point which represents the current node
		TreeNode[] children = root.getChildren();
		if (children!=null) {
			for(int i=0;i<children.length;i++) {
				// Then the connection to the children
				GeneralPath edge = new GeneralPath(GeneralPath.WIND_EVEN_ODD, 3);
			    edge.moveTo((float)root.getX(), (float)root.getY());
			    edge.lineTo((float)root.getX(), (float)children[i].getY());
			    edge.lineTo((float)children[i].getX(), (float)children[i].getY());
			    graph.setStroke(new BasicStroke((float)getLabelHeight(0)/2));
			    graph.draw(edge);
			    if (children[i].leaf) {
			    	graph.setColor(Color.GRAY);
					Line2D.Double line = new Line2D.Double(children[i].getX(), children[i].getY(), x_tree, children[i].getY());
			    	graph.draw(line);
			    	graph.setColor(Color.BLACK);
			    }
			    drawTree(graph, children[i]);
			}			
		}
		
	}

	/* (non-Javadoc)
	 * @see org.thalia.metaqtl.graph.Layer#draw(java.awt.Graphics2D)
	 */
	public void draw(Graphics2D graph) {
		int i=0;
		float xx,yy,tt;
		
		Color           c     = graph.getColor();
		AffineTransform t     = graph.getTransform();
		AffineTransform trans = new AffineTransform();
		trans.translate(x,y);
		graph.transform(trans);
		
		graph.setColor(Color.BLACK);
		// The positions
		for(i=0;i<y_qtls.length;i++) {
			GeneralPath pos = new GeneralPath(GeneralPath.WIND_EVEN_ODD,3);
			pos.moveTo((float)(x_qtls[i]+MetaGraphPar.QTL_PCH_SIZE), (float)(y_qtls[i]-getLabelHeight(i)/2));
			pos.lineTo((float)x_qtls[i], (float)y_qtls[i]);
			pos.lineTo((float)(x_qtls[i]+MetaGraphPar.QTL_PCH_SIZE), (float)(y_qtls[i]+getLabelHeight(i)/2));
			pos.closePath();
			graph.fill(pos);
		}
		// Draw a connection between the position and the label
		for(i=0;i<y_qtls.length;i++) {
			GeneralPath conex = new GeneralPath(GeneralPath.WIND_EVEN_ODD,4);
			xx=(float)(x_qtls[i]+MetaGraphPar.QTL_PCH_SIZE);
			yy=(float)y_qtls[i];
			conex.moveTo(xx, yy);
			tt=(float)Math.abs(xx-x_label_qtls[i]);
			xx+=tt*0.1;
			conex.lineTo(xx, yy);
			xx=(float)x_label_qtls[i];
			xx-=tt*0.1;
			yy=(float)y_label_qtls[i];
			conex.lineTo(xx, yy);
			xx=(float)x_label_qtls[i];
			conex.lineTo(xx, yy);
			graph.draw(conex);
		}
		// The labels
		for(i=0;i<y_label_qtls.length;i++) {
			label_qtls[i].draw(graph, (float) x_label_qtls[i], (float) y_label_qtls[i]);
		}
		// The CIs
		for(i=0;i<ci_qtls.length;i++) {
			double max = MetaGraphPar.QTL_CI_WIDTH_CEX*ci_max;
			xx=(float)(max*ci_qtls[i]);
			Rectangle2D.Double ci = new Rectangle2D.Double(x_ci_qtl, y_label_qtls[i]-getLabelHeight(i), xx, getLabelHeight(i));
			graph.fill(ci);
			ci = new Rectangle2D.Double(x_ci_qtl, y_label_qtls[i]-getLabelHeight(i), max, getLabelHeight(i));
			graph.draw(ci);
		}
		// The probs
		if (pr_qtls != null) {
			Rectangle2D.Double ci;
			double max = MetaGraphPar.QTL_CI_WIDTH_CEX*ci_max;
			for(i=0;i<pr_qtls.length;i++) {
				if (pr_qtls[i] != null) {
					xx=(float)x_pr_qtl;
					for(int j=0;j<pr_qtls[i].length;j++) {
						graph.setColor(MetaGraphPar.QTL_PALETTE[j]);
						ci = new Rectangle2D.Double(xx, y_label_qtls[i]-getLabelHeight(i), max*pr_qtls[i][j], getLabelHeight(i));
						graph.fill(ci);
						xx+=max*pr_qtls[i][j];
					}
					graph.setColor(Color.BLACK);
					ci = new Rectangle2D.Double((float)x_pr_qtl, y_label_qtls[i]-getLabelHeight(i), max, getLabelHeight(i));
					graph.draw(ci);
				}
			}			
		}
		if (qtlPart.tree != null) {
			// Finally the tree
			drawTree(graph, qtlPart.tree.getRoot());
		}
		
		graph.setTransform(t);
		graph.setColor(c);
		
	}

	/* (non-Javadoc)
	 * @see org.thalia.metaqtl.graph.Layer#getYMin()
	 */
	public double getYMin() {
		return y+y_label_qtls[0];
	}

	/* (non-Javadoc)
	 * @see org.thalia.metaqtl.graph.Layer#getYMax()
	 */
	public double getYMax() {
		return y+y_label_qtls[y_label_qtls.length-1];
	}
	/**
	 * @param axe
	 */
	public void setChromAxe(ChromAxe axe) {
		this.chromAxe = axe;		
	}

}
