package ij.measure;
import ij.plugin.filter.Analyzer;
import ij.plugin.frame.Recorder;
import ij.plugin.*;
import ij.*;
import ij.gui.*;
import ij.text.*;
import java.awt.*;
import java.awt.event.*;


/** This class implements the Apply Macro command in tables.
* @author Michael Schmid
*/
public class ResultsTableMacros implements Runnable, DialogListener, ActionListener, KeyListener {
    private static String NAME = "TableMacro.ijm";
    private String defaultMacro = "Sin=sin(row*0.1);\nCos=cos(row*0.1);\nSqr=Sin*Sin+Cos*Cos;";
    private GenericDialog gd;
    private ResultsTable rt, rtBackup;
    private Button runButton, resetButton, openButton, saveButton;
    private String title;
    private int runCount;
    private TextArea ta;

    public ResultsTableMacros(ResultsTable rt) {
        this.rt = rt;
        title = rt!=null?rt.getTitle():null;
        Thread thread = new Thread(this, "ResultTableMacros");
        thread.start();
    }

    private void showDialog() {
        if (rt==null)
            rt = Analyzer.getResultsTable();
        if (rt==null || rt.size()==0) {
            IJ.error("Results Table required");
            return;
        }
        String[] temp = rt.getHeadingsAsVariableNames();
        String[] variableNames = new String[temp.length+2];
        variableNames[0] = "Insert...";
        variableNames[1] = "row";
        for (int i=2; i<variableNames.length; i++)
            variableNames[i] = temp[i-2];
        String dialogTitle = "Apply Macro to "+(title!=null?"\""+title+"\"":"Table");
        Frame parent = title!=null?WindowManager.getFrame(title):null;
        if (parent!=null)
            gd = new GenericDialog(dialogTitle, parent);
        else
            gd = new GenericDialog(dialogTitle);
        gd.setInsets(5, 5, 0);
        gd.addTextAreas(getMacro(), null, 12, 48);
        ta = gd.getTextArea1();
        ta.addKeyListener(this);

        Panel panel = new Panel();
        if (IJ.isMacOSX())
            panel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
        runButton = new Button("Run");
        runButton.addActionListener(this);
        panel.add(runButton);
        resetButton = new Button("Reset");
        resetButton.addActionListener(this);
        panel.add(resetButton);
        openButton = new Button("Open");
        openButton.addActionListener(this);
        panel.add(openButton);
        saveButton = new Button("Save");
        saveButton.addActionListener(this);
        panel.add(saveButton);
        gd.addPanel(panel);
        gd.addToSameRow();
        gd.addChoice("", variableNames, variableNames[0]);


        gd.addHelp("<html><body><h1>Macro Equations for Results Tables</h1><ul>"+
                "<li>The macro, or a selection, is applied to each row of the table."+
                "<li>A new variable starting with an Uppercase character creates<br>a new column."+
                "<li>A new variable starting with a lowercase character is temporary."+
                "<li>The variable <b>row</b> (row index) is pre-defined.\n"+
                "<li>String operations are supported for the 'Label' column only (if<br>enabled"+
                "with Analyze&gt;Set Measurements&gt;Display Label)."+              
                "<li>Click \"<b>Run</b>\", or press "+(IJ.isMacOSX()?"cmd":"ctrl") + "-r, to apply the macro code to the table."+
                "<li>Select a line and press "+(IJ.isMacOSX()?"cmd":"ctrl") + "-r to apply a line of macro code."+
                "<li>Click \"<b>Reset</b>\" to revert to the original version of the table."+
                "<li>The code is saved at <b>macros/TableMacro.ijm</b>, and the<br>\"Apply Macro\" command is recorded, when you click \"<b>OK</b>\"."+
                "<li>All <b>Table.</b> macro functions (such as Table.size) refer to the<br>current (frontmost) table unless the table name is given."+
                "</ul></body></html>");

        gd.addDialogListener(this);
        gd.showDialog();
        if (gd.wasCanceled()) {  // dialog cancelled?
            rt = rtBackup;
            updateDisplay();
            return;
        }
        if (runCount==0)
            applyMacro();
        if (Recorder.record) {
            String macro = getMacroCode();
            macro = macro.replaceAll("\n", " ");
            if (Recorder.scriptMode()) {
                Recorder.recordCall("title = \""+title+"\";");
                Recorder.recordCall("frame = WindowManager.getFrame(title);");
                Recorder.recordCall("rt = frame.getResultsTable();");
                Recorder.recordCall("rt.applyMacro(\""+macro+"\");");
                Recorder.recordCall("rt.show(title);");
            } else {
                if (title.equals("Results"))
                    Recorder.record("Table.applyMacro", macro);
                else
                    Recorder.record("Table.applyMacro", macro, title);
            }
        }
        IJ.saveString(ta.getText(), IJ.getDir("macros")+NAME);
     }

    private void applyMacro() {
        String code = getMacroCode();
        rt.applyMacro(code);
        updateDisplay();
        runCount++;
    }

    private String getMacroCode() {
        int start = ta.getSelectionStart();
        int end = ta.getSelectionEnd();
        return start==end?ta.getText():ta.getSelectedText();
    }
    
    public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
        final String variableName = gd.getNextChoice();
        if (e!=null && (e.getSource() instanceof Choice) && !variableName.equals("Insert...")) {
            final int pos = ta.getCaretPosition();
            ((Choice)e.getSource()).select(0);
            final TextArea textArea = ta;
            new Thread(new Runnable() {
                    public void run() {
                        IJ.wait(100);
                        textArea.insert(variableName, pos);
                        textArea.setCaretPosition(pos+variableName.length());
                        textArea.requestFocus();
                    }}).start();
        }
        return true;
    }

    public void actionPerformed(ActionEvent e) {
        Object source = e.getSource();
        if (source==runButton) {
            applyMacro();
        } else if (source==resetButton) {
            rt = (ResultsTable)rtBackup.clone();
            updateDisplay();
        } else if (source==openButton) {
            String macro = IJ.openAsString(null);
            if (macro==null)
                return;
            if (macro.startsWith("Error: ")) {
                IJ.error(macro);
                return;
            } else
                ta.setText(macro);
        } else if (source==saveButton) {
            ta.selectAll();
            String macro = ta.getText();
            ta.select(0, 0);
            IJ.saveString(macro, null);
        }

    }

    public void keyPressed(KeyEvent e) {
        int flags = e.getModifiers();
        boolean control = (flags & KeyEvent.CTRL_MASK) != 0;
        boolean meta = (flags & KeyEvent.META_MASK) != 0;
        int keyCode = e.getKeyCode();
        if (keyCode==KeyEvent.VK_R && (control||meta))
            applyMacro();
        if (keyCode==KeyEvent.VK_Z && (control||meta)) {
            rt = (ResultsTable)rtBackup.clone();
            updateDisplay();
        }
    }

    private void updateDisplay() {
        if (title!=null)
            rt.show(title);
    }

    public void keyReleased(KeyEvent e) {
    }

    public void keyTyped(KeyEvent e) {
    }

    private String getMacro() {
        String macro = IJ.openAsString(IJ.getDir("macros")+NAME);
        if (macro==null || macro.startsWith("Error:"))
            return defaultMacro;
        else {
            macro = macro.replaceAll("rowNumber", "row");
            macro = macro.replaceAll("rowIndex", "row");
            return macro;
        }
    }

    public void run() {
        rtBackup = (ResultsTable)rt.clone();
        showDialog();
    }

}