/*
 * 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 ui;

import gestionnaires.GestionnaireErreur;
import gestionnaires.GestionnaireExporter;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Hashtable;

import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.KeyStroke;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.undo.UndoManager;

import nat.ConfigNat;

import outils.Embosseur;
import outils.FileToolKit;
import outils.TextSender;

/**
 * Classe décrivant un éditeur braille dans nat
 * @author bruno
 *
 */
public abstract class EditeurBraille extends JFrame implements WindowListener, ActionListener, KeyListener, ComponentListener, TextSender, DocumentListener, UndoableEditListener,FocusListener
{

	/** Objet embosseur, pour l'embossage bien entendu...*/
	protected Embosseur embosseur;
	/** identifiant par défaut pour la sérialisation (non utilisé dans NAT)*/ 
	private static final long serialVersionUID = 1L;
	/** le label contenant le nom du fichier transcrit */
	protected JLabel lFichier = new JLabel("Document: ");
	/** le panneau contenant les éléments à afficher et le scrollPane*/
	protected JPanel panneauAffichage = new JPanel();
	/** la zone d'affichage principale du fichier */
	protected JTextPane resultat = new JTextPane();
	/** le ScrollPane associé au JTextPane resultat */
	protected JScrollPane scrollRes;
	/** Le panneau contenant les boutons d'action */
	protected JPanel lesBoutons = new JPanel();
	/** JButton pour enregistrer le fichier */
	protected JButton btEnregistrer = new JButton("Enregistrer",new ImageIcon("ui/icon/document-save.png"));
	/** JButton pour enregistrer le fichier */
	protected JButton btEnregistrersous = new JButton("Enregistrer sous",new ImageIcon("ui/icon/document-save-as.png"));
    /** JButton pour fermer la fenêtre */
    protected JButton btFermer = new JButton("Fermer",new ImageIcon("ui/icon/exit.png"));
    /** L'adresse du fichier transcrit */
	protected String fichier = null;
	/** encodage du fichier transcrit */
	protected String encodage ="UTF-8";
	/** Jlabel d'information pour l'enregistrement */
	protected JLabel message= new JLabel("");
	/** taille du caractère de la police principale en point (ne fonctionne qu'avec les polices à
	 * chasse fixe */
	protected int tailleCaractere;
	/** tableau destiné à recevoir les codes perkins */
	protected boolean [] tabPoint = new boolean[6];
	/** JCheckBoxpour l'activation du mode perkins */
	protected JCheckBox jcbPerkins = new JCheckBox("Saisie Perkins");
	/** JButton pour lancer l'embossage */
	protected JButton btEmbosser = new JButton("Embosser",new ImageIcon("ui/icon/document-print.png"));
	/** nombre de touches pressées lors d'une saisie perkins */
	protected int nbTouches = 0;
	
	//private boolean resizing = false;
	/** table braille à utiliser pour la saisie perkins */
	protected String tableBraille = ConfigNat.getUserBrailleTableFolder()+"Brltab.ent";
	/** HashTable pour la correspondance entre les caractères braille saisis en mode perkins et leur
	 * représentation dans la table braille */
	protected Hashtable<String,String> ptPerkins = new Hashtable<String,String>();
	
	//fonctionalités pour l'édition
	/** Undo manager */
	protected UndoManager undoMng = new UndoManager();
	/** JButton pour annuler frappe */
	protected JButton btUndo = new JButton(new ImageIcon("ui/icon/edit-undo.png"));
	/** JButton pour répéter frappe */
	protected JButton btRedo = new JButton(new ImageIcon("ui/icon/edit-redo.png"));
	
	/** Position du curseur **/
	protected int positionCurseur = 0;
	
	/** Indique si des modifications sont en cours */
	protected boolean enModif = false;
	/** Indique si il y a des modification non enregistrées */
	protected boolean modif=false;
	
