/*
Jaxe - Editeur XML en Java

Copyright (C) 2006 Observatoire de Paris-Meudon

Ce programme est un logiciel libre ; vous pouvez le redistribuer et/ou le modifier conformément aux dispositions de la Licence Publique Générale GNU, telle que publiée par la Free Software Foundation ; version 2 de la licence, ou encore (à votre choix) toute version ultérieure.

Ce programme est distribué dans l'espoir qu'il sera utile, mais SANS AUCUNE GARANTIE ; sans même la garantie implicite de COMMERCIALISATION ou D'ADAPTATION A UN OBJET PARTICULIER. Pour plus de détail, voir la Licence Publique Générale GNU .

Vous devez avoir reçu un exemplaire de la Licence Publique Générale GNU en même temps que ce programme ; si ce n'est pas le cas, écrivez à la Free Software Foundation Inc., 675 Mass Ave, Cambridge, MA 02139, Etats-Unis.
*/

package pluginsjaxe;

import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import java.net.*;

import javax.imageio.*;
import javax.imageio.metadata.*;
import javax.swing.*;
import javax.swing.border.BevelBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import jaxe.JaxeDocument;
import jaxe.JaxeResourceBundle;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


// compilation : javac -source 1.5 -target 1.5 -encoding ISO-8859-1 -classpath .:Jaxe.jar pluginsjaxe/DialogueEquaTeX.java
/**
 * Dialogue pour JEEquaTeX.
 */
public class DialogueEquaTeX extends JDialog implements ActionListener, DocumentListener {

    private String serveur = null;
    private static String cheminTex2im = "/usr/local/bin/tex2im";
    private static String[] envPathMac = {"/bin","/usr/bin","/usr/local/bin","/usr/local/teTeX/bin/powerpc-apple-darwin-current","/sw/bin"};

    JFrame jframe;
    boolean valide = false;
    ImageIcon iconeq;
    JLabel labeleq;
    JTextArea zoneTexte;
    String texteEquation;
    JPanel epane;
    JaxeDocument doc;
    String nomImage;
    JTextField labelfield = null;
    String valeurLabel;

    public DialogueEquaTeX(JaxeDocument doc, String serveur, String texteEquation, String nomImage) {
        this(doc, serveur, texteEquation, nomImage, null, null);
    }
    
    public DialogueEquaTeX(JaxeDocument doc, String serveur, String texteEquation, String nomImage,
            String nomlabel, String valeurLabel) {
        super(doc.jframe, JaxeResourceBundle.getRB().getString("equation.Equation"), true);
        this.doc = doc;
        this.serveur = serveur;
        jframe = doc.jframe;
        this.texteEquation = texteEquation;
        this.nomImage = nomImage;
        this.valeurLabel = valeurLabel;
        
        JPanel cpane = new JPanel(new BorderLayout());
        setContentPane(cpane);
        epane = new JPanel(new BorderLayout());
        iconeq = new ImageIcon();
        labeleq = new JLabel(iconeq);
        epane.add(labeleq, BorderLayout.CENTER);
        zoneTexte = new JTextArea(texteEquation, 2, 80);
        zoneTexte.setLineWrap(true);
        zoneTexte.setWrapStyleWord(true);
        zoneTexte.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
        zoneTexte.getDocument().addDocumentListener(this);
        epane.add(zoneTexte, BorderLayout.SOUTH);
        cpane.add(epane, BorderLayout.CENTER);
        
        JPanel southpane = new JPanel(new GridLayout((nomlabel==null)?2:3, 1));
        
        JPanel apane = new JPanel(new FlowLayout(FlowLayout.CENTER));
        JButton boutonApercu = new JButton("Aperçu");
        boutonApercu.addActionListener(this);
        boutonApercu.setActionCommand("Apercu");
        apane.add(boutonApercu);
        southpane.add(apane);
        
        if (nomlabel != null) {
            JPanel lpane = new JPanel(new FlowLayout(FlowLayout.LEFT));
            lpane.add(new JLabel(nomlabel));
            labelfield = new JTextField(valeurLabel, 30);
            lpane.add(labelfield);
            southpane.add(lpane);
        }
        
        JPanel bpane = new JPanel(new FlowLayout(FlowLayout.RIGHT));
        JButton boutonAnnuler = new JButton(JaxeResourceBundle.getRB().getString("bouton.Annuler"));
        boutonAnnuler.addActionListener(this);
        boutonAnnuler.setActionCommand("Annuler");
        bpane.add(boutonAnnuler);
        JButton boutonOK = new JButton(JaxeResourceBundle.getRB().getString("bouton.OK"));
        boutonOK.addActionListener(this);
        boutonOK.setActionCommand("OK");
        bpane.add(boutonOK);
        southpane.add(bpane);
        
        cpane.add(southpane, BorderLayout.SOUTH);
        
        getRootPane().setDefaultButton(boutonOK);
        setSize(new Dimension(500, 300));
        zoneTexte.requestFocus();
        addWindowListener(new WindowAdapter() {
            public void windowActivated(WindowEvent we) {
                javax.swing.SwingUtilities.invokeLater( new Runnable() {
                    public void run() {
                        zoneTexte.requestFocus();
                    }
                } );
            }
        });
        if (jframe != null) {
            Rectangle r = jframe.getBounds();
            setLocation(r.x + r.width/4, r.y + r.height/4);
        } else {
            Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
            setLocation((screen.width - getSize().width)/3,(screen.height - getSize().height)/3);
        }
        
        if (texteEquation != null)
            majAffichage();
    }

