/*  
 *  src/org/metaqtl/main/MetaDB.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.main;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;

import org.metaqtl.Chromosome;
import org.metaqtl.IMetaQtlConstants;
import org.metaqtl.MetaDico;
import org.metaqtl.adapter.ChromosomeAdapter;
import org.metaqtl.bio.IBioGenome;
import org.metaqtl.bio.IBioLGroup;
import org.metaqtl.bio.IBioLocus;
import org.metaqtl.bio.IBioOntology;
import org.metaqtl.bio.entity.factory.MapTabFactory;
import org.metaqtl.bio.entity.factory.XmlBioEntityFactory;
import org.metaqtl.bio.entity.factory.XmlGeneticMapFactory;
import org.metaqtl.bio.entity.factory.XmlOntologyFactory;
import org.metaqtl.bio.util.BioUtilities;
import org.metaqtl.factory.MapDBFactory;
import org.metaqtl.util.GeneticMapProperties;

/**
 * 
 */
public final class MetaDB extends MetaMain {
	
	private static final String VERSION = IMetaQtlConstants.VERSION; 
	
	private static final String syntax  =   " Syntaxe: MetaDB "+
											"[{-e, --exp}]] "+
											"[{-m, --mapdb}]] "+
											"[{-q, --qtldb}]] "+
											"[{-o, --outdir}]] "+
											"[{-t, --tonto}]"+
											"[{-d, --mrkdico}]"+
											"[{--mrkup}]"+
											"[{--mrkrm}]"+
											"[{--chrm}]"+
											MetaMain.generalUsage();
	
	public void printUsage() {
        System.err.println(syntax);
    }
	
	public void printHelp() {
		
		System.out.println("MetaDB, "+VERSION);
		System.out.println("Copyright (C) 2005  Jean-Baptiste Veyrieras (INRA)");
		System.out.println("MetaDB comes with ABSOLUTELY NO WARRANTY.  ");
		System.out.println("This is free software, and you are welcome  ");
		System.out.println("to redistribute it under certain conditions.");
		System.out.println();
		System.out.println(syntax);
		System.out.println();
        MetaMain.generalHelp();
        System.out.println();
        System.out.println("-e, --exp     : the location of the experiment table");
        System.out.println("-m, --mapdb   : the location of the directory of the map files");
        System.out.println("-q, --qtldb   : the location of the directory of the qtl files (optional)");
        System.out.println("-o, --outdir  : the location of the output directory");
        System.out.println("-t, --tonto   : the location of the trait ontology");
        System.out.println("-d, --mrkdico : the location of marker dictionary");
        System.out.println("--mrkup : file for updating some marker names.");
        System.out.println("--mrkrm : file for removing some markers.");
        System.out.println("--chrm  : file for removing some chromosomes.");
        
	}
	
