/*  
 *  src/org/thalia/bio/entity/factory/MapTabFactory.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.bio.entity.factory;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;

import org.metaqtl.bio.IBioEntity;
import org.metaqtl.bio.IBioGenome;
import org.metaqtl.bio.IBioLGroup;
import org.metaqtl.bio.IBioLocus;
import org.metaqtl.bio.entity.GeneticMap;
import org.metaqtl.bio.entity.LGroup;
import org.metaqtl.bio.entity.Locus;
import org.metaqtl.bio.util.BioLocusTypes;
import org.metaqtl.bio.util.NumberFormat;
import org.metaqtl.bio.util.TabulatedReader;
import org.metaqtl.bio.util.TabulatedWriter;

/**
 * 
 * 
 * @author Jean-Baptiste
 */
public class MapTabFactory extends BioEntityFactory {
	
	private void writeObject(GeneticMap map, TabulatedWriter writer) {
    	int i,j,l,idx;
    	String[] keys,values;
    	Properties properties;
    	Enumeration e;
    	IBioLGroup[] groups;
    	IBioLocus[] loci;
        
    	keys   = getColumnNames(map);
    	
    	values = new String[keys.length];
    	writer.setKeys(keys);
        
        groups = map.groups();
        
        sortGroup(groups);
        
        for(i=0;i<groups.length;i++) {
        	
        	loci      = groups[i].loci();
        	
        	sortPosition(loci);
        	
        	for(j=0;j<loci.length;j++) {
        		
        		l=0;
        		if (map.getName() != null) {
        			values[l++] = map.getName();
        		}
        		values[l++] = groups[i].getName();
            	values[l++] = loci[j].getName();
            	values[l++] = BioLocusTypes.typeToString(loci[j].getLocusType());
            	values[l++] = NumberFormat.formatDouble(loci[j].getPosition().absolute());
            	/* Clean the array */
            	for(;l<values.length;l++)
            		values[l]="";
            	properties = loci[j].getProperties();
        		if (properties != null) {
        			e = properties.keys();
        			while(e.hasMoreElements()) {
        				idx = getKeyIdx((String)e.nextElement(),keys);
        				if (idx>=0) {
        					values[idx] = properties.getProperty(keys[idx]);
        				}
        			}
        		}
        		writer.nextValues(values);
        	}
        }
        
    }

   
	/**
     * @param string
     * @param keys
     * @return
     */
    private int getKeyIdx(String string, String[] keys) {
        for (int i = 0; i < keys.length; i++) {
            if (keys[i].equals(string))
                return i;
        }
        return -1;
    }

