/* Author: Jeff Dalton <J.Dalton@ed.ac.uk>
 * Updated: Fri Apr 13 00:58:51 2001 by Jeff Dalton
 * Copyright: (c) 2000, AIAI, University of Edinburgh
 */

package ix.examples;

import java.util.*;

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


/**
 * A simple example using an I-X framework.  This example shows how
 * to extend PicoIX for a particular application type, in this case
 * discrete event simulation.
 */

public class PicoISim extends PicoIX {

    static Symbol SYM(String name) {		// Abbreviation
	return Symbol.intern(name);
    }


    /**
     * An IX_System for discrete event simulation.
     */
    static class IX_Simulator extends IX_System {

	long simTime = -1;	// so it notes a change to 0

	boolean stopWhenIdle = true;

	IX_Simulator() {
	    controller =  new SimController(this);
	    model = new ModelManager(this);
	    addIssueHandlers(makeBuiltinIssueHandlers());
	    addSimEventHandlers(makeBuiltinSimEventHandlers());
	}

	IX_Simulator(Controller c, ModelManager m) {
	    super(c != null ? c : new SimController(),
		  m);
	    addIssueHandlers(makeBuiltinIssueHandlers());
	    addSimEventHandlers(makeBuiltinSimEventHandlers());
	}

	public void addSimEventHandlers(Object[] issueHandlers) {
	    ((SimController)controller).installSimEventHandlers(issueHandlers);
	}

	public void setStopWhenIdle(boolean newValue) {
	    stopWhenIdle = newValue;
	}

	public void schedule(SimEvent e) {
	    // This method hides the existence of "schedule" issues.
	    newEvent(new Issue(SYM("schedule"), e));
	}

	public void start() {
	    setSimTime(0);
	    super.start();
	}

	void setSimTime(long t) {
	    if (t > simTime) {		// there's no going back
		Debug.noteln("\n- - - - - time now " + t + " - - - - -\n");
		simTime = t;
	    }
	}

	void postSimEvent(SimEvent e) {
	    ((SimController)controller).addSimEvent(e);
	}

	Object[] makeBuiltinIssueHandlers() {
	    return new Object[] {

		new IssueHandler(SYM("schedule")) {
		    void handleIssue(Issue i) {
			SimEvent e = (SimEvent)i.object;
			((IX_Simulator)system).postSimEvent(e);
		    }
		}

	    };

	}

	Object[] makeBuiltinSimEventHandlers() {
	    return new Object[] {

	    };

	}

    }


    /**
     * The controller for a simulator handles an agenda of simulated
     * events as well as the agenda of issues that the simulator addresses
     * as an agent.
     */
    static class SimController extends Controller {

	LListCollector simEvents = new LListCollector();
	Hashtable simHandlerTable = new Hashtable();

	SimController() {}

	SimController(IX_System system) {
	    super(system);
	}

	void addSimEvent(SimEvent e) {
	    Debug.noteln("Scheduling", e);
	    simEvents.insertElement(e, EARLIER_EST_P);
	}

	static final Predicate2 EARLIER_EST_P =
	    new Predicate2() {
		public boolean trueOf(Object a, Object b) {
		    return ((SimEvent)a).est < ((SimEvent)b).est;
		}
	    };

	void mainLoop() {
	    // Do this forever.
	    while (true) {
		// Convert any external events to Issues.
		while (q.hasMessage()) {
		    addEventIssue(q);
		}
		// If there's an issue, handle it;
		// else if we can execute a SimEvent, do that;
		// otherwise wait for an external event
		// unless we're supposed to stop when idle.
		if (!issues.isEmpty()) {
		    handleIssue(selectIssue());
		}
		else if (!simEvents.isEmpty()) {
		    boolean didSomething = simulateIfPossible();
		    // If we get here and didn't do anything, we'll keep
		    // being unable to do anything until some external
		    // event makes a change that lets us progress,
		    // so we may as well wait for an event rather than
		    // keep looping.
		    if (!didSomething) {
			q.waitForMessage();
		    }
		}
		else {
		    whenIdle();
		}
	    }
	}