	/**
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		
		MetaDB program = new MetaDB();
		
		program.initCmdLineParser();
		
		CmdLineParser parser = program.parser;
		
		CmdLineParser.Option ainFile  = parser.addStringOption('e', "exp");
        CmdLineParser.Option amapDir  = parser.addStringOption('m', "mapdb");
        CmdLineParser.Option aqtlDir  = parser.addStringOption('q', "qtldb");
        CmdLineParser.Option aoutDir  = parser.addStringOption('o', "outdir");
        CmdLineParser.Option aontoFile = parser.addStringOption('t', "tonto");
        CmdLineParser.Option amrkDicoFile = parser.addStringOption('d', "mrkdico");
        CmdLineParser.Option amrkUp  = parser.addStringOption("mrkup");
        CmdLineParser.Option amrkRm  = parser.addStringOption("mrkrm");
        CmdLineParser.Option achRm   = parser.addStringOption("chrm");
        
        program.parseCmdLine(args);
        
        // Mandatory
        String inFile 	 =  (String)parser.getOptionValue(ainFile);
        String mapDir  	 =  (String)parser.getOptionValue(amapDir);
        String qtlDir  	 =  (String)parser.getOptionValue(aqtlDir);
        String outDir 	 =  (String)parser.getOptionValue(aoutDir);
        String ontoFile  =  (String)parser.getOptionValue(aontoFile);
        String mrkDicoFile  =  (String)parser.getOptionValue(amrkDicoFile);
        String mrkUpFile   =  (String)parser.getOptionValue(amrkUp);
        String mrkRmFile   =  (String)parser.getOptionValue(amrkRm);
        String chRmFile    =  (String)parser.getOptionValue(achRm);
        
        if (inFile== null) {
        	System.err.println("No input file defined : EXIT");
        	System.exit(2);
		}
        if (mapDir== null) {
        	System.err.println("No map directory defined : EXIT");
        	System.exit(2);
		}
        if (outDir==null) {
        	System.err.println("No output directory defined : EXIT");
        	System.exit(2);
		}
        
        IBioGenome[] maps = null;
        IBioGenome[] qtls = null;
        
		try {
			
			maps = getMaps(inFile, mapDir);
			setQtls(maps, qtlDir);
			
		} catch (IOException e) {
			System.err.println(e.getMessage());
			System.exit(5);			
		}
		
		// If a marker dictionary is defined, then first rename all marker loci
		if (mrkDicoFile != null) {
			try {
				MetaDico mrkDico = getDico(mrkDicoFile);
				renameMarkers(maps, mrkDico);		
			} catch (IOException e) {
				System.err.println(e.getMessage());
				System.exit(5);
			}		
		}
		// Now check if the maps are valid,
		// manage map expansion and convert
		// all the map distances in cM with
		// the haldane mapping function 
		for(int i=0;i<maps.length;i++) {
			Chromosome[] chroms = ChromosomeAdapter.toChromosomes(maps[i]);
			for(int j=0;j<chroms.length;j++) {
				chroms[j].updateMappingContext(IMetaQtlConstants.MAPPING_FUNCTION_HALDANE, IMetaQtlConstants.MAPPING_UNIT_CM);
			}
			IBioGenome map = ChromosomeAdapter.toIBioGenome(chroms, 0);
			map.setName(maps[i].getName());
			GeneticMapProperties.setMappingContext(map, IMetaQtlConstants.MAPPING_FUNCTION_HALDANE, IMetaQtlConstants.MAPPING_UNIT_CM);
			GeneticMapProperties.setCrossDesign(map, GeneticMapProperties.getCrossDesign(maps[i]));
			maps[i]=map;
		}
		// Now take into account if needed the supplementary files 
		try {
			if (mrkUpFile != null)
				updateMarkerNames(maps, mrkUpFile);
			if (mrkRmFile != null)
				removeLocus(maps, mrkRmFile);
			if (chRmFile != null)
				removeChromosomes(maps, chRmFile);
		} catch (IOException e) {
			System.err.println(e.getMessage());
			System.exit(6);
			
		}
		//
		// Now, write out the map db in Xml format 
		//
		File dir = new File(outDir);
		if (!dir.exists()) dir.mkdirs();
		for(int i=0;i<maps.length;i++) {
			try {
				writeMap(maps[i], dir);
			} catch (IOException e) {
				System.err.println("[ ERROR ] : Unable to unload map " + maps[i].getName());
				System.exit(7);
			}
		}
		//
		// Finally, if an ontology has been supplied, then
		// checks its validity and converts it into Xml.
		//
		if (ontoFile != null) {
			try {
				IBioOntology ontology = getOntology(ontoFile, 't');
				writeOntology(ontology, dir);
			} catch (IOException e) {
				System.err.println(e.getMessage());
				System.exit(8);
			}
		}
	}

	/**
	 * @param maps
	 * @param chRmFile
	 * @throws IOException
	 */
	private static void removeChromosomes(IBioGenome[] maps, String chRmFile) throws IOException {
		Hashtable hash = getChrToRm(new File(chRmFile));
		if (hash == null) return;
		Enumeration iter = hash.keys();
		while(iter.hasMoreElements()) {
			String mapName = (String)iter.nextElement();
			ArrayList chromNames = (ArrayList)hash.get(mapName);
			IBioGenome map = getMap(maps, mapName);
			if (map != null) {
				for(int i=0;i<chromNames.size();i++) {
					String chromName = (String)chromNames.get(i);
					map.removeGroup(chromName);
				}
			} else {
				System.err.println("[ WARNING ] Unable to find map " + mapName + " : wrong name in " + chRmFile);
			}
		}
	}
	
