/*  
 *  src/org/metaqtl/factory/MetaQtlAnalysisFactory.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.factory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;

import org.metaqtl.EMResult;
import org.metaqtl.MetaQtlAnalysis;
import org.metaqtl.MetaQtlResult;
import org.metaqtl.Qtl;
import org.metaqtl.TreeNode;
import org.metaqtl.bio.util.NumberFormat;
/**
 *A factory to deal with the result of a QTL meta-analysis.
 */
public final class MetaQtlAnalysisFactory {
	
	/**
	 * 
	 * @param stream
	 * @return
	 * @throws IOException
	 */
	public static MetaQtlAnalysis read(InputStream stream) throws IOException {
		InputStreamReader reader = new InputStreamReader(stream);
		return read(reader);
	}
	/**
	 * 
	 * @param reader
	 * @return
	 * @throws IOException
	 */
	public static MetaQtlAnalysis read(Reader reader) throws IOException {
		
		if (reader==null) return null;
		
		int qtlNumber,qtlIdx=0,lineNumber;
		boolean first;
		MetaQtlResult result=null;
		MetaQtlAnalysis analysis = null;
		String line,chromName = null, traitName=null;
		StringTokenizer tokenizer;
		Hashtable traitTable;
		Hashtable chromTable; // the results by chromosome names
		Hashtable qtlTable;  // the qtl list by chromosome names
		ArrayList qtlList;
		ArrayList emResultList;
		BufferedReader buffer    = new BufferedReader(reader);
		
		chromTable		= new Hashtable();
		qtlTable   		= new Hashtable();
		qtlList			= null;
		emResultList	= null;
		
		qtlNumber	= 0;
		first		= false;
		
		lineNumber = 1;
		
		while((line=buffer.readLine())!=null) {
			
			if (line.length() < 2) continue;
			
			//System.out.println(line);
			
			switch(line.charAt(0)) {
			
				case 'C' :
					
					switch(line.charAt(1)) {
					
						case 'R' :
							
							// The begining of the result 
							chromName = line.substring(3).trim();							
							if (!chromTable.containsKey(chromName)) {
								chromTable.put(chromName, new Hashtable());
							}
							if (!qtlTable.containsKey(chromName)) {
								qtlList = new ArrayList();
								qtlTable.put(chromName, qtlList);
							} else {
								qtlList = (ArrayList) qtlTable.get(chromName);
							}
							
							//System.out.println(">CHROM " + chromName);
							break;
							
						case 'L' :
							
							if (first) {
							    // Set the indice of the QTL
								for(int i=1;i<=qtlNumber;i++) {
									qtlIdx=qtlNumber-i;
									result.qtlIdx[qtlIdx] = qtlList.size()-i;
									result.x[qtlIdx]   	  = ((Qtl)qtlList.get(qtlList.size()-i)).position;
									result.sd[qtlIdx]  	  = ((Qtl)qtlList.get(qtlList.size()-i)).sd_position;
								}
								first=false;
							}
							
							buffer.reset();
							emResultList.add(EMResultFactory.read(buffer));
							
							break;
							
						default :
							
							break;
					}
					break;
				case 'Q' :
					if (qtlList==null) throw new IOException(MetaQtlAnalysisFactory.class.getName()+": Bad file format "+ lineNumber);
					tokenizer  = new StringTokenizer(line.substring(3));
					if (tokenizer.countTokens() != 4)
						throw new IOException(MetaQtlAnalysisFactory.class.getName()+": Bad line format " + lineNumber);
					Qtl qtl  = new Qtl();
					qtl.id          = Integer.parseInt(tokenizer.nextToken());
					qtl.name        = tokenizer.nextToken();
					qtl.position    = NumberFormat.parseDouble(tokenizer.nextToken());
					qtl.sd_position = NumberFormat.parseDouble(tokenizer.nextToken());
					qtlList.add(qtl);
					//System.out.println("ADD QTL " + qtlList.size());
					break;
				case 'T' :
					// the trait
					if (chromName==null) throw new IOException(MetaQtlAnalysisFactory.class.getName()+": Bad file format "+ lineNumber);
					traitTable = (Hashtable) chromTable.get(chromName);
					tokenizer  = new StringTokenizer(line.substring(3));
					traitName  = tokenizer.nextToken();
					qtlNumber  = Integer.parseInt(tokenizer.nextToken());
					result     = new MetaQtlResult(traitName, qtlNumber);				
					traitTable.put(traitName, result);
					first=true;
					emResultList = new ArrayList();
					//System.out.println("TRAIT " + traitName +" " + qtlNumber);
					break;
				case 'H' :
					// Tree
					line=line.substring(line.indexOf("HC")+2);
					result.tree = TreeFactory.read(line);
					// Set the indice of the QTL
					for(int i=0;i<qtlNumber;i++) {
						result.qtlIdx[i]      = i;
						result.x[i]        	  = ((Qtl)qtlList.get(i)).position;
						result.sd[i]  	      = ((Qtl)qtlList.get(i)).sd_position;
					}
					break;
				case '-' :
					// End of result
					if (emResultList.size()>0)
						result.setClusterings(emResultList);
					chromName = null;
					traitName = null;
					result    = null;
					break;
			    default :
			    	break;
			}
			
			buffer.mark(line.length());
			lineNumber++;
			
		}
		
		if (chromTable.size() > 0) {
			analysis = new MetaQtlAnalysis(chromTable.size());
			int i=0;
			Enumeration e = chromTable.keys();
			while(e.hasMoreElements()) {
				analysis.chromNames[i]		= (String) e.nextElement();
				traitTable					= (Hashtable) chromTable.get(analysis.chromNames[i]);
				qtlList						= (ArrayList) qtlTable.get(analysis.chromNames[i]);
				analysis.resultByChrom[i]	= new MetaQtlResult[traitTable.size()];
				int j=0;
				Enumeration e1 				= traitTable.keys();
				while(e1.hasMoreElements()) {
					analysis.resultByChrom[i][j] = (MetaQtlResult) traitTable.get(e1.nextElement());
					j++;
				}
				analysis.qtlByChrom[i]		= new Qtl[qtlList.size()];
				for(j=0;j<qtlList.size();j++) {
					analysis.qtlByChrom[i][j] = (Qtl)qtlList.get(j);
				}
				i++;
			}
		}
		
		return analysis;
	}
	/**
	 * This methods writes out in the given <code>stream</code> the results
	 * of a QTL meta-analysis. The user is responsible for closing the stream.
	 * 
	 * @param analysis the meta analysis results
	 * @param stream an output stream.
	 * @throws IOException
	 */
	public static void write(MetaQtlAnalysis analysis, OutputStream stream) throws IOException {
		
		int nchr,i,j,l;
		MetaQtlResult[] results;
		PrintWriter writer;
		
		if (analysis == null) return;
		if (stream == null) return;
		
		writer = new PrintWriter(new OutputStreamWriter(stream));
		
		nchr = analysis.nchr;
		
		for(i=0;i<nchr;i++) {
				
			results = analysis.resultByChrom[i];
				
			for(j=0;j<results.length;j++) {
				
				if (results[j] != null) {
					
					writer.println("CR "+ analysis.chromNames[i].replace(" ", "_"));
					writer.println("TR "+ results[j].trait.replace(" ","_") + " " + results[j].nqtl);
					
					for(l=0;l<results[j].nqtl;l++) {
						
						writer.println("QT "
									   + l
									   + " "
								       + analysis.qtlByChrom[i][results[j].qtlIdx[l]].getName().replace(" ","_")
									   + " "
									   + NumberFormat.formatDouble(results[j].x[l])
									   + " "
									   + NumberFormat.formatDouble(results[j].sd[l])
									   );		
					}
					
					if (results[j].clusterings != null) {
						
						writeClustering(results[j], writer);
					
					}
					if (results[j].tree != null) {
						
						writeTree(results[j], writer);
						
					}
					
					writer.println("--");
					
					writer.flush();
					
				}
			}			
		}
		
		writer.flush();
		writer.close();
		
	}
	/**
	 * This methods writes out the result of a QTL gaussian mixture
	 * based clustering. 
	 * 
	 * @param chrom the chromosome
	 * @param results the result of the meta-analysis.
	 * @param writer the writer.
	 */
	private static void writeClustering(MetaQtlResult result, PrintWriter writer)
	{
		int l;
		EMResult cluster;
		
		for(l=0;l<result.nmqc;l++) {
			
			cluster = result.clusterings[l];
			
			if (cluster != null) {
				
				EMResultFactory.write(cluster, writer);
				
			}
		}
		
		
	}
	/**
	 * This methods writes out the result of a QTL hierarchical
	 * clustering using a Newick format to represent the distance
	 * tree. 
	 * 
	 * @param chrom the chromosome
	 * @param results the result of the meta-analysis.
	 * @param writer the writer.
	 * 
	 * @see TreeFactory#write(TreeNode[], PrintWriter)
	 */
	private static void writeTree(MetaQtlResult result, PrintWriter writer)
	{
		writer.print("HC ");
		TreeFactory.write(result.tree, writer);
		writer.println();
		writer.flush();
		
	}

}
