/*******************************************************************************
 * GenRGenS v.1.0, Generation of Random Genomic Sequences                      *
 * Copyright (C) 2001  Alain Denise/Yann Ponty                                 *
 *                                                                             *
 * 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. *
 *******************************************************************************/

//Titre :        Comptage
//Version :      1.1
//Auteur :       Yann Ponty
//Société :      U. P. S.
//Description :  Comptage de sous chaines dans

package GenRGenS.utility;


import java.io.*;
import java.text.DecimalFormat;
import java.util.*;
import GenRGenS.utility.callbacks.*;
import GenRGenS.*;
/**
 * La classe <code>Comptage</code> est une classe permettant le comptage
 * des occurences de sous chaines de longueurs fixees dans un fichier.
 * Elle permet ensuite la sortie du resultat dans le format GGD.
 * On l'utilise de l'une des 2 facons suivantes :<P>
 *            <DD><DD><DT><code> String s1 = "mon_fichier1";
 *            <DD><DD><DT>String s2 = "mon_fichier2";
 *            <DD><DD><DT>int ordre_markov = 3;
 *            <DD><DD><DD><DT>...
 *            <DD><DD><DT>Comptage count = new Comptage(ordre_markov);
 *            <DD><DD><DT>count.countFile(s1);
 *            <DD><DD><DT>count.countFile(s2);
 *            <DD><DD><DD><DT>...
 *            <DD><DD><DT>GenRGenS.out.println(count.toString());</DT></code><P>
 *            <DD><DD><DT><code> String s1 = "mon_fichier1";
 *            <DD><DD><DT>String s2 = "mon_fichier2";
 *            <DD><DD><DT>int ordre_markov = 3;
 *            <DD><DD><DT>int phase_markov = 4;
 *            <DD><DD><DD><DT>...
 *            <DD><DD><DT>Comptage count = new Comptage(ordre_markov,phase_markov);
 *            <DD><DD><DT>count.countFile(s1);
 *            <DD><DD><DT>count.countFile(s2);
 *            <DD><DD><DD><DT>...
 *            <DD><DD><DT>GenRGenS.out.println(count.toString());</DT></code><P>
 * @author  Yann Ponty
 * @version 1.1
 * @see LightState
 */
public class Comptage
{

  private Hashtable<String, LightState>[] _etats;
  private double _sigma = 0.;
  private int _word_size = 1;
  private int _entry_found = 0;
  private HashSet<Character> _lexique = new HashSet<Character>();
  private HashSet<Character> _lexiqueForced = new HashSet<Character>();
  private int _outputMode = 0;

  /**
   * Constante de la classe <code>Comptage</code> permettant
   * le choix du mode de sortie/chaîne.
   * La valeur </code>ONLYOCCURINGPATTERNS<code> provoque une sortie au format .ggd sans énumération
   * de toutes les combinaisons du dictionnaire.
   * @see toString
   */
  public static final int ONLYOCCURINGPATTERNS = 0;

  /**
   * Constante de la classe <code>Comptage</code> permettant
   * le choix du mode de sortie/chaîne.
   * La valeur </code>ALLPATTERNS<code> provoque une sortie au format .ggd en énumérant toutes
   * de toutes les combinaisons du dictionnaire.
   * @see toString
   */
  public static final int ALLPATTERNS = 1;

  /**
   * Constructeur de la classe <code>Comptage</code>.
   * @param markov_order L'ordre de Markov souhaité(taille des sous chaines ...). Prends comme valeur par défaut 1 si une longueur invalide (<1) est souhaitée.
   */
  public Comptage(int markov_order)
  {
	  this(markov_order,1,0.,"");
  }

  /**
   * Constructeur de la classe <code>Comptage</code>.
   * @param markov_order L'ordre de Markov souhaité(taille des sous chaines ...). Prends comme valeur par défaut 1 si une longueur invalide (<1) est souhaitée.
   * @param markov_phasis La phase dans la génération hétérogène de Markov. Prends comme valeur par défaut 1 si une longueur invalide (<1) est souhaitée.
   */
  public Comptage(int markov_order, int markov_phasis)
  {
	  this(markov_order, markov_phasis,0.,"");
  }
  /**
   * Constructeur de la classe <code>Comptage</code>.
   * @param markov_order L'ordre de Markov souhaité(taille des sous chaines ...). Prends comme valeur par défaut 1 si une longueur invalide (<1) est souhaitée.
   * @param markov_phasis La phase dans la génération hétérogène de Markov. Prends comme valeur par défaut 1 si une longueur invalide (<1) est souhaitée.
   * @param sigma Base du nombre d'occurrences dans un pseudo-comptage (Generalisation de la règle de laplace)
   */
  public Comptage(int markov_order, int markov_phasis, double sigma, String alphabet)
  {
	  markov_phasis = Math.max(markov_phasis, 1);
	  markov_order = Math.max(markov_order, 0);
      _word_size = markov_order+1;
    _etats = new Hashtable[markov_phasis];
    _sigma=sigma;
	  if (_sigma>0)
	  {
	    _outputMode = ALLPATTERNS;
	  }
    for(int i = 0; i< _etats.length; i++)
    {
      _etats[i] = new Hashtable<String,LightState>(53);
    }
    for(int i = 0; i< alphabet.length(); i++)
    {
    	_lexiqueForced.add(alphabet.charAt(i));
    }
  }