	private static Hashtable getChrToRm(File file) throws IOException {
		int i=0;
		Hashtable  hash   = null;
		ArrayList  list   = null;
		FileReader reader = new FileReader(file);
		if (reader == null) return null;
		BufferedReader buffer = new BufferedReader(reader);
		String line,map,chrom;
		while ((line = buffer.readLine()) != null) {
			i++;
			StringTokenizer token = new StringTokenizer(line, "\t");
			if (token.countTokens() >= 2) {
				map   = token.nextToken();
				chrom = token.nextToken();
				if (hash==null) hash = new Hashtable();
				list = (ArrayList) hash.get(map);
				if (list == null) list = new ArrayList();
				list.add(chrom);
				hash.put(map, list);
			} else {
				throw new IOException("[ ERROR ] Unexpected line format at line " + i +" in " + file.getName()); 
			}
		}
		reader.close();
		return hash;
	}
	
	

	
	/**
	 * @param maps
	 * @param mrkUpFile
	 */
	private static void updateMarkerNames(IBioGenome[] maps, String mrkUpFile) throws IOException  {
		Hashtable hash = getMrkToUp(new File(mrkUpFile));
		if (hash == null) return;
		Enumeration iter = hash.keys();
		while(iter.hasMoreElements()) {
			String mapName = (String)iter.nextElement();
			ArrayList mrkLists = (ArrayList)hash.get(mapName);
			IBioGenome map = getMap(maps, mapName);
			if (map != null) {
				for(int i=0;i<mrkLists.size();i++) {
					String chromName = (String) ((ArrayList)mrkLists.get(i)).get(0);
					String mrkName = (String) ((ArrayList)mrkLists.get(i)).get(1);
					String upName = (String) ((ArrayList)mrkLists.get(i)).get(2);
					IBioLGroup chrom = map.getGroup(chromName);
					if (chrom != null) {
						IBioLocus locus = chrom.getLocus(mrkName);
						if (locus != null) {
							locus.setName(upName);
							chrom.removeLocus(mrkName);
							chrom.addLocus(locus);
						} else {
							System.err.println("[ WARNING ] Unable to find marker " + mrkName + " : wrong name in " + mrkUpFile);
						}
					} else {
						System.err.println("[ WARNING ] Unable to find chromosome " + chromName + " : wrong name in " + mrkUpFile);
					}
				}
			} else {
				System.err.println("[ WARNING ] Unable to find map " + mapName + " : wrong name in " + mrkUpFile);
			}
		}
	}

	/**
	 * @param file
	 * @return
	 */
	private static Hashtable getMrkToUp(File file) throws IOException {
		int i=0;
		Hashtable  hash   = null;
		ArrayList  list   = null;
		ArrayList  tmp;
		FileReader reader = new FileReader(file);
		if (reader == null) return null;
		BufferedReader buffer = new BufferedReader(reader);
		String line,map,chrom,marker,update;
		while ((line = buffer.readLine()) != null) {
			i++;
			StringTokenizer token = new StringTokenizer(line, "\t");
			if (token.countTokens() >= 4) {
				map   = token.nextToken();
				chrom = token.nextToken();
				marker= token.nextToken();
				update= token.nextToken();
				if (hash==null) hash = new Hashtable();
				list = (ArrayList) hash.get(map);
				if (list == null) list = new ArrayList();
				tmp = new ArrayList();tmp.add(chrom);tmp.add(marker);tmp.add(update);
				list.add(tmp);
				hash.put(map, list);
			} else {
				throw new IOException("[ ERROR ] Unexpected line format at line " + i +" in " + file.getName()); 
			}
		}
		reader.close();
		return hash;
	}

