/*  
 *  src/org/metaqtl/adapter/ChromosomeAdapter.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.adapter;

import java.util.ArrayList;

import org.metaqtl.Chromosome;
import org.metaqtl.bio.IBioCross;
import org.metaqtl.bio.IBioGenome;
import org.metaqtl.bio.IBioLGroup;
import org.metaqtl.bio.IBioLocus;
import org.metaqtl.bio.entity.CrossPopulation;
import org.metaqtl.bio.entity.GeneticMap;
import org.metaqtl.bio.entity.LGroup;
import org.metaqtl.bio.entity.Locus;
import org.metaqtl.numrec.NumericalUtilities;
import org.metaqtl.util.GeneticMapProperties;
import org.metaqtl.util.MappingFunction;
/**
 * This class defines some usefull methods to adapt {@link Chromosome}
 * objects into other kinds of objects or the inverse.
 * 
 */
public final class ChromosomeAdapter {
	
	/**
	 * This methods converts the given map
	 * into an array of {@link Chromosome} which represents
	 * the chromosomes of the input mpe.
	 * @param map
	 * @return an array of <code>Chromosome</code>
	 */
	public static Chromosome[] toChromosomes(IBioGenome map) {
		boolean rescale;
		int mappingFunct,mappingUnit;
		IBioCross crossDesign;
		IBioLGroup[] groups;
		Chromosome[] chroms = null;
		
		/* Get the mapping function for the given map */
		mappingFunct = GeneticMapProperties.getMappingFunction(map);
		/* Get the mapping unit for the given map */
		mappingUnit = GeneticMapProperties.getMappingUnit(map);
		/* Get the scale status */
		rescale     = GeneticMapProperties.needToBeRescaled(map);
		/* Get the cross parameter for the given map */
		crossDesign = GeneticMapProperties.getCrossDesign(map);
		/* Get the linkage groups of the given map */
		groups = map.groups();
		chroms = new Chromosome[groups.length];
		for (int i=0;i<groups.length;i++) {
			chroms[i] = toChromosome(groups[i], crossDesign, mappingFunct, mappingUnit);
			if (rescale) {
				CrossPopulation mappingCross = GeneticMapProperties.getMappingCross(map);
				if (mappingCross != null)
					chroms[i].r2R(mappingCross.getCrossType(), mappingCross.getGeneration());
				chroms[i].R2r();
			}
		}		
		return chroms;
	}
	/**
	 * This method converts a linkage group into a chromosome of type
	 * {@link Chromosome} with the given cross design
	 * and the given mapping context.
	 * 
	 * @param group the linkage group to convert.
	 * @param crossDesign the cross design.
	 * @param mappingFunction the mapping function.
	 * @param mappingUnit the mapping unit.
	 * @return the chromosome 
	 */
	public static Chromosome toChromosome(IBioLGroup group, IBioCross crossDesign, int mappingFunction, int mappingUnit) {
		int i,nm;
		double d,d1,d2,start,end;
		int[] oidx;			  /* index of marker over loci[] */
		int[] idx;			  /* index for Heap sort */
		double[] positions;	  /* the marker positions on the chromosome */
		double[] mr;		  /* the marker interval recombination rates */
		String[] names;		  /* the marker names */
		IBioLocus[] loci;	  /* the list of loci (marker + qtl) on the chromosome */
		ArrayList qtls=null;  /* the QTL on the chromosome */
		Chromosome chrom; 
		
		loci = group.loci();
		
		oidx      = new int[loci.length];
		idx       = new int[loci.length+1];
		positions = new double[loci.length+1];
		mr        = new double[loci.length-1];
		names     = new String[loci.length];
		
		start = Double.POSITIVE_INFINITY;
		end   = Double.NEGATIVE_INFINITY;
		
		for(nm=i=0;i<loci.length;i++)
		{
			if (loci[i].getLocusType()==IBioLocus.MARKER) {
				if (loci[i].getPosition().absolute() < start)
				{
					start = loci[i].getPosition().absolute();
				}
				if (loci[i].getPosition().absolute() > end)
				{
					end = loci[i].getPosition().absolute();
				}				
			}
		}
		
		for(nm=i=0;i<loci.length;i++) {
			if (loci[i].getLocusType()==IBioLocus.MARKER) {
					/* At the same time push the marker names into the
					 * locus name cluster index. We also count the number
					 * of times the locus has been seen.
					 */
					positions[nm+1] = loci[i].getPosition().absolute();
					oidx[nm] 		= i; /* Index */
					nm++;
				
			} else if (loci[i].getLocusType()==IBioLocus.QTL) {
				if (loci[i].getPosition().absolute() < start)
				{
					continue;
				}
				else if (loci[i].getPosition().absolute() > end)
				{
					continue;
				}
				/* Add it to the qtl array list */
				if (qtls==null) qtls = new ArrayList();
				qtls.add(loci[i]);
			}
		}
		if (nm > 0) {
			/* (Heap) Sort the marker positions in order to be sure that the order
			 * which we will use is the correct order
			 */
			NumericalUtilities.indexx(nm, positions, idx);
			names[0] = loci[oidx[idx[1]-1]].getName();
			
			for(d=0.0,i=1;i<nm;i++) {
				d1 		= positions[idx[i]];
				d2 		= positions[idx[i+1]];
				/* Convert the distance into recombination rate */
				mr[i-1] = MappingFunction.recombination(d2-d1, mappingFunction, mappingUnit);
				d += MappingFunction.distance(mr[i-1], mappingFunction, mappingUnit);
				names[i]= loci[oidx[idx[i+1]-1]].getName();
			}
		} else {
			d = 0.0;
		}
		chrom = new Chromosome();
		if (group.getParent() != null)
			chrom.mapName     = group.getParent().getName();
		chrom.name       = group.getName();
		chrom.nm              = nm;
		chrom.mr			  = mr;
		chrom.totd            = d;
		chrom.mrkNames 		  = names;
		chrom.ct       = crossDesign.getCrossType();
		chrom.cs       = crossDesign.getSize();
		chrom.cg = crossDesign.getGeneration();
		chrom.mfc = mappingFunction;
		chrom.mut 	   = mappingUnit;
		if (qtls != null) {
			loci = new IBioLocus[qtls.size()];
			qtls.toArray(loci);
			chrom.attachQTL(loci);
		}
		
		return chrom;
	}
	/**
	 * This routine converts an array of chromosomes into a genetic map
	 * with as many chromosomes as the length of the array. The second
	 * argument gout takes its values in [0,2] :
	 * <ul>
	 *  <li>0 - Both marker and QTL are included.</li>
	 *  <li>1 - Only marker are included.</li>
	 *  <li>2 - Only QTL are included.</li>
	 * </ul>
	 * Use the static attributes <code>MAPPING_UNIT</code> and 
	 * <code>MAPPING_FUNCTION</code> to change the genetic map
	 * mapping unit and function.
	 * 
	 * @param chromosomes the array of <code>Chromosome</code> to convert.
	 * @param gout the convertion mode.
	 * @return a genetic map.
	 */
	public static IBioGenome toIBioGenome(Chromosome[] chromosomes, int gout) {
		int i,j;
		IBioLGroup group;
		IBioLocus  locus;
		Chromosome chrom;
		GeneticMap geneticMap = null;
		
		if (chromosomes==null) return null;
		if (chromosomes.length < 1) return null;
		
		geneticMap = new GeneticMap();
		
		for(i=0;i<chromosomes.length;i++) {
			
			chrom = chromosomes[i];
			
			group = new LGroup(chrom.name, geneticMap);
			
			/* The markers */
			if (gout==0 || gout==1) {
				for(j=0;j<chrom.nm;j++) {
					locus = Locus.newLocus(IBioLocus.MARKER);
					locus.setName(chrom.mrkNames[j]);
					locus.setPosition(chrom.getDistance(j,chrom.mfc,chrom.mut));
					group.addLocus(locus);
				}
			}
			/* The qtls */
			if ((gout==0 || gout == 2) && chrom.qtls != null) {
				for(j=0;j<chrom.qtls.length;j++) {
					locus = QtlAdapter.fromQTL(chrom.qtls[j]);
					group.addLocus(locus);
				}
			}
			
			geneticMap.addGroup(group);
			
		}
		
		return geneticMap;
	}

}
