/* Author: Jeff Dalton <J.Dalton@ed.ac.uk>
 * Updated: Tue Dec  4 23:44:06 2001 by Jeff Dalton
 * Copyright: (c) 2001, AIAI, University of Edinburgh
 */

package ix.icore.process;

import java.util.*;

import ix.icore.domain.Schema;
import ix.icore.process.Variable;
import ix.util.*;
import ix.util.lisp.*;
import ix.util.match.*;

/**
 * A node that represents an action in a process.  This class
 * supports expansion into subactions and the propagation of status
 * changes, thus acting as a simple process model and expansion
 * planner.
 */

public class PNode implements StatusValues {

    public LList pattern;
    public int level = 0;		// expansion level, 1 + the parent's
    public int status = STATUS_BLANK;

    public Set patternVars;

    public PNode parent = null;
    public LList children = Lisp.NIL;

    public HashMap varTable = new HashMap(); // when instantiating a schema
    // maps ?name to instance of Variable.

    public Vector preNodes  = new Vector(); // nodes directly before this
    public Vector postNodes = new Vector(); // nodes directly after this

    public Schema schema = null;

    public PNode(PNode parent, LList pattern) {

	this.pattern = pattern;
	this.parent = parent;
	if (parent != null)
	    level = parent.level + 1;

	// If this is a subnode intriduced by expansion with a schema,
	// all ?-vars in the pattern should have been replaced by values
	// or by Variables.  But if something gives us a level-0 pattern
	// that contains ?=vars, we need to convert them to Variables,
	// like this:
	if (level == 0) {
	    Map varEnv = new HashMap();
	    LList newPattern
		= (LList)SimpleMatcher.emptyEnv.instantiateTree
		            (pattern, new MakeVarIfUnbound(varEnv));
	    if (!varEnv.isEmpty()) {
		Debug.noteln("Replacing top-level pattern", pattern);
		Debug.noteln("with", newPattern);
		pattern = newPattern;
		this.pattern = newPattern;
	    }
	}

	patternVars = Variable.varsIn(pattern);


	// computeStatus();	// may be too soon from subclass POV /\/
    }

    public boolean isAncestorOf(PNode n) {
	for (PNode p = n.parent; p != null; p = p.parent) {
	    if (this == p)
		return true;
	}
	return false;
    }

    public boolean isDescendentOf(PNode n) {
	return n.isAncestorOf(this);
    }

    public Set getUnboundVars() {
	return Variable.unboundVarsIn(patternVars);
    }

    /**
     * Creates child nodes as specified by a schema and installs
     * ordering links.  The subnodes are constructed by a NodeMaker
     * so that they can be instances of a subclass of Node and to
     * allow other, related objects to be constructed at the same time.
     */
    public void expandOneLevel(Schema sourceSchema,
			       MatchEnv env,
			       final PNodeMaker nodeMaker) {

	// Instantiate the schema
	Debug.assert(schema == null);
	Function1 ifUnbound = new MakeVarIfUnbound(varTable);
	schema = sourceSchema.instantiate(env, ifUnbound);

	// Create the child nodes specified by the schema.
	// A nodeSpec is a LList (Long:number LList:pattern).
	children = schema.nodes.mapcar(new Function1() {
	    public Object funcall(Object nodeSpec) {
		LList spec = (LList)nodeSpec;
		LList childPattern = (LList)spec.elementAt(1);
		return nodeMaker.makePNode(PNode.this, childPattern);
	    }
	});

	// Add the orderings
	processOrderings(children, schema.orderings);

	// Have all the children determine their status
	allComputeStatus(children.elements());

    }

    public class MakeVarIfUnbound implements Function1 {

	protected Map nameToVarMap;

	public MakeVarIfUnbound(Map nameToVarMap) {
	    this.nameToVarMap = nameToVarMap;
	};

	public Object funcall(Object name) {
	    Variable v = (Variable)nameToVarMap.get(name);
	    if (v == null) {
		v = new Variable(name);
		v.sourceNode = PNode.this;
		// The schema will be null for Variables in top-level patterns
		v.sourceSchema = PNode.this.schema;
		nameToVarMap.put(name, v);
	    }
	    return v;
	}

    }
	
    protected void processOrderings(LList nodes, LList orderings) {
	for (Enumeration ords = orderings.elements();
	     ords.hasMoreElements();) {
	    LList ord = (LList)ords.nextElement();
	    int n1 = ((Number)ord.car()).intValue() - 1;
	    int n2 = ((Number)ord.cdr().car()).intValue() -1;
	    PNode before = (PNode)nodes.elementAt(n1);
	    PNode after = (PNode)nodes.elementAt(n2);
	    before.linkBefore(after);
	}
    }

    protected void linkBefore(PNode after) {
	Debug.noteln("      " + this + "\n  --> " + after);
	postNodes.addElement(after);
	after.preNodes.addElement(this);
	// Since after has a new preNode, its status may change.
	// after.computeStatus();
    }

    /**
     * computeStatus() is used to change the status of related nodes
     * when a node changes its status.
     */
    public void computeStatus() {
	// We assume that the current status may not be correct,
	// but only certain transitions are handled.

	// If the node is BLANK (waiting to become POSSIBLE), then:
	// if the parent is EXECUTING (or null) and all predecessors 
	// are COMPLETE, then this node should become POSSIBLE.
	if (status == STATUS_BLANK) {
	    if ((parent == null || parent.status == STATUS_EXECUTING)
		&& allHaveStatus(preNodes.elements(), STATUS_COMPLETE))
		setStatus(STATUS_POSSIBLE);
	}

	// If the node is EXECUTING, then:
	// if all children are COMPLETE, this this node should
	// become COMPLETE.
	else if (status == STATUS_EXECUTING) {
	    Debug.assert(!children.isNull(), "Executing w/o children");
	    if (allHaveStatus(children.elements(), STATUS_COMPLETE))
		setStatus(STATUS_COMPLETE);
	}
    }

    /**
     * Changes the node's status and then that of related nodes.
     */
    public void setStatus(int status) {
	// A change in a node's status may affect the parent,
	// successor siblings, or children.
	Debug.noteln("Setting status of " + this + " to "
		     + ViewColor.statusName[status]);
	this.status = status;
	if (status == STATUS_COMPLETE) {
	    if (parent != null)
		parent.computeStatus();			// may -> COMPLETE
	    allComputeStatus(postNodes.elements()); 	// may -> POSSIBLE
	}
	else if (status == STATUS_EXECUTING) {
	    allComputeStatus(children.elements()); 	// may -> POSSIBLE
	}
	else if (status == STATUS_POSSIBLE) {
	    // When a node that already had children becomes POSSIBLE,
	    // we automatically make it start EXECUTING
	    if (!children.isEmpty())
		setStatus(STATUS_EXECUTING);
	}
    }

    public static boolean allHaveStatus(Enumeration e, int status) {
	while(e.hasMoreElements()) {
	    PNode n = (PNode)e.nextElement();
	    if (n.status != status)
		return false;
	}
	return true;
    }

    public static void allComputeStatus(Enumeration e) {
	while (e.hasMoreElements()) {
	    PNode n = (PNode)e.nextElement();
	    n.computeStatus();
	}
    }

    public String toString(){
	return "#<pnode level " + level + " " + pattern + ">";
    }

}