    public boolean afficher() {
        setVisible(true);
        return(valide);
    }

    public String getTexte() {
        return(texteEquation);
    }
    
    public String getImage() {
        return(nomImage);
    }
    
    public String getLabel() {
        return(valeurLabel);
    }
    
    public void actionPerformed(ActionEvent e) {
        String cmd = e.getActionCommand();
        if ("OK".equals(cmd))
            actionOK();
        else if ("Annuler".equals(cmd)) {
            valide = false;
            setVisible(false);
        } else if ("Apercu".equals(cmd))
            majAffichage();
    }
    
    protected void actionOK() {
        valide = true;
        nomImage = creerImage(this, serveur, texteEquation, nomImage, doc);
        if (labelfield != null)
            valeurLabel = labelfield.getText();
        setVisible(false);
    }
    
    protected void changementTexte() {
        if (zoneTexte.getText().indexOf('\n') != -1)
            actionOK();
        else {
            texteEquation = zoneTexte.getText();
            //majAffichage();
        }
    }
    
    public void insertUpdate(DocumentEvent e) {
        changementTexte();
    }
    
    public void removeUpdate(DocumentEvent e) {
        changementTexte();
    }
    
    public void changedUpdate(DocumentEvent e) {
        changementTexte();
    }
    
    public static Image obtenirImageLocal(String texteEquation) throws IOException {
        String[] dangereux = {"include", "def", "command", "loop", "repeat", "open", "toks",
            "output", "input", "catcode", "name", "^^", "every", "errhelp", 
            "errorstopmode", "scrollmode", "nonstopmode", "batchmode", "read", 
            "write", "csname", "newhelp", "uppercase", "lowercase", "relax", 
            "aftergroup", "afterassignment", "expandafter", "noexpand",
            "special"};
        String textemod = texteEquation;
        for (int i=0; i<dangereux.length; i++)
            if (textemod.indexOf(dangereux[i]) != -1) {
                textemod = "\\textrm{\\color{red}Don't '" + dangereux[i] + "' me.}";
                break;
            }
        
        /*String osName = System.getProperty("os.name");
        File ftex2im = new File(cheminTex2im);
        if (osName.indexOf("Windows") == -1 && ftex2im.exists()) {
            // avec tex2im sous Mac ou UNIX
            String[] acmd;
            File tmpPNG;
            tmpPNG = File.createTempFile("Jaxe_tmp_eq_", ".png");
            tmpPNG.deleteOnExit();
            acmd = new String[6];
            acmd[0] = ftex2im.getAbsolutePath();
            acmd[1] = "-r";
            acmd[2] = "90x90";
            acmd[3] = "-o";
            acmd[4] = tmpPNG.getAbsolutePath();
            acmd[5] = textemod;
            String[] envp;
            if (osName.startsWith("Mac")) {
                envp = new String[1];
                envp[0] = "PATH=" + envPathMac[0];
                for (int i=1; i<envPathMac.length; i++)
                    envp[0] += ":" + envPathMac[i];
            } else
                envp = null;
            Process process = Runtime.getRuntime().exec(acmd, envp);
            try {
                Thread.sleep(1000);
                process.waitFor();
            } catch (InterruptedException ex) {
                System.err.println("exec : " + ex.getClass().getName() + ": " + ex.getMessage());
            }
            return(Toolkit.getDefaultToolkit().createImage(tmpPNG.getAbsolutePath()));
            
        } else {*/
            // sans tex2im, utilisation directe de latex, dvips et convert
            File tmpTEX = File.createTempFile("Jaxe_tmp_eq_", ".tex");
            tmpTEX.deleteOnExit();
            
            PrintWriter pw = new PrintWriter(new FileOutputStream(tmpTEX));
            pw.println("\\documentclass[12pt]{article}");
            pw.println("\\usepackage{color}");
            pw.println("\\usepackage[dvips]{graphicx}");
            pw.println("\\usepackage{cmbright}");
            pw.println("\\pagestyle{empty}");
            pw.println("\\pagecolor{white}");
            pw.println("\\begin{document}");
            pw.println("{\\color{black}");
            pw.println("\\begin{eqnarray*}");
            
            pw.println("\\setbox1=\\hbox{$\\displaystyle " + textemod + "$}");
            pw.println("\\newdimen\\haut\n\\newdimen\\prof");
            pw.println("\\haut=\\ht1\n\\prof=\\dp1");
            pw.println("\\ifdim\\haut>\\prof\\prof=\\haut\\else\\haut=\\prof\\fi");
            pw.println("\\advance\\haut by .5em");
            pw.println("\\color{white}\\vrule height \\haut depth \\prof width 0.1pt\\color{black}\\box1");
            
            pw.println("\\end{eqnarray*}}");
            pw.println("\\end{document}");
            pw.close();
            
            File parent = tmpTEX.getParentFile();
            exec("latex -interaction=batchmode " + tmpTEX.getAbsolutePath(), parent);
            String nomtmp = tmpTEX.getName();
            nomtmp = nomtmp.substring(0, nomtmp.indexOf('.'));
            (new File(parent, nomtmp + ".log")).delete();
            (new File(parent, nomtmp + ".aux")).delete();
            File tmpDVI = new File(parent, nomtmp + ".dvi");
            tmpDVI.deleteOnExit();
            File tmpEPS = new File(parent, nomtmp + ".eps");
            exec("dvips -o " + tmpEPS.getAbsolutePath() + " -E " + tmpDVI.getAbsolutePath(), null);
            tmpEPS.deleteOnExit();
            File tmpPNG = new File(parent, nomtmp + ".png");
            exec("convert +adjoin -antialias -density 90x90 " + tmpEPS.getAbsolutePath() + " " + tmpPNG.getAbsolutePath(), null);
            tmpPNG.deleteOnExit();
            return(Toolkit.getDefaultToolkit().createImage(tmpPNG.getAbsolutePath()));
        //}
    }
    