	void whenIdle() {
	    // Subclasses may want to make it do something more interesting.
	    if (((IX_Simulator)system).stopWhenIdle) {
		Debug.noteln("Simulator stopping because nothing to do");
		system.stop();
		Debug.assert(false, "continued after thread stop()");
	    }
	    else {
		q.waitForMessage();
	    }
	}

	boolean simulateIfPossible() {
	    Debug.noteln("Agenda", simEvents.contents());
	    for(LList el = simEvents.contents();
		el != Lisp.NIL; el = el.cdr()) {
		SimEvent e = (SimEvent)el.car();
		if (canSimulate(e)) {
		    return simulateIfPossible(e);
		}
	    }
	    return false;
	}

	boolean simulateIfPossible(SimEvent e) {
	    // This always simulates, but subclasses may have different ideas.
	    simEvents.deleteElement(e);
	    setSimTime(e.est);
	    simulate(e);
	    return true;
	}

	boolean canSimulate(SimEvent e) {
	    SimEventHandler h = findSimHandler(e);
	    // return h.isAble(e);
	    if (h.isAble(e)) {
		return true;
	    }
	    else {
		Debug.noteln("Can't execute " + e + " at this time");
		return false;
	    }
	}

	void simulate(SimEvent e) {
	    SimEventHandler h = findSimHandler(e);
	    h.handleIssue(e);
	}

	void installSimEventHandlers(Object[] issueHandlers) {
	    installIssueHandlers(issueHandlers, simHandlerTable);
	}

	SimEventHandler findSimHandler(Issue i) {
	    SimEventHandler h = (SimEventHandler)simHandlerTable.get(i.verb);
	    Debug.assert(h != null, "no sim handler for", i.verb);
	    return h;
	}

	long getSimTime() {
	    return ((IX_Simulator)system).simTime;
	}

	// It's the controller that sets the sim time because different
	// controllers might handle simulated time in different ways,
	// for instance by using scaled real time.
	void setSimTime(long t) {
	    ((IX_Simulator)system).setSimTime(t);
	}

    }


    /**
     * Issues that represent scheduled events in the simulation.
     */
    static class SimEvent extends Issue {

	long est = 0;

	SimEvent(Object verb, Object object, long when) {
	    super(verb, object);
	    this.est = when;
	}

	void execute(IX_Simulator simulator) {
	    // The default does nothing.
	    // The simulator is passed as an argument to allow the
	    // method to access other parts of the system.
	}

	public String toString() {
	    return "SimEvent[" + Lisp.printToString(verb) + " "
		               + Lisp.printToString(object) + " at "
		               + est + "]";
	}

    }


    /**
     * Handlers for SimEvents.
     */
    static abstract class SimEventHandler extends IssueHandler {

	SimEventHandler(Object verb) {
	    super(verb);
	}

	boolean isAble(SimEvent e) {
	    return true;
	}

	// Handy methods for use in SimEvent handlers.

	long getSimTime() {
	    return ((IX_Simulator)system).simTime;
	}

	void postSimEvent(SimEvent e) {
	    ((IX_Simulator)system).postSimEvent(e);
	}

    }


}


// Issues:
// * Division of labour between Issue and IssueHandler methods.
// * Could the handler just be a method of the Issue?
// * Should isAble(SimEvent s) be a handler method, or should
//   we have isAble(SimEventHandler h) be a method of SimEvent?
//   (The handler is involved because it knows that the system is.)
// * Should SimEvent implement Runnable?  Don't want to imply that
//   all will work unproblematically if they're run in a separate
//   thread.
// * Straighten out what should be public, protected, etc.
// * A simulator could be oriented around objects rather than events.
//   How would that fit into the framework?