	/**
	 * @param maps
	 * @param mrkDico
	 */
	private static void renameMarkers(IBioGenome[] maps, MetaDico mrkDico) {
		for(int i=0;i<maps.length;i++) {
			IBioLGroup[] groups = maps[i].groups();
			for(int j=0;j<groups.length;j++) {
				IBioLocus[] loci=groups[j].loci();
				for(int k=0;k<loci.length;k++) {
					String mrkName = mrkDico.getTerm(loci[k].getName());
					if (mrkName != null) loci[k].setName(mrkName);
				}
			}
		}
	}

	private static void writeOntology(IBioOntology o , File d) throws IOException {
		if (o==null || d==null) return;
		File outf = new File(d.getAbsolutePath()+System.getProperty("file.separator")+"ontology.xml");
		FileOutputStream stream 	   = new FileOutputStream(outf);
		XmlOntologyFactory factory     = new  XmlOntologyFactory();
		factory.unload(o, stream);
		stream.close();
	}
	/**
	 * @param genome
	 * @param dir
	 */
	private static void writeMap(IBioGenome genome, File dir) throws IOException {
		if (genome == null) return;
		if (dir == null) return;
		if (!dir.isDirectory()) return;
		
		String outF = dir.getAbsolutePath()+System.getProperty("file.separator")+genome.getName()+".xml";
		
		FileOutputStream stream 	   = new FileOutputStream(outF);
		
		XmlBioEntityFactory xmlFactory = new  XmlGeneticMapFactory();
		
		xmlFactory.unload(genome, stream);
		
		stream.close();
		
	}

	/**
	 * @param inFile
	 * @param mapDir
	 * @return
	 */
	private static IBioGenome[] getMaps(String inFile, String mapDir) throws IOException {
		if (inFile == null) return null;
		if (mapDir == null) return null;
		
		File dir = new File(mapDir);
		
		if (!dir.isDirectory()) {
			
			System.err.println("[ ERROR ] "+mapDir+" is not a directory");
			System.exit(3);
			
		}
		
		File[] files      = dir.listFiles();
		
		FileReader reader = new FileReader(inFile);
		IBioGenome[] maps = MapDBFactory.read(reader);
		reader.close();
		
		MapTabFactory factory = new MapTabFactory();
		
		for(int i=0;i<maps.length;i++) { // loop on the maps 
			
			for(int j=0;j<files.length;j++) { // loop one the file
				
				if (files[j].getName().startsWith(maps[i].getName())) {
					
					reader 			= new FileReader(files[j]);
					IBioGenome map  = (IBioGenome) factory.load(reader);
					reader.close();
					
					if (map == null) {
						
						System.err.println("[ ERROR ] Had trouble when parsing file " + files[j].getAbsolutePath());
						System.exit(4);
						
					}
					
					// Synchronize the map
					map.setName(maps[i].getName());
					map.setProperties(maps[i].getProperties());
					maps[i] = map;
					
				}
			}
		}
		
		return maps;
	}
	/**
	 * @param inFile
	 * @param qtlDir
	 * @return
	 */
	private static void setQtls(IBioGenome[] maps, String qtlDir) throws IOException {
		if (maps   == null) return;
		if (qtlDir == null) return;
		
		File dir = new File(qtlDir);
		
		if (!dir.isDirectory()) {
			
			System.err.println("[ ERROR ] "+qtlDir+" is not a directory");
			System.exit(3);
			
		}
		
		File[] files      = dir.listFiles();
		
		MapTabFactory factory = new MapTabFactory();
		
		for(int i=0;i<maps.length;i++) { // loop on the maps 
			
			for(int j=0;j<files.length;j++) { // loop one the file
				
				// find the file which name starts with the map name
				if (files[j].getName().startsWith(maps[i].getName())) {
					
					FileReader reader = new FileReader(files[j]);
					IBioGenome qtl    = (IBioGenome) factory.load(reader);
					reader.close();
					
					if (qtl == null) {
						
						System.err.println("[ ERROR ] Had trouble when parsing file " + files[j].getAbsolutePath());
						System.exit(4);
						
					}
					
					BioUtilities.mergeGenome(maps[i], qtl);
					
				}
			}
		}
		
	}

	
}
