/*  
 *  src/org/metaqtl/MetaQtlData.java
 * 
 *  $Author: veyrieras $, $Date: 2006-10-17 15:41:54 $, $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;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;

import org.metaqtl.bio.IBioOntology;
import org.metaqtl.bio.IBioOntologyTerm;
/**
 * QTL meta analysis first requires to gather the QTL according
 * to the group trait they belong and then to format the QTL data
 * into data points to perfom clustering. This class is dedicated
 * to this task. 
 * <blockquote><pre>
 *    
 *    Chromosome chrom = ...
 *    // Compute the standard deviations of the QTL positions 
 *    // on the chromosome.
 *    chrom.computeQtlSD(IMetaQtlConstants.SD_MODE_AVAILABLE);
 * 
 *    MetaQtlData qtlData = new MetaQtlData();
 *    qtlData.qtls = chroms.qtls;
 * 
 *    // First we clusterize
 *    // QTL w.r.t to the trait group information
 *    qtlData.doTraitGroupClustering();
 *    // Then we loop on each trait group 
 *    for(int i=0;i&lt;qtlData.ntg;i++) {
 *       Double[][] XSD = qtlData.getDataPoints(i);
 *       Double[] X     = X[0]; // the qtl positions
 *       Double[] SD    = X[1]; // the qtl standard deviations.
 *       // do something...
 *    }
 * 
 * </pre></blockquote>
 */
public class MetaQtlData {
	/**
	 * The QTL.
	 */
	protected Qtl[] qtls=null;
	/**
	 * The number of trait group.
	 */
	protected int ntg=0;
	/**
	 * The number of QTL in each
	 * trait group.
	 */
	protected int[] ntgQtls=null;
	/**
	 * The trait group names.
	 */
	protected String[] tgnames=null;
	/**
	 * The indices of the QTL in the trait
	 * groups.
	 */
	protected int[][] tgidx=null;
	
