/*
 * NAT - An universal Translator
 * 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;

import java.awt.Frame;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;

import outils.ConfConv;
import nat.ConfigNat;
import nat.convertisseur.Convertisseur;
import nat.convertisseur.ConvertisseurChaine;
import nat.presentateur.PresentateurMEP;
import nat.transcodeur.Transcodeur;
import nat.transcodeur.TranscodeurNormal;
import nat.presentateur.Presentateur;

import joptsimple.OptionSet;
import joptsimple.OptionException;

import gestionnaires.AfficheurLog;
import gestionnaires.GestionnaireErreur;
import ui.AfficheurConsole;
import ui.FenetrePrinc;
/**
 * Classe principale de l'application
 * @author bruno
 *
 */
public class Nat 
{
    //constantes
	/** Représente un niveau de verbosité des logs muet */
	public final static int LOG_AUCUN = 0;
	/** Représente un niveau de verbosité des logs très faible */
    public final static int LOG_SILENCIEUX = 1;
    /** Représente un niveau de verbosité des logs normal */
    public final static int LOG_NORMAL = 2;
    /** Représente un niveau de verbosité des logs verbeux */
    public final static int LOG_VERBEUX = 3;
    /** Représente un niveau de verbosité des logs verbeux avec les informations de débuggage */
    public final static int LOG_DEBUG = 4;
    /** Représente la génération de version de configuration */
    public final static String CONFS_VERSION = "3" ;
    /** adresse web du fichier contenant le n° de la dernière version en ligne */
	private static final String CURRENT_VERSION_ADDRESS = "http://natbraille.free.fr/current-version.txt";
 
	/** String contenant la licence de NAT (GPL) */
    private static String licence;
    /** Une instance de gestionnaire d'erreur */
    private GestionnaireErreur gest;
    /** true si pas de transcriptions en cours */
    private boolean ready = true;
    /** true si nouvelle version disponible */
    private boolean updateAvailable = false;
    

	/** Liste d'instances de transcription représentant les transcription à réaliser */
	private ArrayList<Transcription> transcriptions = new ArrayList<Transcription>();
	//TODO raph a remplacer par ArrayList<Transcription> transcriptions = new ArrayList<Transcription>(); ???
	/**
	 * Constructeur
	 * @param g Une instance de GestionnaireErreur
	 */
    public Nat(GestionnaireErreur g) 
    {
		licence = getLicence("","");
		gest = g;
    }

    /* méthodes d'accès */
    /**
     * renvoie le nom du fichier de configuration
     * @return le nom du fichier de configuration
     */
    public String getFichierConf(){return ConfigNat.getCurrentConfig().getFichierConf();}	
	/**
	 * Renvoie une chaine contenant le numéro long de la version de NAT
	 * @return une chaine contenant le numéro long de version
	 */
    public String getVersionLong(){return ConfigNat.getVersionLong();}
    /**
	 * Renvoie une chaine contenant le nom de version de NAT
	 * @return une chaine contenant le nom de version
	 */
    public String getVersion(){return ConfigNat.getVersion();}
    /**
     * @param ua the updateAvailable to set
     * @see #updateAvailable
     */
    public void setUpdateAvailable(boolean ua){updateAvailable = ua;}

	/**
     * @return the updateAvailable value
     * @see #updateAvailable
     */
    public boolean isUpdateAvailable(){return updateAvailable;}

	/**
     * Renvoie l'instance de GestionnaireErreur
     * @return l'instance de GestionnaireErreur
     * @see Nat#gest
     */
	public GestionnaireErreur getGestionnaireErreur() {return gest;}
	