    private static String chercherChemin(String prog) {
        for (int i=0; i<envPathMac.length; i++)
            if (new File(envPathMac[i]+"/"+prog).exists())
                return(envPathMac[i]+"/"+prog);
        return(prog);
    }
    
    public static void exec(String cmd, File dir) throws IOException {
        String osName = System.getProperty("os.name");
        String[] envp = null;
        if (osName.startsWith("Mac")) {
            int indsp = cmd.indexOf(' ');
            String prog;
            if (indsp == -1)
                prog = cmd;
            else
                prog = cmd.substring(0, indsp);
            prog = chercherChemin(prog);
            if (indsp == -1)
                cmd = prog;
            else
                cmd = prog + cmd.substring(indsp);
            envp = new String[1];
            envp[0] = "PATH=" + envPathMac[0];
            for (int i=1; i<envPathMac.length; i++)
                envp[0] += ":" + envPathMac[i];
        }
        if (osName.indexOf("Windows") != -1)
            cmd = "cmd /c " + cmd;
        //System.out.println("exec \"" + cmd + "\"");
        //if (envp != null)
        //    System.out.println("envp[0]="+envp[0]);
        Process process = Runtime.getRuntime().exec(cmd, envp, dir);
        int r = -1;
        try {
            r = process.waitFor();
        } catch (InterruptedException ex) {
            System.err.println("exec : " + ex.getClass().getName() + ": " + ex.getMessage());
        }
        if (r != 0)
            throw new IOException("Erreur à l'exécution: " + r);
    }
    
    public static Image obtenirImageServeur(String serveur, String texteEquation) {
        URL urlimage;
        try {
            urlimage = new URL(serveur + "?" + URLEncoder.encode(texteEquation, "UTF-8"));
        } catch (MalformedURLException ex) {
            System.err.println("obtenirImageServeur: MalformedURLException: " + ex.getMessage());
            return(null);
        } catch (UnsupportedEncodingException ex) {
            System.err.println("obtenirImageServeur: UnsupportedEncodingException: " + ex.getMessage());
            return(null);
        }
        return(Toolkit.getDefaultToolkit().createImage(urlimage));
    }
    