	/** Instance du gestionnaire d'erreur */
	protected GestionnaireErreur gestErreur;
	
	/** 
	 * Construit un objet EditeurBraille
	 * @param nom le nom de la fenêtre
	 * @param emb l'objet Embosseur à utiliser pour l'embossage
	 * @param g instance de GestionnaireErreur
	 */
	public EditeurBraille(String nom, Embosseur emb, GestionnaireErreur g)
	{
		super(nom);
		embosseur = emb;
		gestErreur = g;
		
		addComponentListener(this);
		addWindowListener(this);
		
		/* 
		 * Boutons 
		 */
		btEnregistrer.addActionListener(this);
		btEnregistrer.getAccessibleContext().setAccessibleName("Bouton enregistrer");
		btEnregistrer.getAccessibleContext().setAccessibleDescription("Valider pour enregistrer le fichier");
		btEnregistrer.setToolTipText("Enregistrer le fichier (Alt+s)");
		btEnregistrer.setMnemonic('s');
		
		btEnregistrersous.addActionListener(new GestionnaireExporter(this,this,GestionnaireExporter.EXPORTER_BRF,g));
		btEnregistrersous.getAccessibleContext().setAccessibleName("Bouton enregistrer sous");
		btEnregistrersous.getAccessibleContext().setAccessibleDescription("Valider pour enregistrer le fichier avec un nouveau nom");
		btEnregistrersous.setToolTipText("Enregistrer le fichier avec un nouveau nom (Alt+n)");
		btEnregistrersous.setMnemonic('n');
		
		btFermer.addActionListener(this);
		btFermer.getAccessibleContext().setAccessibleName("Bouton fermer l'éditeur");
		btFermer.getAccessibleContext().setAccessibleDescription("Valider pour fermer l'éditeur");
		btFermer.setToolTipText("Fermer l'éditeur (Alt+f) ou (Alt+F4)");
		btFermer.setMnemonic('f');
		
		btEmbosser.addActionListener(this);
		btEmbosser.getAccessibleContext().setAccessibleName("Bouton embosser");
		btEmbosser.getAccessibleContext().setAccessibleDescription("Valider pour embosser le document");
		btEmbosser.setToolTipText("Embosser le document (Alt+e)");
		btEmbosser.setMnemonic('e');
		if(embosseur==null){btEmbosser.setEnabled(false);}
		
		jcbPerkins.getAccessibleContext().setAccessibleName("Case à cocher saisie Perkins");
		jcbPerkins.getAccessibleContext().setAccessibleDescription("Cocher cette case pour activer ou désactiver la saisie Perkins");
		jcbPerkins.setToolTipText("Saisie Perkins : saisissez les points braille avec FDS et JKL (Alt+p)");
		jcbPerkins.setMnemonic('p');
		jcbPerkins.addActionListener(this);
		
		btUndo.addActionListener(this);
		btUndo.getAccessibleContext().setAccessibleName("Bouton annuler édition");
		btUndo.getAccessibleContext().setAccessibleDescription("Valider pour annuler la dernière modification");
		btUndo.setToolTipText("Annuler la dernière modification (Alt+u)");
		btUndo.setMnemonic('u');
		btUndo.setEnabled(false);
		
		btRedo.addActionListener(this);
		btRedo.getAccessibleContext().setAccessibleName("Bouton rétablir édition");
		btRedo.getAccessibleContext().setAccessibleDescription("Valider pour rétablir la dernière modification");
		btRedo.setToolTipText("Rétablir la dernière modification (Alt+r)");
		btRedo.setMnemonic('r');
		btRedo.setEnabled(false);
		
		scrollRes = new JScrollPane (resultat);
		scrollRes.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
		scrollRes.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
		
		lFichier.setLabelFor(resultat);
		lFichier.setDisplayedMnemonic('t');
		
		resultat.addKeyListener(this);
	}
	