	/**
	 * Renvoie la licence de nat préfixée par prefixe et terminée par suffixe
	 * @param prefixe préfixe à insérer avant la licence (/* ou <!-- par exemple)
	 * @param suffixe suffixe à insérer après la licence (* / ou --> par exemple)
	 * @return la licence de NAT
	 */
    public static String getLicence(String prefixe, String suffixe)
    {
	licence =  prefixe + " * NAT - An universal Translator\n" +
	    "* Copyright (C) 2009 Bruno Mascret\n" +
	    "* Contact: bmascret@free.fr\n" +
	    "* \n" +
	    "* This program is free software; you can redistribute it and/or\n" +
	    "* modify it under the terms of the GNU General Public License\n" +
	    "* as published by the Free Software Foundation; either version 2\n" +
	    "* of the License.\n" +
	    "* \n" +
	    "* This program is distributed in the hope that it will be useful,\n" +
	    "* but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +
	    "* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n" +
	    "* GNU General Public License for more details.\n" +
	    "* \n" +
	    "* You should have received a copy of the GNU General Public License\n" +
	    "* along with this program; if not, write to the Free Software\n" +
	    "* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n" +
	    suffixe;	
	return licence;
    }
    /**
     * Fait appel à la fabrique Transcription pour obtenir les instances de transcription à réaliser
     * Utilise le booléen <code>reverse</code> pour contraindre le sens de transcription 
     * @param noirs les adresses des fichiers noir
     * @param brailles les adresses des fichiers braille
     * @param reverse indique le sens de transcription: true si inverse, false sinon
     * @return <code>true</code> si la fabrication a réussi
     * @see Transcription#fabriqueTranscription(String, String, GestionnaireErreur, boolean)
     */
    public boolean fabriqueTranscriptions(ArrayList<String> noirs, ArrayList<String> brailles, boolean reverse)
    {
		boolean retour = true;
		//on vide la liste
		transcriptions.removeAll(transcriptions);
		for(int i=0;i<noirs.size();i++)
		{
			String noir = noirs.get(i);
			String braille = brailles.get(i);
			Transcription t = Transcription.fabriqueTranscription(noir,braille,gest,reverse);
			if(t!=null){transcriptions.add(t);}
			else{retour=false;}
		}
		return retour;
    }
    /**
     * Fait appel à la fabrique Transcription pour obtenir les instances de transcription à réaliser
     * Ne détermine pas le sens de la transcription, qui sera établit dans {@link Transcription#fabriqueTranscription(String, String, GestionnaireErreur)}
     * @param noirs les adresses des fichiers noirs
     * @param brailles les adresses des fichiers braille
     * @return <code>true</code> si la fabrication a réussi
     * @see Transcription#fabriqueTranscription(String, String, GestionnaireErreur)
     */
    public boolean fabriqueTranscriptions(ArrayList<String> noirs, ArrayList<String> brailles)
    {
		boolean retour = true;
		//on vide la liste
		transcriptions.removeAll(transcriptions);
		for(int i=0;i<noirs.size();i++)
		{
			String noir = noirs.get(i);
			String braille = brailles.get(i);
			Transcription t = Transcription.fabriqueTranscription(noir,braille,gest);
			if(t!=null){transcriptions.add(t);}
			else{retour=false;}
		}
		return retour;
    }
    /**
     * Lance le processus complet de transcription des instances de <code>transcription</code>
     * Attends éventuellement si une transcription est en cours
     * @return true si le scénario s'est déroulé normallement
     * @see Nat#transcriptions
     */
    public boolean lanceScenario()
    {
    	if(!ready)
    	{
    		gest.afficheMessage("\nLa transcription commencera dès la fin de la transcription en cours\n", Nat.LOG_NORMAL);
	    	while(!ready)
	    	{
	    		try {Thread.sleep(1000);}
				catch (InterruptedException e) {e.printStackTrace();}
	    	}
    	}
    	ready=false;
    	gest.setException(null);
    	boolean retour = true;
    	for(Transcription t : transcriptions)
    	{
	    	try
	    	{
	    		retour = retour & t.transcrire();
	    	}
	    	catch(OutOfMemoryError oome)
	    	{
	    		gest.setException(new Exception("mémoire",oome));
	    		gest.gestionErreur();
	    	}
    	}
    	ready =true;
		return retour;
    }
    /**
     * Appel à la méthode touveEncodingSource de Transcription
     * @param source le fichier source
     * @return une chaîne correspondant à l'encodage du fichier source
     * @see Transcription#trouveEncodingSource(String, GestionnaireErreur)
     */
    public String trouveEncodingSource(String source){return Transcription.trouveEncodingSource(source, gest);}
    /**
     * Charge certaines options de la ligne de commande dans le singleton de ConfigNat 
     * @param options OptionSet des options
     */
    public static void loadCliOptions(OptionSet options)
    {	
		ConfigNat cc = ConfigNat.getCurrentConfig();
	
		String nom = OptNames.ge_log_verbosity;
		if (options.has(nom)){cc.setNiveauLog(((Integer) options.valueOf(nom)).intValue());}
	
		nom = OptNames.fi_braille_table;
		String sys = OptNames.fi_is_sys_braille_table;
		if (options.has(nom))
		{
			if(options.has(sys))
			{
				cc.setIsSysTable(((Boolean) options.valueOf(sys)).booleanValue());
				cc.setTableBraille(((String) options.valueOf(nom)),cc.getIsSysConfig());
			}
			else{cc.setTableBraille(((String) options.valueOf(nom)),true);}
		}
		
		nom = OptNames.fi_math_transcribe;
		if (options.has(nom)){cc.setTraiterMaths(((Boolean) options.valueOf(nom)).booleanValue());}
	
		nom = OptNames.fi_math_use_trigo_spec;
		if (options.has(nom)){cc.setMathTrigoSpec(((Boolean) options.valueOf(nom)).booleanValue());}
		
		nom = OptNames.fi_litt_transcribe;
		if (options.has(nom)){cc.setTraiterLiteraire(((Boolean) options.valueOf(nom)).booleanValue());}
	
		nom = OptNames.fi_litt_abbreg;
		if (options.has(nom)){cc.setAbreger(((Boolean) options.valueOf(nom)).booleanValue());}
	
		nom = OptNames.fi_music_transcribe;
		if (options.has(nom)){cc.setTraiterMusique(((Boolean) options.valueOf(nom)).booleanValue());}
	
		nom = OptNames.fi_hyphenation;
		if (options.has(nom)){cc.setCoupure(((Boolean) options.valueOf(nom)).booleanValue());}
	
		nom = OptNames.fi_hyphenation_dirty;
		if  (options.has(nom)){cc.setModeCoupureSagouin(((Boolean) options.valueOf(nom)).booleanValue());}
	
		nom = OptNames.fi_line_lenght;
		if (options.has(nom)){cc.setLongueurLigne(((Integer) options.valueOf(nom)).intValue());}
		
		nom = OptNames.en_in;
		if (options.has(nom)){cc.setNoirEncoding((String)options.valueOf(nom));}
	
		nom = OptNames.en_out;
		if (options.has(nom)){cc.setBrailleEncoding((String)options.valueOf(nom));}
    }
    
