package ij.plugin;
import ij.*;
import ij.process.*;
import ij.gui.*;
import ij.util.Tools;
import ij.io.OpenDialog;
import ij.macro.Interpreter;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.Vector;

/** This plugin implements the File/Batch/Macro and File/Batch/Virtual Stack commands. */
    public class BatchProcesser implements PlugIn, ActionListener, ItemListener, Runnable {
        private static final String MACRO_FILE_NAME = "BatchMacro.ijm";
        private static final String[] formats = {"TIFF", "8-bit TIFF", "JPEG", "GIF", "PNG", "PGM", "BMP", "FITS", "Text Image", "ZIP", "Raw"};
        private static String format = Prefs.get("batch.format", formats[0]);
        private static final String[] code = {
            "[Select from list]",
            "Add Border",
            "Convert to RGB",
            "Crop",
            "Gaussian Blur",
            "Invert",
            "Label",
            "Timestamp",
            "Max Dimension",
            "Measure",
            "Resize",
            "Scale",
            "Show File Info",
            "Unsharp Mask",
        };
        private String macro = "";
        private int testImage;
        private Button input, output, open, save, test;
        private TextField inputDir, outputDir;
        private GenericDialog gd;
        private Thread thread;
        private ImagePlus virtualStack;

    public void run(String arg) {
        if (arg.equals("stack")) {
            virtualStack = IJ.getImage();
            if (virtualStack.getStackSize()==1) {
                error("This command requires a stack or virtual stack.");
                return;
            }
        }
        String macroPath = IJ.getDirectory("macros")+MACRO_FILE_NAME;
        macro = IJ.openAsString(macroPath);
        if (macro==null || macro.startsWith("Error: ")) {
            IJ.showStatus(macro.substring(7) + ": "+macroPath);
            macro = "";
        }
        if (!showDialog()) return;
        String inputPath = null;
        if (virtualStack==null) {
            inputPath = inputDir.getText();
            if (inputPath.equals("")) {
                error("Please choose an input folder");
                return;
            }
            inputPath = addSeparator(inputPath);
            File f1 = new File(inputPath);
            if (!f1.exists() || !f1.isDirectory()) {
                error("Input does not exist or is not a folder\n \n"+inputPath);
                return;
            }
        }
        String outputPath = outputDir.getText();
        outputPath = addSeparator(outputPath);
        File f2 = new File(outputPath);
        if (!outputPath.equals("") && (!f2.exists() || !f2.isDirectory())) {
            error("Output does not exist or is not a folder\n \n"+outputPath);
            return;
        }
        if (macro.equals("")) {
            error("There is no macro code in the text area");
            return;
        }
        ImageJ ij = IJ.getInstance();
        if (ij!=null) ij.getProgressBar().setBatchMode(true);
        IJ.resetEscape();
        if (virtualStack!=null)
            processVirtualStack(outputPath);
        else
            processFolder(inputPath, outputPath);
        IJ.showProgress(1,1);
        if (virtualStack==null)
            Prefs.set("batch.input", inputDir.getText());
        Prefs.set("batch.output", outputDir.getText());
        Prefs.set("batch.format", format);
        macro = gd.getTextArea1().getText();
        if (!macro.equals(""))
            IJ.saveString(macro, IJ.getDirectory("macros")+MACRO_FILE_NAME);
    }
        
    boolean showDialog() {
        validateFormat();
        gd = new NonBlockingGenericDialog("Batch Process");
        addPanels(gd);
        gd.setInsets(15, 0, 5);
        gd.addChoice("Output Format:", formats, format);
        gd.setInsets(0, 0, 5);
        gd.addChoice("Add Macro Code:", code, code[0]);
        gd.setInsets(15, 10, 0);
        Dimension screen = IJ.getScreenSize();
        gd.addTextAreas(macro, null, screen.width<=600?10:15, 60);
        addButtons(gd);
        gd.setOKLabel("Process");
        Vector choices = gd.getChoices();
        Choice choice = (Choice)choices.elementAt(1);
        choice.addItemListener(this);
        gd.showDialog();
        format = gd.getNextChoice();
        macro = gd.getNextText();
        return !gd.wasCanceled();
    }
    
    void processVirtualStack(String outputPath) {
        ImageStack stack = virtualStack.getStack();
        int n = stack.getSize();
        int index = 0;
        for (int i=1; i<=n; i++) {
            if (IJ.escapePressed()) break;
            IJ.showProgress(i, n);
            ImageProcessor ip = stack.getProcessor(i);
            if (ip==null) return;
            ImagePlus imp = new ImagePlus("", ip);
            if (!macro.equals("")) {
                if (!runMacro("i="+(index++)+";"+macro, imp))
                    break;
            }
            if (!outputPath.equals("")) {
                if (format.equals("8-bit TIFF") || format.equals("GIF")) {
                    if (imp.getBitDepth()==24)
                        IJ.run(imp, "8-bit Color", "number=256");
                    else
                        IJ.run(imp, "8-bit", "");
                }
                IJ.saveAs(imp, format, outputPath+pad(i));
            }
            imp.close();
        }
        if (outputPath!=null && !outputPath.equals(""))
            IJ.run("Image Sequence...", "open=[" + outputPath + "]"+" use");
    }
    
    String pad(int n) {
        String str = ""+n;
        while (str.length()<5)
        str = "0" + str;
        return str;
    }

    
    void processFolder(String inputPath, String outputPath) {
        String[] list = (new File(inputPath)).list();
        int index = 0;
        for (int i=0; i<list.length; i++) {
            if (IJ.escapePressed()) break;
            String path = inputPath + list[i];
            if (IJ.debugMode) IJ.log(i+": "+path);
            if ((new File(path)).isDirectory())
                continue;
            if (list[i].startsWith(".")||list[i].endsWith(".avi")||list[i].endsWith(".AVI"))
                continue;
            IJ.showProgress(i+1, list.length);
            ImagePlus imp = IJ.openImage(path);
            if (imp==null) continue;
            if (!macro.equals("")) {
                if (!runMacro("i="+(index++)+";"+macro, imp))
                    break;
            }
            if (!outputPath.equals("")) {
                if (format.equals("8-bit TIFF") || format.equals("GIF")) {
                    if (imp.getBitDepth()==24)
                        IJ.run(imp, "8-bit Color", "number=256");
                    else
                        IJ.run(imp, "8-bit", "");
                }
                IJ.saveAs(imp, format, outputPath+list[i]);
            }
            imp.close();
        }
    }
    
    private boolean runMacro(String macro, ImagePlus imp) {
        WindowManager.setTempCurrentImage(imp);
        Interpreter interp = new Interpreter();
        try {
            interp.runBatchMacro(macro, imp);
        } catch(Throwable e) {
            interp.abortMacro();
            String msg = e.getMessage();
            if (!(e instanceof RuntimeException && msg!=null && e.getMessage().equals(Macro.MACRO_CANCELED)))
                IJ.handleException(e);
            return false;
        }
        return true;
    }
        
    String addSeparator(String path) {
        if (path.equals("")) return path;
        if (!(path.endsWith("/")||path.endsWith("\\")))
            path = path + File.separator;
        return path;
    }
    
    void validateFormat() {
        boolean validFormat = false;
        for (int i=0; i<formats.length; i++) {
            if (format.equals(formats[i])) {
                validFormat = true;
                break;
            }
        }
        if (!validFormat) format = formats[0];
    }

    void addPanels(GenericDialog gd) {
        Panel p = new Panel();
        p.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 0));
        if (virtualStack==null) {
            input = new Button("Input...");
            input.addActionListener(this);
            p.add(input);
            inputDir = new TextField(Prefs.get("batch.input", ""), 45);
            p.add(inputDir);
            gd.addPanel(p);
        }
        p = new Panel();
        p.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 0));
        output = new Button("Output...");
        output.addActionListener(this);
        p.add(output);
        outputDir = new TextField(Prefs.get("batch.output", ""), 45);
        p.add(outputDir);
        gd.addPanel(p);
    }
    
    void addButtons(GenericDialog gd) {
        Panel p = new Panel();
        p.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 0));
        test = new Button("Test");
        test.addActionListener(this);
        p.add(test);
        open = new Button("Open...");
        open.addActionListener(this);
        p.add(open);
        save = new Button("Save...");
        save.addActionListener(this);
        p.add(save);
        gd.addPanel(p);
    }

    public void itemStateChanged(ItemEvent e) {
        Choice choice = (Choice)e.getSource();
        String item = choice.getSelectedItem();
        String code = null;
        if (item.equals("Convert to RGB"))
            code = "run(\"RGB Color\");\n";
        else if (item.equals("Measure"))
            code = "run(\"Measure\");\n";
        else if (item.equals("Resize"))
            code = "run(\"Size...\", \"width=512 height=512 interpolation=Bicubic\");\n";
        else if (item.equals("Scale"))
            code = "scale=1.5;\nw=getWidth*scale; h=getHeight*scale;\nrun(\"Size...\", \"width=w height=h interpolation=Bilinear\");\n";
        else if (item.equals("Label"))
            code = "setFont(\"SansSerif\", 18, \"antialiased\");\nsetColor(\"red\");\ndrawString(\"Hello\", 20, 30);\n";
        else if (item.equals("Timestamp"))
            code = openMacroFromJar("TimeStamp.ijm");
        else if (item.equals("Crop"))
            code = "makeRectangle(getWidth/4, getHeight/4, getWidth/2, getHeight/2);\nrun(\"Crop\");\n";
        else if (item.equals("Add Border"))
            code = "border=25;\nw=getWidth+border*2; h=getHeight+border*2;\nrun(\"Canvas Size...\", \"width=w height=h position=Center zero\");\n";
        else if (item.equals("Invert"))
            code = "run(\"Invert\");\n";
        else if (item.equals("Gaussian Blur"))
            code = "run(\"Gaussian Blur...\", \"sigma=2\");\n";
        else if (item.equals("Unsharp Mask"))
            code = "run(\"Unsharp Mask...\", \"radius=1 mask=0.60\");\n";
        else if (item.equals("Show File Info"))
            code = "path=File.directory+File.name;\ndate=File.dateLastModified(path);\nsize=File.length(path);\nprint(i+\", \"+getTitle+\", \"+date+\", \"+size);\n";
        else if (item.equals("Max Dimension"))
            code = "max=2048;\nw=getWidth; h=getHeight;\nsize=maxOf(w,h);\nif (size>max) {\n  scale = max/size;\n  w*=scale; h*=scale;\n  run(\"Size...\", \"width=w height=h interpolation=Bicubic average\");\n}";
        if (code!=null) {
            TextArea ta = gd.getTextArea1();
            ta.insert(code, ta.getCaretPosition());
            if (IJ.isMacOSX()) ta.requestFocus();
        }
    }

    String openMacroFromJar(String name) {
        ImageJ ij = IJ.getInstance();
        Class c = ij!=null?ij.getClass():(new ImageStack()).getClass();
        String macro = null;
        try {
            InputStream is = c .getResourceAsStream("/macros/"+name);
            if (is==null) return null;
            InputStreamReader isr = new InputStreamReader(is);
            StringBuffer sb = new StringBuffer();
            char [] b = new char [8192];
            int n;
            while ((n = isr.read(b)) > 0)
                sb.append(b,0, n);
            macro = sb.toString();
        }
        catch (IOException e) {
            return null;
        }
        return macro;
    }

    public void actionPerformed(ActionEvent e) {
        Object source = e.getSource();
        if (source==input) {
            String path = IJ.getDirectory("Input Folder");
            if (path==null) return;
            inputDir.setText(path);
            if (IJ.isMacOSX())
                {gd.setVisible(false); gd.setVisible(true);}
        } else if (source==output) {
            String path = IJ.getDirectory("Output Folder");
            if (path==null) return;
            outputDir.setText(path);
            if (IJ.isMacOSX())
                {gd.setVisible(false); gd.setVisible(true);}
        } else if (source==test) {
            thread = new Thread(this, "BatchTest"); 
            thread.setPriority(Math.max(thread.getPriority()-2, Thread.MIN_PRIORITY));
            thread.start();
        } else if (source==open)
            open();
        else if (source==save)
            save();
    }
    
    void open() {
        String text = IJ.openAsString("");
        if (text==null) return;
        if (text.startsWith("Error: "))
            error(text.substring(7));
        else {
            if (text.length()>30000)
                error("File is too large");
            else
                gd.getTextArea1().setText(text);
        }
    }
    
    void save() {
        macro = gd.getTextArea1().getText();
        if (!macro.equals(""))
            IJ.saveString(macro, "");
    }

    void error(String msg) {
        IJ.error("Batch Processer", msg);
    }
    
    public void run() {
        TextArea ta = gd.getTextArea1();
        //ta.selectAll();
        String macro = ta.getText();
        if (macro.equals("")) {
            error("There is no macro code in the text area");
            return;
        }
        ImagePlus imp = null;
        if (virtualStack!=null)
            imp = getVirtualStackImage();
        else
            imp = getFolderImage();
        if (imp==null) return;
        runMacro("i=0;"+macro, imp);
        Point loc = new Point(10, 30);
        if (testImage!=0) {
            ImagePlus imp2 = WindowManager.getImage(testImage);
            if (imp2!=null) {
                ImageWindow win = imp2.getWindow();
                if (win!=null) loc = win.getLocation();
                imp2.changes=false;
                imp2.close();
            }
        }
        imp.show();
        ImageWindow iw = imp.getWindow();
        if (iw!=null) iw.setLocation(loc);
        testImage = imp.getID();
    }
    
    ImagePlus getVirtualStackImage() {
        ImagePlus imp = virtualStack.createImagePlus();
        imp.setProcessor("", virtualStack.getProcessor().duplicate());
        return imp;
    }

    ImagePlus getFolderImage() {
        String inputPath = inputDir.getText();
        inputPath = addSeparator(inputPath);
        File f1 = new File(inputPath);
        if (!f1.exists() || !f1.isDirectory()) {
            error("Input does not exist or is not a folder\n \n"+inputPath);
            return null;
        }
        String[] list = (new File(inputPath)).list();
        String name = list[0];
        if (name.startsWith(".")&&list.length>1) name = list[1];
        String path = inputPath + name;
        setDirAndName(path);
        return IJ.openImage(path);
    }
    
    void setDirAndName(String path) {
        File f = new File(path);
        OpenDialog.setLastDirectory(f.getParent()+File.separator);
        OpenDialog.setLastName(f.getName());
    }


}