/*
 * NAT - An universal Translator
 * Copyright (C) 2005 Bruno Mascret
 * Contact: bmascret@free.fr
 * 
 * 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.
 * 
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package nat.transcodeur;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
//import javax.xml.parsers.FactoryConfigurationError;
//import javax.xml.parsers.ParserConfigurationException;
//import org.xml.sax.SAXException;
//import org.xml.sax.SAXParseException;
import org.w3c.dom.Document;
//import org.w3c.dom.DOMException;
import javax.xml.transform.Transformer;
//import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
//import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.File;
//import java.io.FileWriter;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;

import java.io.IOException;
//import java.io.BufferedReader;
//import java.io.InputStreamReader;
import java.io.FileInputStream;
import java.util.ArrayList;

import gestionnaires.GestionnaireErreur;

import nat.ConfigNat;
import nat.Nat;
/**
 * <p>La classe <code>Transcodeur</code> réalise la transcription d'un fichier au format
 * XML interne en Braille</p>
 * @author  Bruno Mascret
 */
public class Transcodeur 
{	
	/** Temps d'exécution de la transcription en milliseconde */
	private long tempsExecution;
	/*  Configuration à utiliser   */
	//private ConfigNat confNat;
	/**  La feuille de style xsl à utiliser pour la transcription    */
	private String filtre="xsl/xsl.xsl";
	/**  Adresse du fichier d'entrée au format XML interne    */
	private String entree;
	/**  Adresse du fichier de sortie    */
	private String cible;
	/**  Encodage du fichier de sortie    */
	private String sortieEncoding = "UTF-8";
	/**  Valeur du parametre xsl "abrege"    */
	private String abrege = "1";
	/**  @todo  true si transcription du noir vers le Braille, false sinon    */
	private boolean sens = false; /* TAN = true, NAT = false*/
	
	/**
    *  Exception thrown by merge algorithms.
    *
    *  @param	e			<code>String</code> adresse du fichier d'entrée (format XML interne)
    *  @param	s			<code>String</code> adresse du fichier de sortie
    *  @param	se			<code>String</code> encodage du fichier de sortie
    */
	
	public Transcodeur(String e, String s, String se)
	{
		filtre = ConfigNat.getCurrentConfig().getXSL();
		entree = e;
		cible = s;
		sortieEncoding = se;
		if (!ConfigNat.getCurrentConfig().getAbreger())
		{
			abrege = "0";
		}
	}
	
	/* TAN A VIRER **/
	/** @todo */
	public void setSens(boolean s){sens = s;}
	/** @todo */
	public boolean getSens(){return sens;}
	
	/**Méthode d'accès à <code>tempsExecution</code>
	 * @return le temps mis pour transcire */
	public long donneTempsExecution(){return tempsExecution;}
	