	/*
	 * Assesseurs
	 * 
	 */
	/** Méthode d'accès, modifie la valeur de l'encodage 
	 * @param enc valeur pour {@link #encodage}*/
	public void setEncodage(String enc){encodage = enc;}
	/** Méthode d'accès, indique le nom de la table braille à utiliser 
	 * @param tb valeur pour {@link #tableBraille}*/
	public void setTableBraille(String tb){tableBraille = tb;}
	
	/*
	 * Méthodes	 * 
	 */
	/** Vérifie l'état du manager undo et active ou désactive les boutons undo/redo */
	private void verifBtEdit() 
	{
		btUndo.setEnabled(undoMng.canUndo());
		btRedo.setEnabled(undoMng.canRedo());
	}
	/** 
	 *	Enregistre le fichier
	 */
	protected void enregistrerFichier()
	{
		if (FileToolKit.saveStrToFile(getText(), fichier))
		{
			message.setText("Fichier enregistré");
			modif=false;
		}	
		else{message.setText("<html><p color=\"red\">Erreur lors de l'enregistrement</p></html>");}
	}
	
	/**
	 * Ajoute au document <code>doc</code> les listeners nécéssaires
	 * @param doc le document
	 */
	protected void ajouteListenerDoc(Document doc)
	{
		resultat.setDocument(doc);
		resultat.setCaretPosition(0);
		resultat.getDocument().addDocumentListener(this);
		resultat.getDocument().addUndoableEditListener(this);
	}
	
	/**
	 * Initialise la HashMap d'équivalence entre les entités de la forme &amp;pt123456; et leur
	 * valeur en binaire
	 * @return true si succès, false si erreur lors du chargement
	 * @exception NumberFormatException problème lors de la conversion des entités, la table ne doit pas être valide
	 */
	protected boolean initialiseMap()
	{
		boolean retour = true;
		//création de la hashtable pt -> caractère 
		String fichierTable = tableBraille;
		try
		{
			RandomAccessFile raf = new RandomAccessFile(fichierTable, "r");
			String ligne;
			String[] enregistrement;
	      int i=0;
	      
	      ligne = raf.readLine();
	      //on cherche le début des entitées
	      while(ligne!=null && !ligne.startsWith("<!ENTITY"))
	      {
	    	  ligne = raf.readLine();
	      }
	      if (ligne==null)
	      {
	    	  System.err.println("Le fichier" + fichierTable + "n'est pas un fichier valide");
	    	  message.setText("<html><br><p color=\"red\">Le fichier" + fichierTable + "n'est pas un fichier valide</p></html>");
	    	  retour =false;
	      }
			else
			{
				do
				{
					String pt = "0";
					String code ="";
				   enregistrement = ligne.split(" ");
				   if(!enregistrement[2].startsWith("\"&#"))
				   {
					  if (enregistrement[2].startsWith("\"&apos;")){code = "'";}
					  else if (enregistrement[2].startsWith("\"&quot;")){code = "\"";}
					  else if (enregistrement[2].startsWith("\"&lt;")){code = "<";}
					  else if (enregistrement[2].startsWith("\"&gt;")){code = ">";}
					  else {code = "&";}
					}
				   else{ code = Character.toString((char)(Integer.parseInt(enregistrement[2].substring(3, enregistrement[2].length()-3))));}
				   pt = convertitPoint2Int(enregistrement[1].substring(2,enregistrement[1].length()));
				   ptPerkins.put(pt, code);
				   i++;
				}
			   while ((ligne = raf.readLine()) != null && i<64);
			}
			raf.close();
		}
	   catch (IOException e)
	   {
		   System.err.println("erreur dans: " + e);
		   message.setText(":Erreur d'entrée/sortie lors du chargement");
	   }
	   catch (NumberFormatException e)
	   {
		   System.err.println("La table Braille n'est pas valide: " + e);
		   message.setText("<html><br> <p color=\"red\">La table Braille n'est pas valide</p></html>");
	   }
	   return retour;
	}