    /**
     * Méthode main
     * Analyse la chaine de paramètres, lance ou non l'interface graphique, la transcription, etc
     * @param argv les paramètres de la méthode main
     */
    public static void main (String argv [])
    {
    	//System.setProperty("file.encoding","UTF-8");
    	// Gestionnaire d'erreur de base (non utilisé par interface graphique)
    	//initialisation de certaines propriétés
		System.setProperty("javax.xml.transform.TransformerFactory",
		"net.sf.saxon.TransformerFactoryImpl");
		System.setProperty("javax.xml.parsers.DocumentBuilderFactory",
				"org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
    	ConfigNat.charger(null);  
    	GestionnaireErreur gestErreur = new GestionnaireErreur(null,ConfigNat.getCurrentConfig().getNiveauLog());
		
    	Nat nat = new Nat(gestErreur);
    	
		AfficheurConsole ac = new AfficheurConsole();
    	gestErreur.addAfficheur(ac);
    	gestErreur.addAfficheur(new AfficheurLog());
    	OptionParserNat parser = new OptionParserNat();
    	
    	try
	    {
    		OptionSet options = parser.parse(argv);
    		ConfConv.convert(gestErreur);
    		if (options.has("gui"))
		    {	 
				FenetrePrinc fenetre = new FenetrePrinc(nat);
				fenetre.pack();
				if (ConfigNat.getCurrentConfig().getMaximizedPrincipal())
		    		{fenetre.setExtendedState(Frame.MAXIMIZED_BOTH);}
				fenetre.setVisible(true);
		    }
    		else if (options.has("f") && options.has("t"))
		    {
    			if (options.has("q")) {gestErreur.removeAfficheur(ac);}
				if (options.has("c")) {ConfigNat.charger(options.valueOf( "c" ).toString());}
				loadCliOptions(options);
				
    			String[] cl_from = options.valueOf( "f" ).toString().split(":");
    			String[] cl_to = options.valueOf( "t" ).toString().split(":");
    			
    			/* exécution dans la console ou en ligne de commande?*/
    			if(cl_from[0].equals("-"))
    			{
    				gestErreur.removeAfficheur(ac);
    				gestErreur.afficheMessage("\nLecture de l'entrée standard... Ctrl+C pour quitter \n",Nat.LOG_NORMAL);
    				while(true)
    				{
    					boolean fin=false;
    					String chaine="";
    					while(!fin)
    					{
    						try 
    						{
								char c = (char)(System.in.read());
								if(c=='\n')
								{
									fin=true;
								}
								else
								{
									chaine=chaine+c;
								}
							} 
    						catch (IOException e) {e.printStackTrace();}
    					}
    					Convertisseur c = new ConvertisseurChaine(chaine,ConfigNat.getUserTempFolder()+"/tmp.xml","UTF-8");
    					Transcodeur t=new TranscodeurNormal(ConfigNat.getUserTempFolder()+"/tmp.xml",
    							ConfigNat.getUserTempFolder()+"/tmp.txt","UTF-8",gestErreur);
    					Presentateur p=new PresentateurMEP(gestErreur, "UTF-8", ConfigNat.getUserTempFolder()+"/tmp.txt",
    							ConfigNat.getUserTempFolder()+"/out.txt", "brailleUTF8");
    					
    					c.convertir(gestErreur);
    					t.transcrire(gestErreur);
    					p.presenter();
    					try {
    						BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(ConfigNat.getUserTempFolder()+"/out.txt")));
							System.out.println(br.readLine());
						} catch (FileNotFoundException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
    				}
    				/*
    				 * fin du programme ici, seul un arrêt total du programme permet de sortir de la
    				 * boucle infinie
    				 * 
    				 */
    			}
    			/*
    			 * Si pas fonctionnement temps réel
    			 */
				gestErreur.afficheMessage("conversion de "+cl_from+" vers "+cl_to,Nat.LOG_NORMAL);
				//fabrication des listes
				ArrayList<String> sources = new ArrayList<String>();
				ArrayList<String> cibles = new ArrayList<String>();
				for (int i=0;i<cl_from.length;i++)
				{
					sources.add(cl_from[i]);
					if(i>=cl_to.length){cibles.add(cl_from[i]+".braille");}
					else{cibles.add(cl_to[i]);}
				}
				if (nat.fabriqueTranscriptions(sources, cibles)){nat.lanceScenario();}
				else
				{
					gestErreur.afficheMessage("\n**ERREUR: certain fichiers n'existe pas et ne pourront être transcrits", Nat.LOG_SILENCIEUX);
					nat.lanceScenario();
				}
		    }
    		else 
		    {
    			try{parser.printCliUsage();}
    			catch(IOException ioe){gestErreur.afficheMessage("\nErreur d'affichage pour les options", Nat.LOG_SILENCIEUX);}
		    }
	    }
    	catch (OptionException ex)
	    {
    		System.err.println( "====" );
    		gestErreur.afficheMessage("\nProblème dans la ligne de commande", Nat.LOG_VERBEUX);
    		try{parser.printCliUsage();}
			catch(IOException ioe){gestErreur.afficheMessage("\nErreur d'affichage pour les options", Nat.LOG_SILENCIEUX);}
	    }
    }