    public static Image obtenirImage(String serveur, String texteEquation) {
        Image img;
        try {
            img = obtenirImageLocal(texteEquation);
        } catch (IOException ex) {
            System.err.println("latex en local: IOException: " + ex.getMessage());
            if (serveur != null) {
                System.err.println("essai avec le serveur...");
                img = obtenirImageServeur(serveur, texteEquation);
            } else {
                System.err.println("l'adresse du serveur n'a pas été spécifiée dans le fichier de config, on ne peut pas faire d'essai par le serveur");
                img = null;
            }
        }
        return(img);
    }
    
    protected void majAffichage() {
        Image img = obtenirImage(serveur, remplacerCommandes(texteEquation, doc));
        if (img == null)
            return;
        iconeq.setImage(img);
        labeleq.repaint();
    }
    
    // position de la commande (par ex. \abc) dans le texte
    // par exemple posCommande("\a", "\abc \a") renvoit 5
    // -1 si pas trouvé
    private static int posCommande(String cmd, String texte) {
        int ind = texte.indexOf(cmd);
        char c = ' ';
        if (ind != -1 && ind + cmd.length() < texte.length())
            c = texte.charAt(ind + cmd.length());
        while (ind != -1 && ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) {
            int ind2 = texte.substring(ind+1).indexOf(cmd);
            if (ind2 == -1)
                ind = -1;
            else
                ind = ind + 1 + ind2;
            c = ' ';
            if (ind != -1 && ind + cmd.length() < texte.length())
                c = texte.charAt(ind + cmd.length());
        }
        return(ind);
    }
    
    public static String remplacerCommandes(String texteEquation, JaxeDocument doc) {
        NodeList liste = doc.DOMdoc.getElementsByTagName("DEFCMDTEX");
        for (int i=0; i<liste.getLength(); i++) {
            Element def = (Element)liste.item(i);
            String commande = def.getAttribute("commande");
            if (commande.charAt(0) != '\\')
                commande = "\\" + commande;
            String snb = def.getAttribute("nb-params");
            int nb = 0;
            try {
                nb = Integer.parseInt(snb);
            } catch (NumberFormatException ex) {
            }
            String remplacement = def.getAttribute("remplacement");
            int ind = posCommande(commande, texteEquation);
            int posArg;
            int np;
            while (ind != -1) {
                posArg = ind + commande.length();
                np = nb;
                String tmpremp = remplacement;
                while (np > 0) {
                    int indp = tmpremp.indexOf("#" + (nb - np + 1));
                    if (indp != -1) {
                        if (texteEquation.charAt(posArg) == '{') {
                            int finpos = posArg + 1;
                            int profondeur = 0;
                            while (finpos < texteEquation.length() && (profondeur > 0 || texteEquation.charAt(finpos) != '}')) {
                                if (texteEquation.charAt(finpos) == '{')
                                    profondeur++;
                                else if (texteEquation.charAt(finpos) == '}')
                                    profondeur--;
                                finpos++;
                            }
                            //System.out.print(tmpremp);
                            if (finpos < texteEquation.length())
                                tmpremp = tmpremp.substring(0, indp) + texteEquation.substring(posArg+1, finpos) +
                                    tmpremp.substring(indp+2);
                            //System.out.println(" -> " + tmpremp);
                            posArg = finpos + 1;
                        }
                    }
                    np --;
                }
                texteEquation = texteEquation.substring(0, ind) + tmpremp + texteEquation.substring(posArg);
                //System.out.println("-->   " + texteEquation);
                ind = posCommande(commande, texteEquation);
            }
        }
        return(texteEquation);
    }
    
