package ij.plugin;
import java.awt.*;
import java.io.*;
import java.util.*;
import ij.*;
import ij.gui.*;
import ij.io.*;
import ij.util.*;
import ij.plugin.frame.Editor;
import ij.text.TextWindow;
import java.awt.event.KeyEvent;

/** Compiles and runs plugins using the javac compiler. */
public class Compiler implements PlugIn, FilenameFilter {

    private static com.sun.tools.javac.Main javac;
    private static ByteArrayOutputStream output;
    private static String dir, name;
    private static Editor errors;
    private static boolean generateDebuggingInfo;

    public void run(String arg) {
        if (arg.equals("edit"))
            edit();
        else
            compileAndRun(arg);
     }
     
    void edit() {
        if (open("", "Open macro or plugin")) {
            Editor ed = (Editor)IJ.runPlugIn("ij.plugin.frame.Editor", "");
            if (ed!=null) ed.open(dir, name);
        }
    }
    
    void compileAndRun(String path) {
        if (IJ.altKeyDown()) {
            IJ.setKeyUp(KeyEvent.VK_ALT);
            GenericDialog gd = new GenericDialog("Compile and Run");
            gd.addCheckbox("Generate Debugging Info (javac -g)", generateDebuggingInfo);
            gd.showDialog();
            if (gd.wasCanceled()) return;
            generateDebuggingInfo = gd.getNextBoolean();
        }
        if (!open(path, "Compile and Run Plugin..."))
            return;
        if (name.endsWith(".class")) {
            runPlugin(name.substring(0, name.length()-1));
            return;
        }
        if (!isJavac()) return;
        if (compile(dir+name))
            runPlugin(name);
    }
     
    boolean isJavac() {
        try {
            if (javac==null) {
                output = new ByteArrayOutputStream(4096);
                javac=new com.sun.tools.javac.Main();
            }
        } catch (NoClassDefFoundError e) {
            IJ.error("This JVM does not include the javac compiler.\n"
                    +"Javac is included with the Windows and Linux\n"
                    +"versions of ImageJ that are bundled with Java.");
            return false;
        }
        return true;
    }

    boolean compile(String path) {
        IJ.showStatus("compiling: "+path);
        output.reset();
        String classpath = getClassPath(path);
        Vector v = new Vector();
        if (generateDebuggingInfo)
            v.addElement("-g");
        if (IJ.isJava16()) {
            // needed so plugin will run on Java 1.5
            v.addElement("-source");
            v.addElement("1.5");
            v.addElement("-target");
            v.addElement("1.5");
        }
        if (IJ.isJava15())
            v.addElement("-Xlint:unchecked");
        v.addElement("-deprecation");
        v.addElement("-classpath");
        v.addElement(classpath);
        v.addElement(path);
        String[] arguments = new String[v.size()];
        v.copyInto((String[])arguments);
        if (IJ.debugMode) {
            String str = "javac";
            for (int i=0; i<arguments.length; i++)
                str += " "+arguments[i];
            IJ.log(str);
        }
        boolean compiled = javac.compile(arguments, new PrintWriter(output))==0;
        String s = output.toString();
        boolean errors = (!compiled || areErrors(s));
        if (errors)
            showErrors(s);
        else
            IJ.showStatus("done");
        return compiled;
     }
     
     // Returns a string containing the Java classpath, 
     // the path to the directory containing the plugin, 
     // and paths to any .jar files in the plugins folder.
     String getClassPath(String path) {
        long start = System.currentTimeMillis();
        StringBuffer sb = new StringBuffer();
        sb.append(System.getProperty("java.class.path"));
        File f = new File(path);
        if (f!=null)  // add directory containing file to classpath
            sb.append(File.pathSeparator + f.getParent());
        String pluginsDir = Menus.getPlugInsPath();
        if (pluginsDir!=null)
            addJars(pluginsDir, sb);
        return sb.toString();
     }
     
