import java.io.*;
import java.util.ArrayList;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;

// Handles a single client connection with scheme

class JavaScheme {
    private final int scmport = 60030;
    private final String fnprefix = "resources/";
    private final char argc = '\\';
    private final char compoundc = 202;
    private final char endargsc = 203;
    private final char stringc = 204;
    private final char endc = 205;
    private final char symbolc = 206;
    private final boolean debug = true;
    private Socket socket;
    private InputStream input;
    private OutputStream output;
    private HashMap defines;

    JavaScheme() throws java.net.UnknownHostException, java.io.IOException {
	socket = new Socket("localhost", scmport);
	input = socket.getInputStream();
	output = socket.getOutputStream();
	defines = new HashMap();
    }

    public void CommandLoop(PrintStream out) throws java.io.IOException {
	while (true) {
	    Object result = ScmEvaluate(ScmInterpret(out, 0));
	    String resstr =
		(result == null ? (new String()) : result.toString());
	    resstr += "\n";
	    output.write(resstr.getBytes());
	}
    }

    private String ScmInterpret(PrintStream out, int level)
	throws java.io.IOException {
	if (debug)
	    System.out.println("ScmInterpret: " + level);
	int c = input.read();
	switch ((char) c) {
	case stringc:
	    return ScmReadString(out);
	case symbolc:
	    return ScmReadSymbol(out);
	case compoundc:
	    return ScmCompound(out, level);
	default:
	    System.out.println("  Unknown: " + String.valueOf(c));
	    return "";
	}
    }

    // for string, returns without string prefix
    // for symbol, returns value
    private Object ScmEvaluate(String arg) {
	if (arg == null || arg.length() == 0)
	    return "";
	if (arg.charAt(0) == stringc)
	    return arg.substring(1);
	else if (arg.charAt(0) == symbolc) {
	    return defines.get(arg.substring(1));
	} else {
	    System.out.println("Error: Evaluated neither string nor symbol: " + arg);
	    return arg;
	}
    }

    private String ScmReadString(PrintStream out) throws java.io.IOException {
	if (debug)
	    System.out.println("  ScmReadString");

	String result = "";
	char c;
	while ((c = (char) input.read()) != endc) {
	    result += Character.toString((char) c);
	}

	return Character.toString(stringc) + result;
    }

    private String ScmReadSymbol(PrintStream out) throws java.io.IOException {
	if (debug)
	    System.out.println("  ScmReadSymbol");
	return Character.toString(symbolc) + ScmReadString(out).substring(1);
    }

    private String ScmCompound(PrintStream out, int level)
	throws java.io.IOException {
	if (debug)
	    System.out.println("   ScmCompound");
	char c;
	ArrayList args = new ArrayList();

	// Read arguments
	while ((c = (char) input.read()) == argc)
	    args.add(ScmInterpret(out, level + 1));

	if (c != endargsc)
	    System.out.println("Not ENDARGSC at end of ScmSubstitute args");

	// Read procedure or template
	String proc = ScmInterpret(out, level + 1);

	if (input.read() != endc) // clear known endc
	    System.out.println("Not ENDC at end of ScmCompound");

	if (proc.charAt(0) == stringc)
	    return ScmSubstitute(proc.substring(1), args, out);
	else {
	    String procname = proc.substring(1);
	    if (procname.equals("loadc"))
		return ScmLoadFile(args, out);
	    else if (procname.equals("defc"))
		return ScmDefine(args, out);
	    else if (procname.equals("getstrc"))
		return ScmGetString(args, out);
	    else if (procname.equals("dispc"))
		return ScmDisplay(args, out);
	    else if (procname.equals("getc"))
		return ScmGetElement(args, out);
	    else if (procname.equals("eachc"))
		return ScmEachElement(args, out, level);
	    else {
		System.out.println("Error: unknown function");
		return null;
	    }
	}
    }

    private String ScmSubstitute(String tmpl, ArrayList args, PrintStream out)
	throws java.io.IOException {
	if (debug)
	    System.out.println("  ScmSubstitute");
	// Substitute into template
	String[] parts = tmpl.split("\\\\");
	
	String result = parts[0];
	for (int i = 1; i < parts.length; i++) {
	    result += ScmEvaluate((String) args.get((int) parts[i].charAt(0))).
		toString();
	    result += parts[i];
	}

	return Character.toString(stringc) + result;
    }

    private String ScmLoadFile(ArrayList args, PrintStream out) throws java.io.IOException {
	if (debug)
	    System.out.println("  ScmLoadFile");

	// Grab the filename
	String filename = ScmEvaluate((String) args.get(0)).toString();
	FileInputStream file = new FileInputStream(fnprefix + filename);
	byte data[] = new byte[file.available()];
	file.read(data);
	return Character.toString(stringc) + new String(data);
    }

    private String ScmDefine(ArrayList args, PrintStream out) throws java.io.IOException {
	if (debug)
	    System.out.println("  ScmDefine");
	// Grab the thing to be bound
	String value = ScmEvaluate((String) args.get(1)).toString();

	// Grab the name to bind to
	String name = ((String) args.get(0)).substring(1);

	// Bind the two together
	defines.put(name, value);

	return null;
    }

    private String ScmGetString(ArrayList args, PrintStream out) throws java.io.IOException {
	if (debug)
	    System.out.println("  ScmGetString");

	String line;

	BufferedReader inread =
	    new BufferedReader(new InputStreamReader(System.in));
	line = inread.readLine();
	return Character.toString(stringc) + line.trim();
    }

    private String ScmDisplay(ArrayList args, PrintStream out)
	throws java.io.IOException {
	for (int i = 0; i < args.size(); i++)
	    out.print(ScmEvaluate((String) args.get(i)).toString());

	return null;
    }

    private String ScmGetElement(ArrayList args, PrintStream out)
	throws java.io.IOException {
	// The first is to be looked up in the defines
	Object curr = defines.get(((String) args.get(0)).substring(1));
	int i = 0;

	// The rest are traced recursively
	try {
	    for (i = 1; i < args.size(); i++)
		curr = 
		    curr.getClass().getField((String) args.get(i)).get(curr);
	} catch (java.lang.NoSuchFieldException e) {
	    System.out.println("No such field: " + (String) args.get(i));
	} catch (java.lang.IllegalAccessException e) {
	    System.out.println("Cannot access field: " + (String) args.get(i));
	}

	return curr.toString();
    }

    private String ScmEachElement(ArrayList args, PrintStream out, int level)
	throws java.io.IOException {
	// Determine arraylist to loop through
	ArrayList lst = (ArrayList) ScmEvaluate((String) args.get(0));
	int i = 0, c;

	while (true) {
	    defines.put("eachcarg", lst.get(i));
	
	    // Loop until we read the endc, change on every endargc
	    ScmInterpret(out, level + 1);
	    c = input.read();
	    if (c == endargsc)
		continue;
	    else if (c == endc)
		break;
	    else {
		System.out.println("Saw bad character at end of element loop: "
				   + Character.toString((char) c));
		break;
	    }
	}

	return null;
    }

    private boolean CommandChar(int c) {
	return (c == compoundc || c == symbolc || c == argc ||
		c == endargsc || c == stringc || c == endc || c == -1);
    }
}