	/**
	 * Outil de conversion des entités 123456 par la notation "binaire"
	 * @param s La chaine d'origine sous forme 123456
	 * @return Une chaine représentant l'entier en base 10 obtenu par conversion binaire
	 */
	private String convertitPoint2Int(String s)
	{
		int retour = 0;
		if (s.indexOf("1")>= 0){retour = retour + 1;}
		if (s.indexOf("2")>= 0){retour = retour + 2;}
		if (s.indexOf("3")>= 0){retour = retour + 4;}
		if (s.indexOf("4")>= 0){retour = retour + 8;}
		if (s.indexOf("5")>= 0){retour = retour + 16;}
		if (s.indexOf("6")>= 0){retour = retour + 32;}
		return Integer.toString(retour);
	}
	
	/**
	 * Vérifie si il faut enregistrer le fichier
	 */
	private void verifDoc()
	{
		if(modif)
		{
			if(JOptionPane.showConfirmDialog( this,"Enregistrer les modifications?","Enregistrement", JOptionPane.YES_NO_OPTION)==JOptionPane.OK_OPTION)
			{
				enregistrerFichier();
			}
		}
		dispose();		
	}
	
	/*
	 * 
	 * Implémentations
	 */
	/**
	 * Envoie l'adresse du fichier d'origine ({@link #fichier}
	 * @see outils.TextSender#getOrigine()
	 */
	public String getOrigine() {return fichier;}
	
	/** Non implémentée ici
	 * @see outils.TextSender#getText()
	 */
	public abstract String getText();
	
	/** 
	 * Implémente la méthode actionPerformed d'ActionListener
	 * Gère les actions des boutons et met à jour l'InputMap du JTextPane resultat en fonction de
	 * l'état du JCheckBox jcbPerkins
	 * @param evt l'objet ActionEvent
	 */
	public void actionPerformed(ActionEvent evt)
	{
		if(evt.getSource()==btEnregistrer)
		{
			enregistrerFichier();
			resultat.grabFocus();
		}
		else if (evt.getSource() == btFermer){verifDoc();}
		else if (evt.getSource() == btUndo && undoMng.canUndo())
		{
			undoMng.undo();
			verifBtEdit();
			resultat.grabFocus();
		}
		else if (evt.getSource() == btRedo && undoMng.canRedo())
		{
			undoMng.redo();
			verifBtEdit();
			resultat.grabFocus();
		}
		else if (evt.getSource()==jcbPerkins)
		{
			if (jcbPerkins.isSelected())
			{
				/* Changement de l'action par défaut pour les touches de la saisie
				 * perkins (s, d, f j, k, l). Amélioration possible: plutôt que d'utiliser
				 * writableAction, fabriquer une Action qui ne fait rien
				 */
				InputMap inputMap = resultat.getInputMap();
		        KeyStroke key = KeyStroke.getKeyStroke('s');
		        inputMap.put(key, DefaultEditorKit.writableAction);
		        key = KeyStroke.getKeyStroke('d');
		        inputMap.put(key, DefaultEditorKit.writableAction);
		        key = KeyStroke.getKeyStroke('f');
		        inputMap.put(key, DefaultEditorKit.writableAction);
		        key = KeyStroke.getKeyStroke('j');
		        inputMap.put(key, DefaultEditorKit.writableAction);
		        key = KeyStroke.getKeyStroke('k');
		        inputMap.put(key, DefaultEditorKit.writableAction);
		        key = KeyStroke.getKeyStroke('l');
		        inputMap.put(key, DefaultEditorKit.writableAction);
		        
		        resultat.addKeyListener(this);
			}
			else
			{
				/* rétablissement de l'action par défaut associée au touches de la saisie perkins*/
				InputMap inputMap = resultat.getInputMap();
		        KeyStroke key = KeyStroke.getKeyStroke('s');
		        inputMap.put(key, DefaultEditorKit.defaultKeyTypedAction);
		        key = KeyStroke.getKeyStroke('d');
		        inputMap.put(key, DefaultEditorKit.defaultKeyTypedAction);
		        key = KeyStroke.getKeyStroke('f');
		        inputMap.put(key, DefaultEditorKit.defaultKeyTypedAction);
		        key = KeyStroke.getKeyStroke('j');
		        inputMap.put(key, DefaultEditorKit.defaultKeyTypedAction);
		        key = KeyStroke.getKeyStroke('k');
		        inputMap.put(key, DefaultEditorKit.defaultKeyTypedAction);
		        key = KeyStroke.getKeyStroke('l');
		        inputMap.put(key, DefaultEditorKit.defaultKeyTypedAction);
		        resultat.removeKeyListener(this);
			}
			resultat.grabFocus();
		}
	}
	
