//
// @author Bernhard Pfahringer
//
// Bad style, because comments are still missing :-(
// but see "MyShell.README" for some info
//
import java.util.*;
import java.io.*;
import java.lang.reflect.*;

public class MyShell {

  public static final Set<Class> PRIMITIVE_CLASS = new HashSet<Class>();
  public static final Map<String,Class> PRIMITIVE_MAP = new HashMap<String,Class>();
  public static final Map<String,Constructor> PRIMITIVE_CONSTRUCTOR = new HashMap<String,Constructor>();
  public static final Map<String,Method> COMMAND_TABLE = new HashMap<String,Method>();

  static {
    try {
      Class[] wrapperClasses = new Class[] {
	  Boolean.class, Byte.class, Character.class, Integer.class, Long.class,
	  Short.class, Float.class, Double.class };
      Class[] primitiveClasses = new Class[] {
	  boolean.class, byte.class, char.class, int.class, long.class,
	  short.class, float.class, double.class };
      for(int i = 0; i < primitiveClasses.length; i++) {
	String name = primitiveClasses[i].toString();
	PRIMITIVE_MAP.put(name, primitiveClasses[i]);
	PRIMITIVE_CLASS.add(primitiveClasses[i]);
	if (!name.equals("char")) {
	  Constructor cons = wrapperClasses[i].getConstructor(new Class[]{String.class});
	  PRIMITIVE_CONSTRUCTOR.put(name, cons);
	}
      }
      for(String name: new String[]{"show","new","method","String"}) {
	Method command = MyShell.class.getMethod(name+"Command", new Class[0]);
	COMMAND_TABLE.put(name, command);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }



  public static void main(String[] args) throws Exception {

    (new MyShell()).commandLoop();
    System.exit(0);

  }


  private String _line;
  private String[] _command;
  private Map<String,Value> _environment = new HashMap<String,Value>();
  private Class[] _types;
  private Object[] _values;


  public void commandLoop() throws Exception {
    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    while ((_line = readCommandLine("JAVA> ", reader)) != null) {
      try {
	_command = _line.split(" ");
	if ("quit".equals(_command[0])) {
	  return;
	} 
	Method m = COMMAND_TABLE.get(_command[1]);
	if (m != null) {
	  m.invoke(this, new Object[0]);
	} else if (PRIMITIVE_MAP.get(_command[1]) != null) {
	  Value primitiveValue = getPrimitiveValue(_command[1], _command[2]);
	  _environment.put(_command[0], primitiveValue);
	  showValue(_command[0]);
	} else {
	  System.err.println("Unknown command: " + _line);
	}
      } catch (Exception e) {
	System.err.println("Offending command: " + _line);
	e.printStackTrace();
      }
    }
  }
  

  public void showValue(String key) {
    System.out.println(key + " = " + _environment.get(key));
  }


  public String readCommandLine(String prompt, BufferedReader r) throws Exception {
    System.out.print(prompt);
    return r.readLine();
  }


  public void StringCommand() throws Exception {
    String value = _line.substring(_line.indexOf('"')+1, _line.lastIndexOf('"'));
    _environment.put(_command[0], new Value(String.class, value));
    showValue(_command[0]);
  }


  public void showCommand() throws Exception {
    if (_environment.get(_command[0]) == null) {
      System.out.println(_command[0] + " = " + " ??? unbound ");
    } else {
      showValue(_command[0]);
    }
  }


  public void newCommand() throws Exception {
    extractTypesAndValues(3);
    Class c = Class.forName(_command[2]);
    Constructor cons = null;
    try {
      cons = c.getConstructor(_types);
    } catch (NoSuchMethodException e) {
      cons = findConstructor(c.getConstructors());
    }
    Object result = cons.newInstance(_values);
    _environment.put(_command[0], new Value(c, result));
    showValue(_command[0]);
  }


  public void methodCommand() throws Exception {
    extractTypesAndValues(4);
    Class c = _environment.get(_command[2]).type;
    Method m = null;
    try {
      m = c.getMethod(_command[3], _types);
    } catch (NoSuchMethodException e) {
      m = findMethod(c.getMethods());
    }
    Object result = m.invoke(_environment.get(_command[2]).value, _values);
    Class resultType = m.getReturnType();
    if ((result != null) && (resultType.isAssignableFrom(result.getClass()))) {
      resultType = result.getClass();
    }
    _environment.put(_command[0], new Value(resultType, result));
    showValue(_command[0]);
  }


  public Constructor findConstructor(Constructor[] constructors) throws Exception {
    for(Constructor cons: constructors) {
      Class[] formalTypes = cons.getParameterTypes();
      if (typesAreCompatible(formalTypes, _types)) {
	return cons;
      }
    }
    throw new NoSuchMethodException();
  }


  public Method findMethod(Method[] methods) throws Exception {
    for(Method m: methods) {
      Class[] formalTypes = m.getParameterTypes();
      if (typesAreCompatible(formalTypes, _types)) {
	return m;
      }
    }
    throw new NoSuchMethodException();
  }


  public void extractTypesAndValues(int offset) {
    _types = new Class[_command.length-offset];
    _values = new Object[_command.length-offset];

    for(int i = offset; i < _command.length; i++) {
      _types[i-offset] = _environment.get(_command[i]).type;
      _values[i-offset] = _environment.get(_command[i]).value;
    }
  }
    

  public boolean typesAreCompatible(Class[] superClass, Class[] subClass) {
    if (subClass.length != superClass.length) {
      return false;
    }
    for(int i = 0; i < superClass.length; i++) {
      if (!superClass[i].isAssignableFrom(subClass[i])) {
	return false;
      }
    }
    return true;
  }


  public Value getPrimitiveValue(String typeName, String valueString) throws Exception {
    if ("char".equals(typeName)) {
      return new Value(char.class, new Character(valueString.charAt(0)));
    }      
    Constructor cons = PRIMITIVE_CONSTRUCTOR.get(typeName);
    return new Value(PRIMITIVE_MAP.get(typeName), cons.newInstance(new Object[]{valueString}));
  }


  public class Value {
    Class type;
    Object value;

    public Value(Class type, Object value) {
      this.type = type;
      this.value = value;
    }

    public String toString() {
      if (PRIMITIVE_CLASS.contains(type)) {
	return "Primitive " + type + " " + value;
      } else if (type == void.class) {
	return "void";
      }
      return String.valueOf(value);
    }
  }
}

