package jscheme;

/** Environments allow you to look up the value of a variable, given
 * its name.  Keep a list of variables and values, and a pointer to
 * the parent environment.  If a variable list ends in a symbol rather
 * than null, it means that symbol is bound to the remainder of the
 * values list.
 * @author Peter Norvig, peter@norvig.com http://www.norvig.com
 * Copyright 1998 Peter Norvig, see http://www.norvig.com/license.html */

public class Environment extends SchemeUtils {
    public Object vars;
    public Object vals;
    public Environment parent;

    /** A constructor to extend an environment with var/val pairs. */
    public Environment(Object vars, Object vals, Environment parent) {
        this.vars = vars;
        this.vals = vals;
        this.parent = parent;
        if (!numberArgsOK(vars, vals))
            warn("wrong number of arguments: expected " + vars +
                        " got " + vals);
    }

    /** Construct an empty environment: no bindings. **/
    public Environment() {}

    /** Find the value of a symbol, in this environment or a parent. */
    public Object lookup (String symbol) {
        Object varList = vars, valList = vals;
        // See if the symbol is bound locally
        while (varList != null) {
            if (first(varList) == symbol) {
                return first(valList);
            } else if (varList == symbol) {
                return valList;
            } else {
                varList = rest(varList);
                valList = rest(valList);
            }
        }
        // If not, try to look for the parent
        if (parent != null) return parent.lookup(symbol);
        else return error("Unbound variable: " + symbol);
    }

    /** Add a new variable,value pair to this environment. */
     public Object define(Object var, Object val) {
         vars = cons(var, vars);
         vals = cons(val, vals);
         if (val instanceof Procedure
             && ((Procedure)val).name.equals("anonymous procedure"))
             ((Procedure)val).name = var.toString();
         return var;
     }

  /** Set the value of an existing variable **/
  public Object set(Object var, Object val) {
    if (!(var instanceof String))
      return error("Attempt to set a non-symbol: " + stringify(var));
    String symbol = (String) var;
    Object varList = vars, valList = vals;
    // See if the symbol is bound locally
    while (varList != null) {
      if (first(varList) == symbol) {
        return setFirst(valList, val);
      } else if (rest(varList) == symbol) {
        return setRest(valList, val);
      } else {
        varList = rest(varList);
        valList = rest(valList);
      }
    }
    // If not, try to look for the parent
    if (parent != null) return parent.set(symbol, val);
    else return error("Unbound variable: " + symbol);
  }

    public Environment defPrim(String name, int id, int minArgs) {
      define(name, new Primitive(id, minArgs, minArgs));
      return this;
    }

    public Environment defPrim(String name, int id, int minArgs, int maxArgs) {
      define(name, new Primitive(id, minArgs, maxArgs));
      return this;
    }

    /** See if there is an appropriate number of vals for these vars. **/
    boolean numberArgsOK(Object vars, Object vals) {
        return ((vars == null && vals == null)
                || (vars instanceof String)
                || (vars instanceof Pair && vals instanceof Pair
                    && numberArgsOK(((Pair)vars).rest, ((Pair)vals).rest)));
    }

}