    // Adds .jar files in plugins folder, and subfolders, to the classpath
    void addJars(String path, StringBuffer sb) {
        String[] list = null;
        File f = new File(path);
        if (f.exists() && f.isDirectory())
            list = f.list();
        if (list==null) return;
        if (!path.endsWith(File.separator))
            path += File.separator;
        for (int i=0; i<list.length; i++) {
            File f2 = new File(path+list[i]);
            if (f2.isDirectory())
                addJars(path+list[i], sb);
            else if (list[i].endsWith(".jar")&& list[i].indexOf("_")==-1) {
                sb.append(File.pathSeparator+path+list[i]);
                if (IJ.debugMode) IJ.log("javac: "+path+list[i]);
            }
        }
    }

    boolean areErrors(String s) {
        boolean errors = s!=null && s.length()>0;
        //if(errors&&s.startsWith("Note:com.sun.tools.javac")&&s.indexOf("error")==-1)
        //  errors = false;
        return errors;
    }
    
    void showErrors(String s) {
        if (errors==null || !errors.isVisible()) {
            errors = (Editor)IJ.runPlugIn("ij.plugin.frame.Editor", "");
            errors.setFont(new Font("Monospaced", Font.PLAIN, 12));
        }
        if (errors!=null)
            errors.display("Errors", s);
        IJ.showStatus("done (errors)");
    }

     // open the .java source file
     boolean open(String path, String msg) {
        boolean okay;
        String fileName, directory;
        if (path.equals("")) {
            if (dir==null)
                dir = Prefs.getHomeDir();
            OpenDialog od = new OpenDialog(msg, dir, name);
            directory = od.getDirectory();
            fileName = od.getFileName();
            okay = fileName!=null;
            String lcName = okay?fileName.toLowerCase(Locale.US):null;
            if (okay) {
                if (msg.startsWith("Compile")) {
                    if (!(lcName.endsWith(".java")||lcName.endsWith(".class"))) {
                        IJ.error("File name must end with \".java\" or \".class\".");
                        okay = false;
                    }
                } else if (!(lcName.endsWith(".java")||lcName.endsWith(".txt")||lcName.endsWith(".ijm"))) {
                    IJ.error("File name must end with \".java\" or \".txt\".");
                    okay = false;
                }
            }
        } else {
            int i = path.lastIndexOf('/');
            if (i==-1)
                i = path.lastIndexOf('\\');
            if (i>0) {
                directory = path.substring(0, i+1);
                fileName = path.substring(i+1);
            } else {
                directory = "";
                fileName = path;
            }
            okay = true;
        }
        if (okay) {
            name = fileName;
            dir = directory;
            Editor.setDefaultDirectory(dir);
        }
        return okay;
    }

    // only show files with names ending in ".java"
    // doesn't work with Windows
    public boolean accept(File dir, String name) {
        return name.endsWith(".java")||name.endsWith(".macro")||name.endsWith(".txt");
    }
    
    // run the plugin using a new class loader
    void runPlugin(String name) {
        name = name.substring(0,name.length()-5); // remove ".java"
        new PlugInExecuter(name);
    }
    
}


class PlugInExecuter implements Runnable {

    private String plugin;
    private Thread thread;

    /** Create a new object that runs the specified plugin
        in a separate thread. */
    PlugInExecuter(String plugin) {
        this.plugin = plugin;
        thread = new Thread(this, plugin);
        thread.setPriority(Math.max(thread.getPriority()-2, Thread.MIN_PRIORITY));
        thread.start();
    }

    public void run() {
        try {
            ImageJ ij = IJ.getInstance();
            IJ.resetEscape();
            if (ij!=null) ij.runUserPlugIn(plugin, plugin, "", true);
        } catch(Throwable e) {
            IJ.showStatus("");
            IJ.showProgress(1.0);
            ImagePlus imp = WindowManager.getCurrentImage();
            if (imp!=null) imp.unlock();
            String msg = e.getMessage();
            if (e instanceof RuntimeException && msg!=null && e.getMessage().equals(Macro.MACRO_CANCELED))
                return;
            CharArrayWriter caw = new CharArrayWriter();
            PrintWriter pw = new PrintWriter(caw);
            e.printStackTrace(pw);
            String s = caw.toString();
            if (IJ.isMacintosh())
                s = Tools.fixNewLines(s);
            new TextWindow("Exception", s, 350, 250);
        }
    }

}