	/**
     * Vérifie si une nouvelle version est disponible en ligne
     * Met à jour {@link #updateAvailable}
     * @return true si vérification effectuée, false si vérification impossible
     */
    public boolean checkUpdate()
    {
        boolean retour = true;
    	gest.afficheMessage("Recherche d'une mise à jour de NAT...", LOG_VERBEUX);
    	URL url;
		try 
		{
			url = new URL(CURRENT_VERSION_ADDRESS);
			URLConnection urlCon = url.openConnection();
	        
			BufferedReader br = new BufferedReader(new InputStreamReader(urlCon.getInputStream()));
	        
	        String ligne= br.readLine();
	        br.close();
	        if(Integer.parseInt(ligne) > ConfigNat.getSvnVersion())
	        {
	        	updateAvailable = true;
	        }
		}
		catch (NumberFormatException nfe){gest.afficheMessage("\n** pas de connexion web pour vérifier la présence de mise à jour", Nat.LOG_SILENCIEUX);retour=false;}
		catch (MalformedURLException e) {gest.afficheMessage("\n** adresse internet " + CURRENT_VERSION_ADDRESS +" non valide", Nat.LOG_SILENCIEUX);retour=false;}
		catch (IOException e) {gest.afficheMessage("\n** erreur d'entrée sortie lors de la vérification de l'existence d'une mise à jour", Nat.LOG_SILENCIEUX);retour=false;}
                return retour;
	   
    }

}
