/* Author: Jeff Dalton <J.Dalton@ed.ac.uk>
 * Updated: Sun Dec  2 23:41:32 2001 by Jeff Dalton
 * Copyright: (c) 2000, AIAI, University of Edinburgh
 */

package ix.icore.domain;

import java.util.*;

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

/**
 * A Schema describes a possible action in a process being modelled.
 * There can also be schemas that describe the top-level of a process.
 */

public class Schema implements SchemaSymbols, NamedObject, Cloneable {
    public String name;		// e.g. "develop coa quickly" /\/
    public Symbol type;		// e.g. process or action
    public LList pattern;	// e.g. ("develop COA" ?coa)
    public Object action;	// e.g. "devalop COA"
    public LList nodes = Lisp.NIL;
    public LList orderings = Lisp.NIL;
    public LList properties = Lisp.NIL;
    public LList effects = Lisp.NIL;
    public String comments = "";
    
    public boolean isForEachCoa; // true iff pattern contains ?coa /\/

    public Schema() {
	this.type = S_ACTION;
    }

    public Schema (String name, LList pattern) {
	this.name = name;
	this.type = S_ACTION;
	this.pattern = pattern;
	this.action = pattern.elementAt(0);
	this.isForEachCoa = (pattern.elementAt(1) == Q_COA);

	Debug.noteln("New blank schema " + Util.quote(name) + " for",
		     pattern);

    }

    public Schema(LList def) {

	// def has the form (name type pattern . slot-alist)

	this.name = def.elementAt(0).toString();
	this.type = (Symbol)def.elementAt(1);
	this.pattern = (LList)def.elementAt(2);
	this.action = pattern.elementAt(0);
	this.isForEachCoa = (pattern.elementAt(1) == Q_COA);

	Debug.noteln("New schema", Util.quote(name));

	// Init schema fields that correspond to slots in the definition.
	for (LList slots = def.drop(3); slots != Lisp.NIL; 
	     slots = slots.cdr()) {
	    Cons slot = (Cons)slots.car();
	    Symbol name = (Symbol)slot.car();
	    Object value = slot.cdr();
	    if (name == S_NODES)
		this.nodes = (LList)value;
	    else if (name == S_ORDERINGS)
		this.orderings = (LList)value;
	    else if (name == S_PROPERTIES)
		this.properties = (LList)value;
	    else if (name == S_EFFECTS)
		this.effects = (LList)value;
	    else
		Debug.warn("Illegal schema slot:" + slot);
	}
	checkConsistency();

    }

    public String getName() {
	return name;
    }

    public void checkConsistency() {
	checkNodeNumbers();
    }

    protected void checkNodeNumbers() {
	// Check that the nodes in the nodes clause are sequentally
	// numbered and start with 1.
	LList nodeSpecs;
	int place;
	for (nodeSpecs = nodes, place = 1;
	     nodeSpecs != Lisp.NIL; 
	     nodeSpecs = nodeSpecs.cdr(), place++) {
	    LList spec = (LList)nodeSpecs.car();
	    int number = ((Number)spec.car()).intValue();
	    if (number != place) {
		Debug.warn("Nodes not sequentially numbered from 1 " +
			   "in schema for " + pattern);
		break;
	    }
	}
	// Check that all orderings refer to node numbers that exist
	final int nodeMax = nodes.length();
	orderings.walkTree(new Function1() {
	    public Object funcall(Object nodeNumber) {
		int n = ((Number)nodeNumber).intValue();
		if (n < 1 || n > nodeMax)
		    Debug.warn("Ordering in schema for " + pattern +
			       "refers to nonexistent node " + nodeNumber);
		return null;
	    }
	});
    }

    public Object getPropertyObject (String name) {
	for (LList ps = properties; ps != Lisp.NIL; ps = ps.cdr()) {
	    Cons prop = (Cons)ps.car();
	    if (((String)prop.car()).equals(name)) {
		return prop.cdr().car();
	    }
	}
	return null;
    }

    public String getProperty(String name) {
	return (String)getPropertyObject(name);
    }

    public boolean isTrue(String name) {
	String value = getProperty(name);
	if (value == null || value.equals("false"))
	    return false;
	else {
	    Debug.assert(value.equals("true"), "not true, but not false");
	    return true;
	}
    }

    public Schema instantiate(MatchEnv env) {
	return instantiate(env, new Function1() {
	    public Object funcall(Object name) {
		throw new RuntimeException("Unbound variable " + name);
	    }
	});
    }

    public Schema instantiate(final MatchEnv env,
			      Function1 ifUnbound) {
	try {
	    Schema s = (Schema)clone();
	    s.pattern = (LList)env.instantiateTree(s.pattern, ifUnbound);
	    s.nodes = (LList)env.instantiateTree(s.nodes, ifUnbound);
	    s.properties = (LList)env.instantiateTree(s.properties, ifUnbound);
	    // /\/: Still have to supply ifUnbound for effects.
	    s.effects = s.effects.mapcar(new Function1() {
		public Object funcall(Object effect) {
		    return ((Effect)effect).instantiate(env);
		}
	    });
	    return s;
	}
	catch (CloneNotSupportedException e) {
	    Debug.noteException(e);
	    return null;
	}
    }

    public Schema forCoa(Long coa) {
	LList callPattern = isForEachCoa
	    ? Lisp.list(action, coa)
	    : Lisp.list(action);
	MatchEnv e = SimpleMatcher.match(pattern, callPattern);
	Debug.assert(e != null, "Can't instantiate schema for coa", pattern);
	return instantiate(e);
    }


    public Object clone() throws CloneNotSupportedException {
	return super.clone();
    }

    public LList toLList() {			// for debugging
	Object[] form = new Object[] {
	    name, type, pattern,
		Lisp.list(S_NODES, nodes),
		Lisp.list(S_ORDERINGS, orderings),
		Lisp.list(S_PROPERTIES, properties)
		};
	return Seq.toLList(Seq.elements(form));
    }

    public String toString() {
	return "Schema[" + type + " " + name + " expands " + pattern + "]";
    }

}

// Issues:
// * Property names should be either Keyword or Object, not String.
// * String property values should not be the default or have any
//   special status.
// * Should all methods that don't refer to the Schema be static?
// * Should there be so many public instance variables?