  /**
   * Fixe le mode de rendu de l'instance de classe.
   * @param mode Le nouveau mode de rendu. <code>mode</code> doit être une valeur valide
   * (<code>ONLYOCCURINGPATTERNS</code> ou <code>ALLPATTERNS</code>). Sinon, le mode de sortie
   * est laissé à sa valeur précedente ...
   * @see toString
   */

  public void setOutputMode(int mode)
  { 
	  _outputMode = ((mode>=0)||((mode<2)))? mode : _outputMode ;
  }



  /**
   * Parcourt le fichier donné en argument.
   * Ajoute tous les caracteres absents du lexique à celui ci.
   * Met à jour les nombres d'occurences de tous les caracteres.
   * @param file_name Le nom du fichier à traiter
   * @param verbose True => Messages de descritption de l'avancement du processus de comptage
   * @throws FileNotFoundException Si il n'existe pas de fichier de nom file_name
   * @throws FileTooSmallException Si le fichier de chemin file_name est plus petit
   * que l'ordre de Markov
   * @throws IOException Si une erreur d'entree/sortie se produit après l'ouverture
   * du fichier.
   */
  public void countFile(String file_name,boolean verbose) throws FileNotFoundException, FileTooSmallException, IOException
  {
    FileInputStream input = new FileInputStream(file_name);

    int restants = input.available();
    int fraction = 100;
    if (verbose) 
    {
      GenRGenS.progress.setWorkName("Analysing input file");
      GenRGenS.progress.setAmount(restants);
      GenRGenS.progress.setProgress(0);
    }
    int cent = restants / fraction;
    int countdown = cent;
    int pourcents = 0;
    int progres = 0;
 	  char[] buffer = new char[_word_size];
    char c;
    int numSequences = 0;
    c = (char) input.read();
    do
    {
 	 	  LightState e;
     	String s;
 	    int i;
 	    int phasis = 0;
      numSequences++;
      i=0;
      if (c=='>')
      {
          while((c!='\n')&&(c!=(char)0))
          {c = (char) input.read();}
      }
 	    while(i < _word_size)
 	    {
 	      if((c==(char)-1)||(c=='>'))
 	      {
 		      throw(new FileTooSmallException("File " + file_name + " is smaller than the Markov order !!!."));
 	      };
   	    if((c!='\n')&&(c!='\t')&&(c!=' ')&&(c!='\r'))
     	  {
   	      buffer[i] = c;
         	Character c2 = new Character(c);
 	        if(!(_lexique.contains(c2)))
   	      {
 	          _lexique.add(c2);
   	      }
          i++;
        };
        c = (char) input.read();

 	    };
   	  s = new String(buffer);
      phasis = (_word_size+_etats.length-1)%_etats.length;
      if(_etats[phasis].containsKey(s))
      {
 	      e = _etats[phasis].get(s);
   	    e.incCount();
     	}
      else
      {
 	      e = new LightState(s,0.);
   	    e.incCount();
     	  _etats[phasis].put(s,e);
      }

    
   	  countdown--;
     	for(_entry_found = 1; (c!=(char)-1)&&(c!='>'); _entry_found++)
     	{
       	progres++;
 	      if ((countdown==0)&&(verbose))
   	    {
     	    countdown = cent;
       	  pourcents++;
         	GenRGenS.progress.setProgress(progres);
 	      }
   	    if((c!='\n')&&(c!='\t')&&(c!=' ')&&(c!='\r'))
     	  {
       	  phasis = (phasis + 1) % _etats.length;
         	Character c2 = new Character(c);
 	        if(!(_lexique.contains(c2)))
   	      {
     	      _lexique.add(c2);
       	  }
         	for(i=1; i < _word_size; i++)
 	        {
   	        buffer[i-1] = buffer[i];
     	    }
         	buffer[_word_size-1]=c;
       	  s = new String(buffer);
 	        if(_etats[phasis].containsKey(s))
   	      {
     	      e = (LightState) _etats[phasis].get(s);
       	    e.incCount();
         	}
 	        else
   	      {
     	      e = new LightState(s,0.);
       	    e.incCount();
         	  _etats[phasis].put(s,e);
 	        }
   	    }
     	  c = (char) input.read();
 	      countdown--;
	 	  }
    }
    while(c!=(char)-1);
    if (verbose) GenRGenS.progress.setProgress(restants);
    if (verbose && (numSequences>1)) GenRGenS.err.println("Found "+numSequences+" sequences in Fasta format.");
  }
  