	/**
	 * Implémente removeUpdate de DocumentListener
	 * @see javax.swing.event.DocumentListener#removeUpdate(javax.swing.event.DocumentEvent)
	 */
	public void removeUpdate(DocumentEvent de) 
	{
		message.setText("Fichier modifié");		
		verifBtEdit();
	}
	/**
	 * Implémente undoableEditHappened de UndoableEditListener
	 * @see javax.swing.event.UndoableEditListener#undoableEditHappened(javax.swing.event.UndoableEditEvent)
	 */
	public void undoableEditHappened(UndoableEditEvent uee) 
	{
		undoMng.addEdit(uee.getEdit());
		verifBtEdit();
	}
	/**
	 * Implémente focusGained de Focus Listener
	 * positionne le curseur sur le text area
	 * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent)
	 */
	public void focusGained(FocusEvent arg0){resultat.setCaretPosition(positionCurseur);}
	/**
	 * Ne fait rien de plus
	 * @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent)
	 */
	public void focusLost(FocusEvent arg0) {positionCurseur=resultat.getCaretPosition();}
	
	/**
	 * Ne fait rien
	 * @see java.awt.event.WindowListener#windowActivated(java.awt.event.WindowEvent)
	 */
	@Override
	public void windowActivated(WindowEvent arg0) {/*rien*/}
	/**
	 * ne fait rien
	 * @see java.awt.event.WindowListener#windowClosed(java.awt.event.WindowEvent)
	 */
	@Override
	public void windowClosed(WindowEvent arg0) {/*rien*/}
	/**
	 * Vérifie si il faut demander l'enregistrement
	 * Appelle {@link #verifDoc()}
	 * @see java.awt.event.WindowListener#windowClosing(java.awt.event.WindowEvent)
	 */
	@Override
	public void windowClosing(WindowEvent arg0) {verifDoc();/*rien*/}
	/**
	 * Ne fait rien
	 * @see java.awt.event.WindowListener#windowDeactivated(java.awt.event.WindowEvent)
	 */
	@Override
	public void windowDeactivated(WindowEvent arg0) {/*rien*/}
	/**
	 * Ne fait rien
	 * @see java.awt.event.WindowListener#windowDeiconified(java.awt.event.WindowEvent)
	 */
	@Override
	public void windowDeiconified(WindowEvent arg0) {/*rien*/}
	/**
	 * Ne fait rien
	 * @see java.awt.event.WindowListener#windowIconified(java.awt.event.WindowEvent)
	 */
	@Override
	public void windowIconified(WindowEvent arg0) {/*rien*/}
	/**
	 * Ne fait rien
	 * @see java.awt.event.WindowListener#windowOpened(java.awt.event.WindowEvent)
	 */
	@Override
	public void windowOpened(WindowEvent arg0) {/*rien*/}
	
