package hc.adlrunner;

import hc.adl.ADLException;
import hc.adl.AutomatCreator;
import hc.adl.EquationParser;
import hc.adl.IFunctionCallback;
import hc.adl.IfBlock;
import hc.adl.StringTools;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Vector;

public class ADLRunner implements IFunctionCallback, Runnable {
	private static Map<String, ADLRunnerFunctions> functions = new HashMap<String, ADLRunnerFunctions>();

	private Map<String, IfBlock> program;

	private Map<String, Object> vars = new HashMap<String, Object>();

	private EquationParser parser = new EquationParser();

	private Random random = new Random();

	private String[] args;

	private Socket socket;

	private PrintStream out = System.out;

	private InputStream in = System.in;

	static {
		initFunctions();
	}

	public ADLRunner(Socket s, String[] args) throws IOException {
		this.socket = s;
		this.args = args;
		if (this.socket != null) {
			this.in = this.socket.getInputStream();
			this.out = new PrintStream(this.socket.getOutputStream());
		}
	}

	public static void main(String[] args) throws IOException {
		ADLRunner runner = new ADLRunner(null, args);
		runner.run();
	}

	private static void initFunctions() {
		assert (functions.size() == 0);
		functions.put("print", ADLRunnerFunctions.print);
		functions.put("write", ADLRunnerFunctions.write);
		functions.put("readline", ADLRunnerFunctions.readline);
		functions.put("split", ADLRunnerFunctions.split);
		functions.put("getelem", ADLRunnerFunctions.getelem);
		functions.put("newline", ADLRunnerFunctions.newline);
		functions.put("colon", ADLRunnerFunctions.colon);
		functions.put("set", ADLRunnerFunctions.set);
		functions.put("del", ADLRunnerFunctions.del);
		functions.put("get", ADLRunnerFunctions.get);
		functions.put("has", ADLRunnerFunctions.has);
		functions.put("open", ADLRunnerFunctions.open);
		functions.put("random", ADLRunnerFunctions.random);
		functions.put("time", ADLRunnerFunctions.time);
		functions.put("lower", ADLRunnerFunctions.lower);
		functions.put("upper", ADLRunnerFunctions.upper);
		functions.put("len", ADLRunnerFunctions.len);
		functions.put("getenv", ADLRunnerFunctions.getenviron);
		functions.put("tab", ADLRunnerFunctions.tab);
		functions.put("timestamp", ADLRunnerFunctions.timestamp);
		functions.put("dict", ADLRunnerFunctions.dict);
		functions.put("sql", ADLRunnerFunctions.sql);
		functions.put("list", ADLRunnerFunctions.list);
		functions.put("randelem", ADLRunnerFunctions.randelem);
		functions.put("eval", ADLRunnerFunctions.eval);
	}

