package com.jayantkrish.jklol.lisp;

import java.util.List;
import java.util.Map;

import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.jayantkrish.jklol.util.IndexedList;

public class Environment {

  private final Map<Integer, Object> bindings;
  private final Environment parentEnvironment;

  public Environment(Map<Integer, Object> bindings, Environment parentEnvironment) {
    this.bindings = Preconditions.checkNotNull(bindings);
    this.parentEnvironment = parentEnvironment;
  }

  public static Environment empty() {
    return Environment.extend(null);
  }

  public static Environment extend(Environment parentEnvironment) {
    return new Environment(Maps.<Integer, Object> newHashMap(), parentEnvironment);
  }

  public void bindName(String name, Object value, IndexedList<String> symbolTable) {
    if (!symbolTable.contains(name)) {
      symbolTable.add(name);
    }
    int index = symbolTable.getIndex(name);
    bindings.put(index, value);
  }

  public void bindName(int nameIndex, Object value) {
    bindings.put(nameIndex, value);
  }

  public void bindNames(List<String> names, List<Object> values,
      IndexedList<String> symbolTable) {
    Preconditions.checkArgument(names.size() == values.size());
    for (int i = 0; i < names.size(); i++) {
      bindName(names.get(i), values.get(i), symbolTable);
    }
  }

  public void bindNames(int[] nameIndexes, List<Object> values) {
    Preconditions.checkArgument(nameIndexes.length == values.size());
    for (int i = 0; i < nameIndexes.length; i++) {
      bindings.put(nameIndexes[i], values.get(i));
    }
  }

  public Object getValue(String name, IndexedList<String> symbolTable) {
    int index = symbolTable.getIndex(name);
    return getValue(index, symbolTable);
  }

  public Object getValue(int symbolIndex, IndexedList<String> symbolTable) {
    if (bindings.containsKey(symbolIndex)) {
      return bindings.get(symbolIndex);
    }

    Preconditions.checkState(parentEnvironment != null,
        "Tried accessing unbound variable: %s", symbolTable.get(symbolIndex));
    Object value = parentEnvironment.getValue(symbolIndex, symbolTable);
    return value;
  }
}

@@ -521,24 +521,465 @@
 ronment;%0A  %7D
+%0A  %0A  /**%0A   * Copy constructor. If %7B@code env%7D has a parent environment,%0A   * that environment (and its parents) are recursively copied%0A   * as well.%0A   * %0A   * @param env%0A   */%0A  public Environment(Environment env) %7B%0A    this.bindings = Maps.newHashMap(env.bindings);%0A    if (env.parentEnvironment != null) %7B%0A      this.parentEnvironment = new Environment(env.parentEnvironment);%0A    %7D else %7B%0A      this.parentEnvironment = null;%0A    %7D%0A  %7D
 %0A%0A  public s
@@ -2063,32 +2063,568 @@
 t(i));%0A    %7D%0A  %7D
+%0A  %0A  /**%0A   * Removes a binding from this environment. Returns the value%0A   * bound to that name if the name was bound, otherwise returns%0A   * %7B@code null%7D. %0A   * %0A   * @param name%0A   * @param symbolTable%0A   * @return%0A   */%0A  public Object unbindName(String name, IndexedList%3CString%3E symbolTable) %7B%0A    if (!symbolTable.contains(name)) %7B%0A      return null;%0A    %7D%0A    int index = symbolTable.getIndex(name);%0A    return bindings.remove(index);%0A  %7D%0A  %0A  public Object unbindName(int nameIndex) %7B%0A    return bindings.remove(nameIndex);%0A  %7D
 %0A%0A  public Objec

copy constructor and unbind