	/** Méthode redéfinie de KeyListener
	 * Gère la navigation
	 * L'affichage est réalisé dans la méthode keyReleased
	 * @param e L'objet KeyEvent intercepté
	 */
	public void keyPressed(KeyEvent e)
	{
		char ch = ' ';
		ch = e.getKeyChar();

		/* PERKINS */
		if(jcbPerkins.isSelected())
		{
			switch (ch)
			{
				case 's':
					tabPoint[3]=true;
					nbTouches++;
				break;
				case 'd':
					tabPoint[4]=true;
					nbTouches++;
				break;
				case 'f':
					tabPoint[5]=true;
					nbTouches++;
				break;
				case 'l':
					tabPoint[0]=true;
					nbTouches++;
				break;
				case 'k':
					tabPoint[1]=true;
				break;
				case 'j':
					tabPoint[2]=true;
					nbTouches++;
				break;	
			}
		}
	}
	
	/** Méthode redéfinie de KeyListener
	 * Gère la saisie en mode Perkins
	 * Réalise l'affichage du caractère braille dans le JTextPane resultat
	 * @param e L'objet KeyEvent intercepté
	 */
	public void keyReleased(KeyEvent e) 
	{
		char ch = e.getKeyChar();
		if(jcbPerkins.isSelected())
		{			
			if(ch == 's' || ch == 'd' || ch=='f' || ch=='j' || ch=='k' || ch == 'l')
			{
				nbTouches--;
				if(nbTouches <= 0)
				{
					int res =0;
					for (int i=0;i<tabPoint.length;i++)
					{
						if (tabPoint[i])
						{
							res = res + (int)Math.pow(2, tabPoint.length - i -1);
							tabPoint[i] = false;
						}
					}
					//System.err.println("code " + res);
					if (res>0)
					{
						try
						{
							resultat.getDocument().insertString(resultat.getCaretPosition(), ptPerkins.get(Integer.toString(res)), resultat.getInputAttributes());
						}
						catch (Exception exp){exp.printStackTrace();}
					}
				}
			}
		}
	}

	/** Méthode redéfinie de KeyListener
	 * ne fait rien
	 * @param e Le KeyEvent
	 */
	public void keyTyped(KeyEvent e){/*do nothing*/}	
	
	/** Méthode redéfinie de DocumentListener
	 * Affiche un message si le test area est modifié
	 * @see javax.swing.event.DocumentListener#insertUpdate(javax.swing.event.DocumentEvent)
	 */
	public void insertUpdate(DocumentEvent de) 
	{
		message.setText("Fichier modifié");
		modif=true;
		positionCurseur=  resultat.getCaretPosition();
		verifBtEdit();
	}
	/**
	 * Ne fait rien de plus
	 * @see javax.swing.event.DocumentListener#changedUpdate(javax.swing.event.DocumentEvent)
	 */
	public void changedUpdate(DocumentEvent de){/*rien*/}
	
	/** Méthode redéfinie de ComponentListener
	 * Ne fait rien
	 * @param arg0 Le ComponentEvent
	 */
	public void componentHidden(ComponentEvent arg0){/*do nothing*/}
	/** Méthode redéfinie de ComponentListener
	 * Ne fait rien
	 * @param arg0 Le ComponentEvent
	 */
	public void componentMoved(ComponentEvent arg0){/*do nothing*/}
	/** Méthode redéfinie de ComponentListener
	 * Ne fait rien
	 * @param arg0 Le ComponentEvent
	 */
	public void componentShown(ComponentEvent arg0){/*do nothing*/}
	
	/** Méthode redéfinie de ComponentListener
	 * Ne fait rien
	 * @param arg0 Le ComponentEvent
	 */
	public void componentResized(ComponentEvent arg0){/*do nothing*/}

	/**
	 * Change le nom du fichier en cours d'édition
     * @param nf le nouveau nom du fichier
     */
    public void setFichier(String nf)
    {
    	fichier = nf;
    	lFichier.setText("Document: "+fichier);
    }
	
}