	/**
	 * Create a new instance of <code>MetaQtlData</code> which
	 * will represent the given array of <code>Qtl</code>.
	 * @param qtls
	 */
	public MetaQtlData(Qtl[] qtls) {
		this.qtls = qtls;
	}
	/**
	 * According to the values of the QTLTrait.group
	 * attribute of the trait attribute of the qtls
	 * this methos clusterize the qtls in trait
	 * groups.
	 */
	public void doTraitGroupClustering() {
		int i;
		Enumeration e;
		Hashtable hash;
		ArrayList list,undef=null;
		
		if (qtls==null) return;
		
		hash = new Hashtable();
		
		for(i=0;i<qtls.length;i++) {
			
			if (qtls[i].trait != null) {
				
				if (qtls[i].trait.group != null) {
				
					if (!hash.containsKey(qtls[i].trait.group)) {
						list = new ArrayList();
						hash.put(qtls[i].trait.group, list);
					} else {
						list = (ArrayList) hash.get(qtls[i].trait.group);
					}
					list.add(new Integer(i));
					
				} else {
					
					if (!hash.containsKey(qtls[i].trait.name)) {
						list = new ArrayList();
						hash.put(qtls[i].trait.name, list);
					} else {
						list = (ArrayList) hash.get(qtls[i].trait.name);
					}
					list.add(new Integer(i));
					
				}
				
			} else {
				
				if (undef==null) undef = new ArrayList();
				undef.add(new Integer(i));
 			}
		}
		ntg = hash.size();
		if (ntg > 0)  {
			tgnames = new String[ntg];
			ntgQtls = new int[ntg];
			tgidx   = new int[ntg][];
			
			i=0;e=hash.keys();
			/*
			 * Store the name of the group, 
			 * get the the number of QTL per
			 * group and store their indices.
			 */
			while(e.hasMoreElements()) {
				/* Get the name of the group */
				tgnames[i]=(String) e.nextElement();
				/* Get the indices of the QTL which belongs
				 * to this group */
				list = (ArrayList) hash.get(tgnames[i]);
				ntgQtls[i] = list.size();
				tgidx[i]   = new int[list.size()];
				for(int j=0;j<list.size();j++) {
					tgidx[i][j] = ((Integer)list.get(j)).intValue();
				}
				i++;
			}
		}
		if (undef!=null) {
			/*
			 * Log the QTL for which any trait name or group
			 * is defined.
			 */
			 for(i=0;i<undef.size();i++) {
			 	System.err.println("[ WARNING ]");
			 	System.err.println("Any Trait or Group Trait defined for QTL : " + qtls[((Integer)undef.get(i)).intValue()].name);
			 }
		}
	}
	/**
	 * For a given trait group indice this methods returns
	 * the data points as a matrix of Double X[0..1][0..n-1]
	 * where X[0] is the vector of the n position values
	 * and X[1] is the vector of the n standard deviations
	 * of the positions. 
	 * @param tg the trait group indice
	 * @return the data points.
	 */
	public Double[][] getDataPoints(int tg, boolean ignore) {
		int j,n;
		Double[][] xy=null;
		ArrayList xl=null,sdl=null;
		/* First count the number of positions
		 * to consider in the clustering
		 */
		for(n=j=0;j<ntgQtls[tg];j++) {
			
			if (!qtls[tgidx[tg][j]].isIgnore() || !ignore) {
				
				n++;
				if (xl==null) {
					xl=new ArrayList();
					sdl=new ArrayList();
				}
				xl.add(new Double(qtls[tgidx[tg][j]].position));
				sdl.add(new Double(qtls[tgidx[tg][j]].sd_position));
				
			}
			
		}
		
		if (n > 0) {
			xy = new Double[2][n];
			xl.toArray(xy[0]);
			sdl.toArray(xy[1]);
		}
		
		return xy;
	}
	/**
	 * According to the given trait ontology this methods
	 * clusterize the QTL in group in such a way that each
	 * group represents a trait term in the ontology.
	 * 
	 * @param ontology the trait ontology.
	 */
	public void doTraitOntologyClustering(IBioOntology ontology) {
		
		if (ontology==null) 
			doTraitGroupClustering();
		
		IBioOntologyTerm root,term;
		Hashtable hash,parent,move;
		ArrayList list,clist,undef=null;
		Enumeration e;
		
		hash   = new Hashtable();
		parent = new Hashtable();
		move   = new Hashtable();
		
		root = ontology.getRoot();
		
		for(int i=0;i<qtls.length;i++) {
			
			if (qtls[i].trait != null) {
				
				String termName = qtls[i].trait.name;
				
				term = ontology.getTerm(termName);
				
				if (term != null) {
					
					//System.out.println(termName + " " + term.getParentTerm().getName());
				
					if (!hash.containsKey(term)) {
						
						list = new ArrayList();
						hash.put(term, list);
						
						clist = null;
						
						if (!parent.containsKey(term.getParentTerm())) {
							
							if (term.getParentTerm() != root) {
								
								clist = new ArrayList();
								parent.put(term.getParentTerm(), clist);
								
							}
							
						} else {
							
							clist = (ArrayList) parent.get(term.getParentTerm());
							
						}
						
						if (clist != null) clist.add(term);
						
					} else {
						
						list = (ArrayList) hash.get(term);
						
					}
					
					list.add(new Integer(i));
				
				} else {
					
					if (undef==null) undef = new ArrayList();
					undef.add(new Integer(i));
					
				}
				
			}
			
		}
		/* Is there any parent for which the parent is one
		 * of the key element of parents ?*/
		e = parent.keys();
		while(e.hasMoreElements()) {
			term = (IBioOntologyTerm) e.nextElement();
			moveUpParentTerm(term, parent, move);
		}
		
		e   = parent.keys();
		ntg = 0;
		while(e.hasMoreElements()) {
			term  = (IBioOntologyTerm) e.nextElement();
			/* Get the children terms */
			clist = (ArrayList) parent.get(term);
			if (!move.containsKey(term)) 
				ntg++;
		}
		
		tgnames = new String[ntg];
		ntgQtls = new int[ntg];
		tgidx   = new int[ntg][];
		
		e   = parent.keys();
		ntg = 0;
		while(e.hasMoreElements()) {
			term         = (IBioOntologyTerm) e.nextElement();
			/* Get the children terms */
			clist = (ArrayList) parent.get(term);
			if (!move.containsKey(term)) {
				tgnames[ntg]=term.getName();
				//System.out.println("GROUP " + term.getName());
				ntgQtls[ntg]=0;
				for(int i=0;i<clist.size();i++) {
					term = (IBioOntologyTerm) clist.get(i);
					list = (ArrayList) hash.get(term);
					//System.out.println(">>TRAIT " + term.getName());
					ntgQtls[ntg]+=list.size();
				}
				tgidx[ntg] = new int[ntgQtls[ntg]];
				for(int i=0,j=0;i<clist.size();i++) {
					term = (IBioOntologyTerm) clist.get(i);
					list = (ArrayList) hash.get(term);
					for(int k=0;k<list.size();k++,j++) {
						tgidx[ntg][j] = ((Integer)list.get(k)).intValue();
					}
				}
				ntg++;
			}			
		}
		if (undef!=null) {
			/*
			 * Log the QTL for which any trait name or group
			 * is undefined.
			 */
			 for(int i=0;i<undef.size();i++) {
			 	//System.err.println("[ WARNING ]");
			 	//System.err.println("Any Trait Term defined for QTL : " + qtls[((Integer)undef.get(i)).intValue()].name);
			 }
		}
	}
	/**
	 * @param term
	 * @param parent
	 * @param hash
	 */
	private void moveUpParentTerm(IBioOntologyTerm term, Hashtable parent, Hashtable move) {
		
		ArrayList plist,clist;
		IBioOntologyTerm p;
		
		p = term.getParentTerm();
		
		if (p != null && parent.containsKey(p)) {
			
			if (move.containsKey(p)) {
			
				p = (IBioOntologyTerm) move.get(p);
				
			}
			/* Get the list of children of the upper parent */
			plist = (ArrayList) parent.get(p);
			/* Get the  list of children of teh current parent */
			clist = (ArrayList) parent.get(term);
			/* and add it to the list of the upper parent */
			for(int i=0;i<clist.size();i++) {
				plist.add(clist.get(i));
			}
			
			move.put(term, p);
		}
	}
	/**
	 * @return
	 */
	public int getQtlNumber() {
		int nqtl = 0;
		if (ntgQtls != null) {
			for(int i=0;i<ntg;i++)
				nqtl+=ntgQtls[i];
		}
		return nqtl;
	}
	/**
	 * Manage the missing data.
	 * <code>opt</code> to parametrize the behaviour of the method to
	 * do the imputation.
	 * 
	 * @param opt the option for missing data management.
	 
	 */ 
	public void manageMissingData(int opt) {
		int i,j,sd_n;
		double sd_mean;
		
		/* Loop on trait groups */
		for(i=0;i<ntg;i++) {
			/* Loop on members */
			sd_mean = 0.0;
			sd_n    = 0;
			for(j=0;j<ntgQtls[i];j++) {
				//System.out.println(qtls[tgidx[i][j]].name+ " " + qtls[tgidx[i][j]].sd_position);
				if (qtls[tgidx[i][j]].sd_position <= 0.0) {
					/* Means that the standard deviation of this QTL
					 * is not (properly) defined, then flag it as
					 * missing
					 */
					 qtls[tgidx[i][j]].setIgnore(true);
				} else {
					sd_mean += qtls[tgidx[i][j]].sd_position;
					sd_n++;
				}				
			} /* end loop on QTL inside trait group */
			/* Compute the mean of the standard deviation */
			if (sd_n > 0) sd_mean /= (double) sd_n;
			else sd_mean = 0.0;
			
			if (sd_mean > 0.0 && opt == IMetaQtlConstants.CI_MISS_IMPUTATION) {
				for(j=0;j<ntgQtls[i];j++) {
					if (qtls[tgidx[i][j]].isIgnore()) {
						qtls[tgidx[i][j]].setIgnore(false);
						qtls[tgidx[i][j]].sd_position = sd_mean;						
					}
				}
			} /* end loop on QTL inside trait group */			
		} /* end loop on trait group */
		
	}
	/**
	 * Returns the number of trait groups for this data set as
	 * defined by the clustering method.
	 * 
	 * @return the number of trait groups for this data set
	 * 
	 * @see #doTraitGroupClustering()
	 * @see #doTraitOntologyClustering(IBioOntology)
	 */
	public int getTraitClusterNumber() {
		return ntg;
	}
	
