/* File: AutoTester.java
 * Contains: A class for simulating Kqml input
 * Author: Jeff Dalton <J.Dalton@ed.ac.uk>
 * Created: April 1998
 * Updated: Sun Jul  8 19:25:48 2001 by Jeff Dalton
 * Copyright: (c) 1998, 2000, 2001, AIAI, University of Edinburgh
 */

package ix.ileed;

import java.util.*;
import java.io.*;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JMenu;
import javax.swing.JMenuItem;

import ix.util.*;
import ix.util.lisp.*;

/**
 * A class that can be used to replay recorded messages for testing
 * and demonstration.
 */

public class AutoTester {

    protected Vector allTestRunners = new Vector();

    public static int defaultDelay = 5000; // milliseconds between messages
    public int initialDelay = 10*1000; 	   // extra delay before 1st message
    public int maxDelay = 10000; 	   // milliseconds between messages
    public int minDelay = 0;		   // milliseconds between messages

    public boolean askUserBeforeSend = false;

    BasicIleed ileed;

    public AutoTester(BasicIleed ileed) {
	this.ileed = ileed;
    }

    public void processParameters() {

	defaultDelay = Parameters.getInt("autotester-delay", defaultDelay);

	askUserBeforeSend = Parameters.getBoolean
	    ("autotester-ask-user-before-send");
    }

    public class TestRunner extends Thread {

	public LList testMessages;

	public TestRunner(LList testMessages) {
	    this.testMessages = testMessages;
	    AutoTester.this.allTestRunners.addElement(this);
	}

	public void run() {

	    // Here's where we send the messages that constitute a test.
	    Debug.noteln("AutoTester will send", testMessages);

	    Debug.noteln("First sleeping for " + initialDelay/1000
			 + " seconds");

	    try { Thread.sleep(initialDelay); }
	    catch (InterruptedException e) {}

	    long time = 0;

	    for (LList ms = testMessages; ms != Lisp.NIL; ms = ms.cdr()) {

		String event = (String)ms.car();

		Debug.noteln("\n---------- AutoTester sending", event);

		if (askUserBeforeSend) {
		    Util.askLine("--- press return to send");
		}
		else {
		    long delay = defaultDelay;
		    if (delay > maxDelay) {
			Debug.noteln("Delay would be " + delay / 1000.0
				     + " seconds.");
			Debug.noteln("Using " + maxDelay / 1000.0
				     + " seconds instead.");
			delay = maxDelay;
		    }
		    if (delay > 0) {
			try { Thread.sleep(delay); }
			catch (InterruptedException e) {}
		    }
		}
		
		// Here's where we actually "send" it.
		AutoTester.this.ileed.interpreter.interpret(event);

	    }

	    // The test is complete from our POV.
	    Debug.noteln("\n\n- - - - - - - - - - - - - - - - - - - -\n");
	    Debug.noteln("Autotester sent", testMessages);
	    Debug.noteln("");

	}

    } // end of class TestRunner

    /*
     * Reset
     */

    public void stopAll() {
	for (Enumeration e = allTestRunners.elements();
	     e.hasMoreElements();) {
	    TestRunner t = (TestRunner)e.nextElement();
	    if (t.isAlive())
		t.stop();
	}
    }

    /*
     * Test table, etc.
     */

    protected List testNames = new LinkedList();

    protected Hashtable testTable = new Hashtable() {
	public Object put(Object key, Object value) {
	    testNames.add(key); // to remember the order in which added
	    return super.put(key, value);
	}
    };

    {
	testTable.put("Default messages", "");
	testTable.put("Generate messages", "");
	testTable.put("Generate message permutation", "");
	// testTable.put("Ask for a file name","");
    }

    public void addTestMenuItems(JMenu testMenu) {
	JMenu menu = new JMenu("Auto-test");
	testMenu.add(menu);
	
	for (Iterator i = testNames.iterator(); i.hasNext();) {
	    String name = (String)i.next();
	    JMenuItem item = new JMenuItem(name);
	    item.addActionListener(testMenuActionListener);
	    menu.add(item);
	}
    }

