/*--- formatted by Jindent 2.1, (www.c-lab.de/~jindent) ---*/

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;

/**
 * Records keyboard events in a class that can be replayed later.
 * <p>
 * Mouse events are not handled for various reasons. The justification is that
 * mouse clicks on the windows title bar don't get reported (so handling these requires
 * clumsy logic involving window events as well), and involve factors outside my control
 * such as double-click speed and screen size. The real reason is that I encountered
 * a lot of repaint problems when mouse clicks occurred that did not arise with
 * keyboard input.
 * As I strongly believe in providing keyboard short-cuts for all actions anyway this
 * ommission does not cause me any problems.
 * </p><p>
 * I have chosen to capture the results in a java program, which must be compiled, rather
 * than in a script that could be parsed and executed, becuase I find it convenient to
 * be able to manually edit the results. For example, it is trivial to add loops around
 * certain sections of the script.
 * </p><p>
 * To execute: <olist>
 * <li>java Record <className> <args> </li>
 * <li>javac Playback</li>
 * <li>java Playbakc</li>
 * </olist>
 * </p><p>
 * This class requires Java 1.3 becuase it uses the Robot and the AWTEventListener.
 * </p><p>
 * @author Graham Jenkins
 */
final public class Record implements AWTEventListener, Runnable {

    /**
     * Runs the specified swing program and captures keyboard input events in a
     * replayable java class.
     */
    public static void main(String[] args) throws Exception {
        new Record(args);
    }

    private PrintStream out;
    private String mainClassName;
    private String[] mainArgs;
    private KeyEvent lastKeyPressEvent, lastKeyReleaseEvent;
    private long lastEventTime;

    /**
     * Invokes the specified program, adding a listener for keyboard events and
     * a shutdown thread to close the output file.
     */
    private Record(String[] args) throws Exception {
        mainClassName = args[0];
        mainArgs = new String[args.length - 1];

        System.arraycopy(args, 1, mainArgs, 0, mainArgs.length);
        Runtime.getRuntime().addShutdownHook(new Thread(this));
        startRecording();
        Class.forName(mainClassName).getDeclaredMethod("main", new Class[] {
            String[].class
        }).invoke(null, new Object[] {
            mainArgs
        });
    }

    /**
     * Prints the class header to Playback.java.
     */
    private void startRecording() throws Exception {
        Toolkit.getDefaultToolkit().addAWTEventListener(this,
                AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK);

        out = new PrintStream(new FileOutputStream("Playback.java"), false);

        out.print("final public class Playback {\n");
        out.print("\tpublic static void main(String[] args) throws Exception {\n");
        out.print("\t\tClass.forName(\"" + mainClassName
                  + "\").getDeclaredMethod(\"main\", new Class[] {String[].class}).invoke(null, new Object[]{new String[] {");

        for (int i = 0; i < mainArgs.length; i++) {
            out.print("\"" + mainArgs[i] + "\"");

            if (i < mainArgs.length) {
                out.print(", ");
            }
        }

        out.print("}});\n");
        out.print("\t\tjava.awt.Robot robot = new java.awt.Robot();\n");
        out.print("\t\trobot.setAutoWaitForIdle(true);\n");
        out.print("\t\trobot.setAutoDelay(0);\n");
    }

    /**
     * Closes Playback.java.
     */
    private void stopRecording() throws Exception {
        Toolkit.getDefaultToolkit().removeAWTEventListener(this);
        out.print("\t}\n}");
        out.close();
    }

    /**
     * Copies delays longer than a set amount into the playback script
     */
    private void insertDelay() {
        long diff = System.currentTimeMillis() - lastEventTime;

        if (lastEventTime > 0 && diff > 1000) {
            out.print("\t\trobot.delay(" + diff + ");\n");
        }

        lastEventTime = System.currentTimeMillis();
    }

    /**
     * Records keyboard events to Playback,java. Consumes mouse events to
     * stop the tester from using them and messing up the script.
     * Note that keyboard events may be thrown against more than one component -
     * only the first occurrence is recorded.
     */
    public void eventDispatched(AWTEvent ev) {
        if (ev instanceof KeyEvent) {
            KeyEvent ke = (KeyEvent) ev;

            switch (ke.getID()) {

            case KeyEvent.KEY_PRESSED:
                if (ke != lastKeyPressEvent) {
                    insertDelay();
                    out.print("\t\trobot.keyPress(" + ke.getKeyCode()
                              + "); // "
                              + KeyEvent.getKeyText(ke.getKeyCode()) + "\n");

                    lastKeyPressEvent = ke;
                }

                break;

            case KeyEvent.KEY_RELEASED:
                if (ke != lastKeyReleaseEvent) {
                    insertDelay();
                    out.print("\t\trobot.keyRelease(" + ke.getKeyCode()
                              + "); // "
                              + KeyEvent.getKeyText(ke.getKeyCode()) + "\n");

                    lastKeyReleaseEvent = ke;
                }

                break;
            }
        } else if (ev instanceof MouseEvent) {
            MouseEvent me = (MouseEvent) ev;

            me.consume();

            if (me.getID() == MouseEvent.MOUSE_PRESSED) {
                System.out.println("Put the mouse down. Bad pussycat.");
                Toolkit.getDefaultToolkit().beep();
            }
        }
    }

    /**
     * Called on shutdown to close the output.
     * It would be nice to compile the output here but it is unlikely
     * that this would work reliably inside a shutdown hook.
     */
    public void run() {
        try {
            stopRecording();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

}