    /**
     * Returns a String array which represents the keys to be used int the
     * tabulated format.
     * 
     * @param mapBean
     * @return
     */
    private String[] getColumnNames(GeneticMap map) {
    	int i,j,size;
    	String[]  keysStrs;
    	Hashtable keys=null;
    	Properties properties;
        IBioLGroup[] groups;
        IBioLocus[] loci;
        Enumeration e;
        
        groups = map.groups();  
        
        for(i=0;i<groups.length;i++) {
        	
        	loci = groups[i].loci();
        	
        	for(j=0;j<loci.length;j++) {
        		
        		properties = loci[j].getProperties();
        		
        		if (properties != null) {
        			if (keys==null) 
        				keys = new Hashtable();
        			e = properties.keys();
        			while(e.hasMoreElements()) {
        				keys.put((String)e.nextElement(), " ");
        			}
        		}
        	}
        	
        }
        
        size = 4;
        if (map.getName() != null) size++;
        size += keys.size();
        
        keysStrs = new String[size];
        
        j=0;
        
        if (map.getName() != null) {
        
        	keysStrs[j++] = "map";
        	
        }
        
        keysStrs[j++] = "group";
        keysStrs[j++] = "locus";
        keysStrs[j++] = "type";
        keysStrs[j++] = "position";
        
        e = keys.keys();
        
        while(e.hasMoreElements()) {
        	keysStrs[j] = (String) e.nextElement();
        	//System.out.println("Extra columns " + keysStrs[j]);
        	j++;
        }
            
        return keysStrs;
    }
    /**
     * Returns org.thalia.bio.extend.entity.GeneticMap.
     * 
     * @param tabReader
     * @return
     */
    private IBioGenome readObject(TabulatedReader tabReader) throws IOException {
    	IBioGenome map;
        IBioLocus locus;
        IBioLGroup group;
        Properties prop;
        Set keys;
        Iterator it;
    	Hashtable groups = new Hashtable();
        String key,value,mapName=null;
        
        keys = tabReader.getKeys();
        
        map = new GeneticMap();
        
        while (tabReader.hasNext()) { /* loop on row */
        	if (mapName==null && tabReader.get("map")!=null)
        		map.setName(tabReader.get("map"));
           /* First get the group to which this locus
            * belongs */
        	value = tabReader.get("group");
        	if (!groups.containsKey(value)) {
        		group=new LGroup(value, map);
        		map.addGroup(group);
        		groups.put(value,group);
        	} else {
        		group = (LGroup)groups.get(value);
        	}
        	locus=null;
        	if (tabReader.containsKey("type")) {
	        	value = tabReader.get("type");
	        	locus = Locus.newLocus(BioLocusTypes.parseType(value));
	        	value = tabReader.get("locus");
	        	locus.setName(value);
        	} else if (tabReader.containsKey("marker")) {
        		locus = Locus.newLocus(IBioLocus.MARKER);
        		value = tabReader.get("marker");
            	locus.setName(value);
        	} else if (tabReader.containsKey("qtl")) {
        		locus = Locus.newLocus(IBioLocus.QTL);
        		value = tabReader.get("qtl");
            	locus.setName(value);
        	}
        	if (locus != null) {
	        	value = tabReader.get("position");
	        	locus.setPosition(parseDouble(value));
	        	/* Then get the values of the other properties */
	        	prop = locus.getProperties();
	        	it = keys.iterator();
	            while (it.hasNext()) { /* loop on columns */
	            	key = (String)it.next();
	            	if (key.compareTo("group")!=0
	            		&& key.compareTo("locus")!=0
	            		&& key.compareTo("map")!=0
						&& key.compareTo("type")!=0
						&& key.compareTo("position")!=0) {
	            		if (tabReader.get(key)!=null) { 
	            			prop.setProperty(key, tabReader.get(key));
	            		} else {
	            			prop.setProperty(key, "");
	            		}
	                }
	            }
	            locus.setProperties(prop);
	            group.addLocus(locus);
        	} else {
        		throw new IOException("Bad Header Format");
        	}
        }
        
        return map;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.thalia.core.runtime.ITiedFactory#setObject(java.lang.Object,
     *      org.thalia.core.runtime.ITiedConnection)
     */
    public void unload(IBioEntity obj, Writer writer) throws IOException {
    	TabulatedWriter twriter = new TabulatedWriter(writer);
    	writeObject((GeneticMap) obj, twriter);
    	writer.close();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.thalia.core.runtime.ITiedFactory#getObject(org.thalia.core.runtime.ITiedConnection)
     */
    public IBioEntity load(Reader reader) throws IOException {

        return readObject(new TabulatedReader(reader));

    }
    
    /**
	 * This method parse the given string to a double value. If the 
	 * string is not valid 0.0 is returned. Note that this method
	 * is 'coma'/'dot' compliant.
	 *   
	 * @param value
	 * @return
	 */
	private static double parseDouble(String value) {
		try { 
			return Double.parseDouble(value);
		} catch (NumberFormatException e1) {
			/* May be its due to the fact that a coma is used instead of
			 * a point */
			try {
				return Double.parseDouble(value.replace(',', '.'));
			} catch (NumberFormatException e2) {
				return 0.0;
			}
		}
	}
	
	/**
     * @param markers
     * @return
     */
    private void sortPosition(IBioLocus[] loci) {
        int min;
        IBioLocus t;
        int N = loci.length;

        for (int i = 0; i < N - 1; ++i) {
            min = i;
            for (int j = i + 1; j < N; ++j)
                if (loci[j].getPosition().absolute() < loci[min]
                        .getPosition().absolute())
                    min = j;
            t = loci[min];
            loci[min] = loci[i];
            loci[i] = t;
        }
    }
    
    
    /**
	 * @param groups
	 */
	private void sortGroup(IBioLGroup[] groups) {
		int min;
        IBioLGroup t;
        int N = groups.length;

        for (int i = 0; i < N - 1; ++i) {
            min = i;
            for (int j = i + 1; j < N; ++j)
                if (groups[j].getName().compareTo(groups[min].getName()) < 0)
                    min = j;
            t = groups[min];
            groups[min] = groups[i];
            groups[i] = t;
        }
		
	}


	/* (non-Javadoc)
	 * @see org.thalia.bio.extend.entity.factory.BioEntityFactory#load(java.io.InputStream)
	 */
	public IBioEntity load(InputStream stream) throws IOException {
		return load(new InputStreamReader(stream));
	}


	/* (non-Javadoc)
	 * @see org.thalia.bio.extend.entity.factory.BioEntityFactory#unload(org.thalia.bio.entity.IBioEntity, java.io.OutputStream)
	 */
	public void unload(IBioEntity entity, OutputStream stream) throws IOException {
		unload(entity, new OutputStreamWriter(stream));
	}

}