    public static String creerImage(Component comp, String serveur, String texteEquation, String nomImage, JaxeDocument doc) {
        // effacement de la précédente
        if (nomImage != null && !"".equals(nomImage)) {
            File fimg;
            if (doc.fsave == null)
                fimg = new File(nomImage);
            else
                fimg = new File(doc.fsave.getParent() + File.separatorChar + nomImage);
            if (fimg.exists() && fimg.isFile())
                fimg.delete();
        }
        
        // reconstruction de l'image
        Image img1 = obtenirImage(serveur, remplacerCommandes(texteEquation, doc));
        if (img1 == null)
            return(null);
        MediaTracker tracker = new MediaTracker(comp);
        tracker.addImage(img1, 0);
        try {
            tracker.waitForAll();
        } catch (InterruptedException ex) {
        }
        int w = img1.getWidth(null);
        int h = img1.getHeight(null);
        if (w == -1 || h == -1) {
            System.err.println("Erreur à l'obtention des dimensions de l'image ");
            JOptionPane.showMessageDialog(doc.jframe, "Erreur à l'obtention des dimensions de l'image ", JaxeResourceBundle.getRB().getString("erreur.Erreur"), JOptionPane.ERROR_MESSAGE);
            return(null);
        }
        Dimension dim = new Dimension(w, h);
        
        // récupération dans une BufferedImage
        BufferedImage img2 = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2 = img2.createGraphics();
        g2.setColor(Color.white);
        g2.fillRect(0, 0, dim.width, dim.height);
        g2.setColor(Color.black);
        g2.drawImage(img1, 0, 0, null);
        
        // application masque
        // IE ne gère pas les PNG avec une palette -> utilisation d'une échelle de gris avec transparence
        ColorSpace grayColorSpace = ColorSpace.getInstance(ColorSpace.CS_GRAY);
        final ColorModel cm = new ComponentColorModel(grayColorSpace, true, true, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
        final WritableRaster raster = cm.createCompatibleWritableRaster(dim.width, dim.height);
        final BufferedImage img3 = new BufferedImage(cm, raster, false, null);
        int[] rgbArray = new int[dim.width*dim.height];
        img2.getRGB(0, 0, dim.width, dim.height, rgbArray, 0, dim.width);
        for (int i=0; i<rgbArray.length; i++)
            rgbArray[i] = (255 - (rgbArray[i] & 0x000000FF)) << 24;
        img3.setRGB(0, 0, dim.width, dim.height, rgbArray, 0, dim.width);
        
        // IE met un fond gris si aucun bKGD n'est spécifié -> ajout de bKGD dans les métadonnées
        ImageWriter premierPourPNG = ImageIO.getImageWritersByFormatName("PNG").next();
        IIOMetadata metadata = premierPourPNG.getDefaultImageMetadata(new ImageTypeSpecifier(img3), null);
        Element racine = (Element)metadata.getAsTree("javax_imageio_png_1.0");
        NodeList nl = racine.getElementsByTagName("bKGD");
        if (nl.getLength() == 0) {
            Node suivant = racine.getFirstChild();
            String name = suivant.getNodeName();
            if (("IHDR".equals(name) || "PLTE".equals(name)))
                suivant = suivant.getNextSibling();
            if (suivant != null && "PLTE".equals(suivant.getNodeName()))
                suivant = suivant.getNextSibling();
            Element bKGD = new IIOMetadataNode("bKGD");
            if (suivant == null)
                racine.appendChild(bKGD);
            else
                racine.insertBefore(bKGD, suivant);
            Element bKGD_Grayscale = new IIOMetadataNode("bKGD_Grayscale");
            bKGD.appendChild(bKGD_Grayscale);
            bKGD_Grayscale.setAttribute("gray", "255");
            try {
                metadata.mergeTree("javax_imageio_png_1.0", racine);
            } catch (IIOInvalidTreeException ex) {
                System.err.println("Erreur à l'enregistrement de l'image: " + ex.getMessage());
                JOptionPane.showMessageDialog(doc.jframe, "Erreur à l'enregistrement de l'image: " + ex.getMessage(), JaxeResourceBundle.getRB().getString("erreur.Erreur"), JOptionPane.ERROR_MESSAGE);
            }
        }
        
        // enregistrement
        if (doc.fsave == null)
            return(null); // on pourrait afficher un message d'erreur
        File imgFile = null;
        String baseNom = "equation";
        String nouveauNom = null;
        String nomfxml = doc.fsave.getName();
        if (nomfxml.indexOf('.') != -1)
            nomfxml = nomfxml.substring(0, nomfxml.lastIndexOf('.'));
        String dossierEquations = "eq_tex_" + nomfxml;
        File dossier = new File(doc.fsave.getParent() + File.separator + dossierEquations);
        if (!dossier.exists())
            if (!dossier.mkdir()) {
                System.err.println("Erreur à la création du dossier des équations");
                return(null);
            }
        int i=1;
        while (imgFile == null || imgFile.exists()) {
            nouveauNom = baseNom + i + ".png";
            imgFile = new File(dossier.getPath() + File.separator + nouveauNom);
            i++;
        }
        try {
            premierPourPNG.setOutput(ImageIO.createImageOutputStream(imgFile));
            premierPourPNG.write(new IIOImage(img3, null, metadata));
        } catch (IOException ex) {
            System.err.println("IOException: " + ex.getMessage());
            JOptionPane.showMessageDialog(doc.jframe, JaxeResourceBundle.getRB().getString("erreur.Enregistrement") + ": " +
                ex.getMessage(), JaxeResourceBundle.getRB().getString("erreur.Erreur"), JOptionPane.ERROR_MESSAGE);
            return(null);
        }
        nomImage = dossierEquations + "/" + nouveauNom;
        return(nomImage);
    }
}