	/**
	 * Fabrique le fichier xsl en fonction de la configuration de <code>configNat</code>
	 * @param	gestErreur	Un objet <code>GestionnaireErreur</code> pour l'affichage et la gestion
	 * des improbables erreurs.
	 */
	private void CreerFiltre(GestionnaireErreur gestErreur)
	{
// je rechange l'encoding en UTF-8; 1.2
		try
		{
			BufferedWriter fichierXSL = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filtre),"UTF-8"));
			//FileWriter fichierXSL = new FileWriter(filtre);
			fichierXSL.write("<?xml version='1.0' encoding=\"UTF-8\" ?>\n" +
			"<!DOCTYPE xsl:stylesheet SYSTEM \"" + ConfigNat.getCurrentConfig().getDTD() +"\">\n" +
			"<xsl:stylesheet version=\"2.0\"\n" +
			"xmlns:xsl='http://www.w3.org/1999/XSL/Transform'\n" +
			"xmlns:saxon='http://icl.com/saxon'\n" +
			"xmlns:m='http://www.w3.org/1998/Math/MathML'\n" +
			"xmlns:xs='http://www.w3.org/2001/XMLSchema'\n" +
			"xmlns:fn='http://www.w3.org/2005/xpath-functions'\n" +
			"xmlns:lit='espacelit'\n" +
			"xmlns:doc='espaceDoc'>\n" +
			"<xsl:output method=\"text\" encoding=\""+sortieEncoding +"\" indent=\"no\"/>\n" +
			"<!-- Bruno :je décommente la ligne suivante et j'ajoute les éléments où il faut virer les espaces -->\n" +
			"<xsl:strip-space elements = \"doc:doc lit espace phrase mot ponctuation m:* m:math m:semantics m:mrow m:msqrt\" />" +
			"\n<!-- *************** PARAMETRES ***************** -->\n" +
			"<xsl:param name=\"longueur\" as=\"xs:integer\" select=\""+ConfigNat.getCurrentConfig().getLongueurLigne()+"\"/>\n" +
			"<xsl:param name=\"coupons\" as=\"xs:integer\" select=\"");
			if(ConfigNat.getCurrentConfig().getCoupure()){fichierXSL.write("1");}
			else{fichierXSL.write("0");}
			fichierXSL.write("\"/>\n" +
			"<!-- coupons = 1 si on coupe l'equation, 0 sinon-->\n" +
			"<!-- sagouin = 1: si la coupure n'est pas possible proprement, couper quand même -->\n" +
			"<xsl:param name=\"sagouin\" as=\"xs:integer\" select=\"");
			if(ConfigNat.getCurrentConfig().getModeCoupureSagouin()){fichierXSL.write("1");}
			else{fichierXSL.write("0");}
			fichierXSL.write("\"/>\n" + 
			"<xsl:param name=\"abrege\" as=\"xs:string\" select=\"'");
			if(ConfigNat.getCurrentConfig().getAbreger()){fichierXSL.write("1");}
			else{fichierXSL.write("0");}
			fichierXSL.write("'\"/>\n" + 
			"\t<!-- abrege = 1 si braille abrege, 0 sinon, defaut a 1 -->\n" +
			"<xsl:param name=\"trigoSpecif\" as=\"xs:integer\" select=\"");
			if(ConfigNat.getCurrentConfig().getMathTrigoSpec()){fichierXSL.write("1");}
			else{fichierXSL.write("0");}
			fichierXSL.write("\"/>\n" + "<xsl:param name=\"cp_2\" as=\"xs:integer\" select=\"");
			
			if(ConfigNat.getCurrentConfig().getLitMajDouble()){fichierXSL.write("1");}
			else{fichierXSL.write("0");}
			fichierXSL.write("\"/>\n"+"<xsl:param name=\"cp_part\" as=\"xs:integer\" select=\"");
			
			if(ConfigNat.getCurrentConfig().getLitMajPassage()){fichierXSL.write("1");}
			else{fichierXSL.write("0");}
			fichierXSL.write("\"/>\n"+"<xsl:param name=\"cp_mix\" as=\"xs:integer\" select=\"");
			
			if(ConfigNat.getCurrentConfig().getLitMajMelange()){fichierXSL.write("1");}
			else{fichierXSL.write("0");}
			fichierXSL.write("\"/>\n"+"<xsl:param name=\"emph_w\" as=\"xs:integer\" select=\"");
			
			if(ConfigNat.getCurrentConfig().getLitEvidenceMot()){fichierXSL.write("1");}
			else{fichierXSL.write("0");}
			fichierXSL.write("\"/>\n"+"<xsl:param name=\"emph_part\" as=\"xs:integer\" select=\"");
			
			if(ConfigNat.getCurrentConfig().getLitEvidencePassage()){fichierXSL.write("1");}
			else{fichierXSL.write("0");}
			fichierXSL.write("\"/>\n"+"<xsl:param name=\"emph_mix\" as=\"xs:integer\" select=\"");
			
			if(ConfigNat.getCurrentConfig().getLitEvidenceDansMot()){fichierXSL.write("1");}
			else{fichierXSL.write("0");}
			fichierXSL.write("\"/>\n");
			//caractères à chercher pour usage spécifique
			String[] carSpec = donneCharNonUtilise(33,6,gestErreur);
			gestErreur.AfficheMessage("\n** Utilisation de '"+ carSpec[0] +"' pour la coupure ",Nat.LOG_VERBEUX);
			gestErreur.AfficheMessage("\n** Utilisation de '"+ carSpec[1] +"' pour la coupure esthétique en mathématique ",Nat.LOG_VERBEUX);
			gestErreur.AfficheMessage("\n** Utilisation de '"+ carSpec[2] +"' pour début d'expression mathématique ",Nat.LOG_VERBEUX);
			gestErreur.AfficheMessage("\n** Utilisation de '"+ carSpec[3] +"' pour fin d'expression mathématique ",Nat.LOG_VERBEUX);
			gestErreur.AfficheMessage("\n** Utilisation de '"+ carSpec[4] +"' pour l'espace insécable à générer ",Nat.LOG_VERBEUX);
			gestErreur.AfficheMessage("\n** Utilisation de '"+ carSpec[5] +"' pour l'espace sécable à générer ",Nat.LOG_VERBEUX);
			fichierXSL.write("<xsl:param name=\"coupe\" as=\"xs:string\" select=\"'" + carSpec[0] + "'\" />\n" +
			"<xsl:param name=\"coupeEsth\" as=\"xs:string\" select=\"'" + carSpec[1] + "'\" />\n" +
			"<xsl:param name=\"debMath\" as=\"xs:string\" select=\"'" + carSpec[2] + "'\" />\n" +
			"<xsl:param name=\"finMath\" as=\"xs:string\" select=\"'" + carSpec[3] + "'\" />\n" +
			"<xsl:param name=\"espace\" as=\"xs:string\" select=\"'"+ carSpec[4] + "'\" />\n" +
			"<xsl:param name=\"espaceSecable\" as=\"xs:string\" select=\"'" + carSpec[5] + "'\" />\n" +
			"\n<!-- *************** VARIABLES ******************* -->\n" +
			"<!-- variables pour eviter que les translate plantent en mettant directement les entités -->\n" +
			"<xsl:variable name=\"apos\" as=\"xs:string\">'</xsl:variable>\n" +
			"<xsl:variable name=\"quot\" as=\"xs:string\">\"</xsl:variable>\n" +
			"<xsl:include href=\"base.xsl\"/>\n");
			/* TAN ! **/
			if(sens)
			{
				fichierXSL.write("<!--TAN!!!-->\n");
				fichierXSL.write("<xsl:include href=\"" + "tan.xsl" +"\"/>\n");
			}
			else
			{
				if (ConfigNat.getCurrentConfig().getTraiterLiteraire())
				{
					fichierXSL.write("<!--Litéraire-->\n");
					if (ConfigNat.getCurrentConfig().getAbreger())
					{
						fichierXSL.write("<xsl:include href=\"" + ConfigNat.getCurrentConfig().getXSL_g2() +"\"/>\n");
					}
					else
					{
						fichierXSL.write("<xsl:include href=\"" + ConfigNat.getCurrentConfig().getXSL_g1() +"\"/>\n");
					}
				}
				if (ConfigNat.getCurrentConfig().getTraiterMaths())
				{
					fichierXSL.write("<!-- mathématiques -->\n<xsl:include href=\"" + ConfigNat.getCurrentConfig().getXSL_maths() +"\"/>");
				}
				
				if (ConfigNat.getCurrentConfig().getTraiterMusique())
				{
					fichierXSL.write("\n<!-- musique -->\n<xsl:include href=\"" + ConfigNat.getCurrentConfig().getXSL_musique() +"\"/>");
				}
			}
			fichierXSL.write("</xsl:stylesheet>");
			fichierXSL.close();
		}
		catch (IOException e)
		{
			gestErreur.AfficheMessage("\nerreur dans: " + e,Nat.LOG_SILENCIEUX);
		}
	}
	
	/**
	 * Réalise la transcription du fichier d'entrée vers le fichier de sortie en utilisant
	 * le filtre xsl
	 * @param	gestErreur	Un objet <code>GestionnaireErreur</code> pour l'affichage et la gestion
	 * des improbables erreurs.
	 */
	public boolean Transcrire(GestionnaireErreur gestErreur)
	{
		boolean retour = true;
		tempsExecution = System.currentTimeMillis();
		//Exception exception = null;
		gestErreur.AfficheMessage("\nDébut de la transcription ... ok\n",Nat.LOG_SILENCIEUX);
		gestErreur.AfficheMessage("** Fichiers :\n Fichier d'entree:" + entree + "\n Filtre :" + filtre,Nat.LOG_VERBEUX);
		gestErreur.AfficheMessage("\n** Mise en place du scénario de transcription...",Nat.LOG_VERBEUX);
		CreerFiltre(gestErreur);
		gestErreur.AfficheMessage("ok\n** Création de la fabrique (DocumentBuilderFactory) ...",Nat.LOG_VERBEUX);
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		//configuration de la fabrique
		factory.setNamespaceAware(true);
		factory.setValidating(true);
		factory.setIgnoringElementContentWhitespace(true);
		factory.setIgnoringComments(true);
		factory.setIgnoringElementContentWhitespace(false);
		try 
		{
			DocumentBuilder builder = factory.newDocumentBuilder();
			gestErreur.AfficheMessage("ok\n** Parsage du document d'entrée avec SAX ...",Nat.LOG_VERBEUX);
			builder.setErrorHandler(gestErreur);
			System.out.println("\nEntree :"+entree+";filtre :"+filtre+";\n");
			Document doc = builder.parse(new FileInputStream(entree),"UTF-8"); //(new File(entree));
			doc.setStrictErrorChecking(true);
			gestErreur.AfficheMessage("ok\n** Initialisation et lecture de la feuille de style ...",Nat.LOG_VERBEUX);
			TransformerFactory transformFactory = TransformerFactory.newInstance();
			StreamSource styleSource = new StreamSource(new File(filtre));
			// lire le style
			
			Transformer transform = transformFactory.newTransformer(styleSource);
			//transform.setOutputProperty(name, value)
			// conformer le transformeur au style
			DOMSource in = new DOMSource(doc);
			gestErreur.AfficheMessage("ok\n** Création du fichier de sortie ...",Nat.LOG_VERBEUX);
			// Création du fichier de sortie
			File file = new File(cible);
			///Result resultat = new StreamResult(fichier);
			StreamResult out = new StreamResult(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file),"UTF-8")));
			gestErreur.AfficheMessage("ok\n** Transformation du document ...",Nat.LOG_VERBEUX);
			transform.setParameter("abrege",abrege);
			transform.transform(in, out);
			// transformer selon le style 
			tempsExecution = System.currentTimeMillis() - tempsExecution;
		}
		catch (Exception e)  
		{
			gestErreur.setException(e);
			gestErreur.GestionErreur();
			retour = false;
		}
		return retour;
	}
	/**
	 * Cherche quel est le premier caractère non utilisé dans la table braille pour s'en servir
	 * comme caractère de coupure braille
	 * @param	debut	le code à partir duquel on commence à chercher
	 * @param	gestErreur	Un objet <code>GestionnaireErreur</code> pour l'affichage et la gestion
	 * des improbables erreurs.
	 * @return le premier caractère disponible pour couper, rendu sous forme de String
	 */
	private String[] donneCharNonUtilise(int debut, int nb, GestionnaireErreur gestErreur)
	{
		String[] retour = new String[nb];
		gestErreur.AfficheMessage("\n** Recherche de " + nb +" caractères non utilisés...",Nat.LOG_VERBEUX);
		ArrayList<String> donnees = new ArrayList<String>();
		try
		{
	      RandomAccessFile raf = new RandomAccessFile("./xsl/tablesBraille/Brltab.ent", "r");
	      String ligne;
	      String[] enregistrement;
	      int i=1;
	      
	      ligne = raf.readLine();
	      //on cherche le début des entitées
	      while(!ligne.startsWith("<!ENTITY") && ligne!=null){ ligne = raf.readLine();}
	      if (ligne==null){gestErreur.AfficheMessage("\n*** Erreur: Table Braille non valide\n",Nat.LOG_SILENCIEUX);}
	      else
	      {
	    	  enregistrement = ligne.split(" ");
	
	    	  /*if(!enregistrement[2].startsWith("\"&#"))
	    	  {
	    		  if (enregistrement[2].startsWith("\"&apos;")){donnees.add("39");}
	    		  else if (enregistrement[2].startsWith("\"&quot;")){donnees.add("34");}
	    		  else if (enregistrement[2].startsWith("\"&lt;")){donnees.add("60");}
	    		  else if (enregistrement[2].startsWith("\"&gt;")){donnees.add("62");}
	    		  else{donnees.add("38");}
	    	  }
	    	  else{donnees.add(enregistrement[2].substring(3, enregistrement[2].length()-3));}
	    	  */
	    	  if(enregistrement[2].startsWith("\"&#")){donnees.add(enregistrement[2].substring(3, enregistrement[2].length()-3));}
		      while ( (ligne = raf.readLine()) != null)
			  	{
		    	  enregistrement = ligne.split(" ");
		    		
		    	  /*if(!enregistrement[2].startsWith("\"&#"))
		    	  {
		    		  if (enregistrement[2].startsWith("\"&apos;")){donnees.add("39");}
		    		  else if (enregistrement[2].startsWith("\"&quot;")){donnees.add("34");}
		    		  else if (enregistrement[2].startsWith("\"&lt;")){donnees.add("60");}
		    		  else if (enregistrement[2].startsWith("\"&gt;")){donnees.add("62");}
		    		  else{donnees.add("38");}
		    	  }
		    	  else{donnees.add(enregistrement[2].substring(3, enregistrement[2].length()-3));}*/
		    	  if(enregistrement[2].startsWith("\"&#"))
		    	  {
		    		  donnees.add(enregistrement[2].substring(3, enregistrement[2].length()-3));
		    	  }
		    	  i++;
		  	    }
	      }
	      raf.close();
	      //ajout des caractères interdits
	      donnees.add("34");//"
	      donnees.add("39");//'
	      donnees.add("38");//&
	      donnees.add("60");//<
	      donnees.add("63");//>
	      i=debut;
	      int j=0;
	      while(j<nb)
	      {
	    	  retour[j]=Integer.toString(i);
	    	  if(!donnees.contains(retour[j]))
	    	  {
	    		  retour[j] = "" + (char)(i);
	    		  gestErreur.AfficheMessage("\n    trouvé: '"+ retour[j] +"'("+i+") ",Nat.LOG_VERBEUX);
	    		  j++;
	    	  }
	    	  i++;
	      }
		}
		catch (IOException e){gestErreur.AfficheMessage("\n *** Erreur d'entrée/sortie lors du chargement de la table braille", Nat.LOG_SILENCIEUX);}
		catch (NumberFormatException e){gestErreur.AfficheMessage("\n *** La table Braille n'est pas valide: " + e, Nat.LOG_SILENCIEUX);}
		
		return retour;
	}
}