    ActionListener testMenuActionListener = new ActionListener() {
	public void actionPerformed(ActionEvent e) {
	    String command = e.getActionCommand();
	    Debug.noteln("AutoTester action:", command);
	    runTest(command);
	}
    };

    public void runTest(final String testName) {

	// New thread because we're called from a MainFrame menu-slection
	// and hence in an AWT thread.
	// /\/: Not clear this is needed, since we start another
	// new thread anyway for each test run.
	new Thread() {
	    public void run() {
		Debug.noteln("Test requested", testName);
		String filename = (String)testTable.get(testName);
		Debug.assert(filename != null);

		LList messages = Lisp.NIL;
		if (testName == "Default messages") {
		    messages =
			Seq.toLList(Seq.elements(defaultMessageSequence));
		}
		else if (testName == "Generate messages") {
		    permuteSuccessors = false;
		    messages = generateTestMessageList();
		}
		else if (testName == "Generate message permutation") {
		    permuteSuccessors = true;
		    messages = generateTestMessageList();
		}
		else {
		    Debug.warn("Unknown test: " + testName);
		}

		// Send some messages.
		new TestRunner(messages).start();

	    }

	}.start();

    }

    /* 
     * Order of messages:
     *
     *  Start --> S1M1a --> S1M2a --> S1M3a --> S1M4a
     *              |         |         |         |
     *              V         V         V         V
     *            S1M1b --> S1M2b --> S1M3b     S1M4b
     *                                  |         ^
     *                                  V         |
     *                                  I1a ----> I1b
     *
     * John Levine, 9th June 2000.
     */

    static String[][] messageOrderSpecs = new String[][] {
        {"Start", "S1M1a"},
	{"S1M1a", "S1M1b", "S1M2a"},
	{"S1M1b", "S1M2b"},
        {"S1M2a", "S1M2b", "S1M3a"},
	{"S1M2b", "S1M3b"},
	{"S1M3a", "S1M3b", "S1M4a"},
        {"S1M3b", "I1a"},
        {"S1M4a", "S1M4b"},
        {"I1a", "I1b"},
        {"I1b", "S1M4b"},
	// Not sure the rest is right.
        {"S1M4b", "S2M1a"},
        {"S2M1a", "S2M1b", "S2M2a"},
        {"S2M1b", "S2M2b"},
        {"S2M2a", "S2M2b"},
        {"S2M2b"}};

    static String[] defaultMessageSequence = new String[] {
	"S1M1a", "S1M1b", "S1M2a", "S1M2b", "S1M3a", "S1M3b",
	"I1a", "I1b",
	"S1M4a", "S1M4b", "S2M1a", "S2M1b", "S2M2a", "S2M2b"
    };

    static Hashtable messageSuccessors = new Hashtable();

    public static LList defaultMessageList =
        Seq.toLList(Seq.elements(defaultMessageSequence));

    // /\/: Having this global variable is a terrible way to do it.
    static boolean permuteSuccessors = false;

    static {
	for (int i = 0; i < messageOrderSpecs.length; i++) {
	    LList spec = Seq.toLList(Seq.elements(messageOrderSpecs[i]));
	    String from = (String)spec.car();
	    LList to = spec.cdr();
	    messageSuccessors.put(from, to);
	}
    }

    static LList generateTestMessageList() {
	LListCollector result = new LListCollector();
	walkSampleTestMessages("Start", result.elementPusher()); 
	LList messages = result.contents();
	Debug.assert(((String)messages.car()).equals("Start"));
	return messages.cdr();	// minus "Start"
    }

    static void walkSampleTestMessages(String at, Function1 f) {
	// Think "topological sort".
	Hashtable marks = new Hashtable();
	messageWalk(at, f, marks);
    }

    static void messageWalk(String at, Function1 f, Hashtable marks) {
	if (marks.get(at) == null) {
	    marks.put(at, "start");
	    LList next = (LList)messageSuccessors.get(at);
	    // Can put them in some other order at this point.
	    if (permuteSuccessors)
		next = next.permute();
	    for (; next != Lisp.NIL; next = next.cdr()) {
		messageWalk((String)next.car(), f, marks);
	    }
	    f.funcall(at);
	    // Could change mark to "finish" for debugging
	}
    }

}