  private boolean validState(String n)
  {
	  if (_lexiqueForced.isEmpty())
		  return true;
	  for(char c : n.toCharArray()) {
		    if (!_lexiqueForced.contains(c))
		    	return false;
		    	
		}
	  return true;
  }

/**
 * Renvoie une chaîne de caractères au format ggd représentant cette instance de classe.
 * Si le mode de sortie (affecté par la méthode <code>setOutputMode</code>) a pour valeur
 * la constante <code>ONLYOCCURINGPATTERNS</code>, ne renvoie que les combinaisons connaissant au
 * moins une occurence. Si le mode de sortie a pour valeur <code>ALLPATTERNS</code>, génère toutes
 * les combinaisons de "ordre de Markov" éléments du dictionaire, principalement pour un soucis de
 * compatibilité avec la version 0.6 de GenRGenS.
 * @see ONLYOCCURINGPATTERNS
 * @see ALLPATTERNS
 */
  public String toString()
  {
    String main ="";
    String tmp1 ="";
    LightState e;
    DecimalFormat df = new DecimalFormat("0.###");

    double[] values = { 1.0, 3.5, 123.4567, 10.0 };

    boolean fini = false;
    int[] coords_vector = new int[_word_size];
    Character[] lexique_tab;
    if (_lexiqueForced.size()>0)
    	lexique_tab = _lexiqueForced.toArray(new Character[0]);
    else
    	lexique_tab = _lexique.toArray(new Character[0]);

    // Impression du type
    main = "TYPE = MARKOV\n";

    // Impression de l'ordre
    main = main + "ORDER = " + (_word_size - 1) + '\n';

    // Impression de la phase
    main = main + "PHASE = " + _etats.length + '\n';

    // Impression du type de symbole
    main = main + "SYMBOLS = LETTERS\n";

    // Impression du lexique
    /*  Ancien format de fichier ...

    for (Enumeration enum = _lexique.elements(); enum.hasMoreElements() ;)
    {
      tmp1 = enum.nextElement()+ " " + tmp1;
    }
    */

    // Initialisation du vecteur des coordonnées
    for( int i = 0; i < _word_size; i++)
    {
      coords_vector[i] = lexique_tab.length - 1;
    }

    // Impression des fréquences
    main = main + tmp1 + "FREQUENCIES =\n  ";
    switch(_outputMode)
    {
      case(ALLPATTERNS) :
      {

        tmp1 = "";

        while( ! fini )
        {
          String s = "";
          int k;
          for( int i = 0; i < _word_size; i++)
          {
            s = s + lexique_tab[coords_vector[i]];
          }
          tmp1 = s + " ";
          for (int j = 0; j< _etats.length; j++)
          {
            if (_etats[j].containsKey(s))
            {
              e = _etats[j].get(s);
              tmp1 = tmp1 + df.format(_sigma+e.getCount()) + " ";
            }
            else
            {
              tmp1 = tmp1 + _sigma + " ";
            }
          }

          boolean retenue = true;
          k = _word_size - 1;
          while((k>=0) && retenue)
          {
            coords_vector[k]--;
            if (coords_vector[k] < 0)
            {
              coords_vector[k]=lexique_tab.length - 1;
              k--;
            }
            else
            {
              retenue = false;
            }
          }
          fini = retenue;
          main = main + tmp1;
          if (_word_size>1)
        	  if (coords_vector[1]==lexique_tab.length - 1)
                  main += "\n  ";
        }
      }
      break;
      case(ONLYOCCURINGPATTERNS) :
      {
    	  if(_sigma>0)
    	  {
  		      throw(new RuntimeException("Cannot restrict output to occurring patterns if pseudocount activated (sigma>0)."));
    	  }
        tmp1 = "";
        boolean fin = false;
        int phase = 0;
        while(!fin)
        {
          for(Enumeration<String> jo = _etats[phase].keys() ; jo.hasMoreElements(); )
          {
            e = _etats[phase].get(jo.nextElement());
            if (validState(e.getName()))
            {
	            tmp1 = tmp1 + e.getName() + " ";
	            for(int j = 0;j < phase; j++)
	            {
	              tmp1 = tmp1 + 0 + "  ";
	            }
	            tmp1 = tmp1 + df.format(e.getCount()) + "  ";
	            _etats[phase].remove(e.getName());
	            for(int j = phase + 1;j < _etats.length; j++)
	            {
	              if (_etats[j].containsKey(e.getName()))
	              {
	                LightState e2 = _etats[j].get(e.getName());
	                tmp1 = tmp1 + df.format(e2.getCount()) + "  ";
	                _etats[j].remove(e.getName());
	              }
	              else
	              {
	                tmp1 = tmp1 + 0 + "  ";
	              }
	            }
            }
          }
          phase++;

          if ( phase>=_etats.length)
          {fin = true;}
        }
        main = main + tmp1;
      }
      break;
    }
    return main;
  }

}