	public String getTraitClusterName(int i) {
		return tgnames[i];
	}
	/**
	 * Returns the indices of the Qtl for the given
	 * trait cluster indice <code>i</code>.
	 * 
	 * @param i the indice of the trait cluster.
	 * @return the indices of the Qtl which belongs to this cluster.
	 */
	public int[] getQtlIdx(int i) {
		int k,j;
		int[] idx=null;
		
		for(k=j=0;j<ntgQtls[i];j++)
			if (!qtls[tgidx[i][j]].isIgnore())
				k++;
		
		if (k>0) {	
			idx = new int[k];
			for(k=j=0;j<ntgQtls[i];j++)
				if (!qtls[tgidx[i][j]].isIgnore())
					idx[k++]=tgidx[i][j];
		}
		
		return idx;
	}
	/**
	 * Returns the names of the trait clusters.
	 * 
	 * @return the names of the trait clusters. 
	 */
	public String[] getTraitClusterNames() {
		return this.tgnames;
	}
	/**
	 * Returns the size of the trait cluster i.
	 * 
	 * @param i 
	 * @return 
	 */
	public int getTraitClusterSize(int i) {
		return this.ntgQtls[i];
	}
	/**
	 * Returns the j^th QTL in the i^th cluster. 
	 * 
	 * @param i
	 * @param j
	 * @return
	 */
	public Qtl getQtl(int i, int j) {
		return qtls[tgidx[i][j]];
	}
}