	public void run() {
		if (args.length < 1) {
			System.err
					.format("Error: syntax: ADLRunner <filename> [arguments to program]");
			return;
		}
		InputStream stream;
		File file;
		try {
			file = new File(args[0]);
			stream = new FileInputStream(file);
		} catch (FileNotFoundException e) {
			System.err.format("File %s not found!\n", args[0]);
			return;
		}
		if (file.length() > 65535) {
			System.err
					.format("This is the GNU GPL 'shareware' version of ADL which supports ADL files of up to 64 kbytes only! MUHAHAHA!\n");
			return;
		}
		byte[] code = new byte[(int) file.length()];
		try {
			stream.read(code);
		} catch (IOException e) {
			System.err.format("I/O error\n");
			return;
		}
		String codeString;
		try {
			codeString = new String(code, "utf-8");
		} catch (UnsupportedEncodingException e) {
			System.err.format("Encoding shit!\n");
			return;
		}
		AutomatCreator creator = new AutomatCreator();
		try {
			program = creator.BuildAutomat(codeString);
		} catch (Exception e) {
			e.printStackTrace();
			return;
		}
		String entryPoint = "main";
		if (args.length >= 2) {
			entryPoint = args[1];
		}
		IfBlock main = program.get(entryPoint);
		if (main == null) {
			System.err
					.format("Error: can't find entry point %s!\n", entryPoint);
			return;
		}
		parser.SetCallback(this);
		parser.SetVariable("message", new MessageBoxes());
		Object[] arguments;
		if (args.length > 2) {
			int numArguments = args.length - 2;
			arguments = new Object[numArguments];
			for (int i = 2; i < args.length; i++) {
				arguments[i - 2] = args[i];
			}
		} else {
			arguments = null;
		}
		Object retval;
		parser.SetVariable("env", vars);
		try {
			retval = main.Handle(parser, arguments);
		} catch (ADLException e) {
			System.err.format("ADL expection: %s\n", e);
			return;
		}
		Integer iRetval;
		try {
			iRetval = (Integer) retval;
		} catch (NullPointerException e) {
			System.err.format("Warning: main should return something!\n");
			return;
		} catch (ClassCastException e) {
			System.err.format("Error: main must return an integer!\n");
			return;
		}
		System.err.format("Program return code: %d\n", iRetval);
		try {
			socket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@SuppressWarnings("unchecked")
	public Object FunctionCallback(String funcName, Vector<Object> vParams) {
		ADLRunnerFunctions function = functions.get(funcName);
		if (function == null) {
			IfBlock block = program.get(funcName);
			if (block == null) {
				System.err.format("Unknown builtin/function %s!\n", funcName);
				return null;
			}
			try {
				if ((vParams != null) && (vParams.size() != 0)) {
					return block.Handle(parser, vParams.toArray());
				} else {
					return block.Handle(parser, null);
				}
			} catch (ADLException e) {
				System.err
						.format("Error in sub-function %s: %s\n", funcName, e);
				return null;
			}
		}
		switch (function) {
		case print:
			out.println(vParams.get(0));
			return true;
		case readline: {
			String rl = readline(in);
			if (StringTools.isCRNL(rl)) {
				Object x = StringTools.convertToNL(null, rl.substring(1));
				return (String) x;
			}
			return rl;
		}
		case write:
			out.format("%s", vParams.get(0));
			return true;
		case newline:
			return "\n";
		case split:
			if (vParams.size() > 2) {
				return ((String) vParams.get(0)).split((String) vParams.get(1),
						(Integer) vParams.get(2));
			} else {
				return ((String) vParams.get(0)).split((String) vParams.get(1));
			}
		case colon:
			return ":";
		case list:
			return vParams.toArray();
		case randelem: {
			Random rr = new Random();
			Object[] foo = (Object[]) vParams.get(0);
			return foo[rr.nextInt(foo.length)];
		}
		case eval: {
			Map<String, Object> foo = new HashMap<String, Object>();
			try {
				foo = (Map<String, Object>) vParams.get(1);
			} catch (Throwable t) {
			}
			try {
				return parser.Parse((String) vParams.get(0), foo);
			} catch (ADLException e) {
				return e;
			}
		}
		case sql:
			return new ADLSQL();
		case getelem:
			try {
				return ((Object[]) vParams.get(0))[(Integer) vParams.get(1)];
			} catch (ArrayIndexOutOfBoundsException e) {
				return null;
			}
		case isnull:
			return vParams.get(0) == null;
		case set:
			vars.put((String) vParams.get(0), vParams.get(1));
			return true;
		case get:
			return vars.get((String) vParams.get(0));
		case del:
			vars.remove((String) vParams.get(0));
			return true;
		case has:
			return vars.containsKey((String) vParams.get(0));
		case open:
			return new ADLFile((String) vParams.get(0));
		case random:
			if (vParams.size() == 1) {
				return random.nextInt((Integer) vParams.get(0));
			}
			return random.nextInt();
		case time:
			return System.currentTimeMillis();
		case lower:
			return ((String) vParams.get(0)).toLowerCase();
		case upper:
			return ((String) vParams.get(0)).toUpperCase();
		case len:
			try {
				Object[] array = (Object[]) vParams.get(0);
				return array.length;
			} catch (ClassCastException e) {
			}
			if (vParams.get(0).getClass() == String.class) {
				return ((String) vParams.get(0)).length();
			}
		case getenviron:
			// return System.getenv((String) vParams.get(0));
		{
			String what = (String) vParams.get(0);
			if (what.equals("TCPREMOTEIP")) {
				return this.socket.getInetAddress().getHostAddress();
			} else if (what.equals("TCPREMOTEHOST")) {
				// return this.socket.getInetAddress().getCanonicalHostName();
				return this.socket.getInetAddress().getHostAddress();
			} else if (what.equals("TCPLOCALHOST")) {
				// return this.socket.getLocalAddress().getCanonicalHostName();
				return this.socket.getLocalAddress().getHostAddress();
			}
			return "";
		}
		case tab:
			return "\t";
		case timestamp:
			return DateFormat.getInstance().format(
					Calendar.getInstance().getTime());
		case dict: {
			int i;
			int l = vParams.size();
			HashMap<Object, Object> foo = new HashMap<Object, Object>();
			for (i = 0; i < l; i += 2) {
				foo.put(vParams.get(i), vParams.get(i + 1));
			}
			return foo;
		}
		}
		return false;
	}

	/**
	 * Terribly slow readline function
	 * 
	 * @param in
	 *            InputStream to read from
	 * @return omg one read line
	 */
	private static String readline(InputStream in) {
		String line = "";
		while (true) {
			int character;
			try {
				character = in.read();
			} catch (IOException e) {
				return "";
			}
			if (character == '\n') {
				break;
			}
			if (character != '\r') {
				char[] onechar = new char[1];
				onechar[0] = (char) character;
				line = line + new String(onechar);
			}
		}
		return line;
	}

}
