package net.yak.stalk;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

import net.yak.stalk.Pro.Assoc;
import net.yak.stalk.Pro.FalseObj;
import net.yak.stalk.Pro.Map;
import net.yak.stalk.Pro.NilObj;
import net.yak.stalk.Pro.Num;
import net.yak.stalk.Pro.Str;
import net.yak.stalk.Pro.TrueObj;
import net.yak.stalk.Pro.Vec;
import net.yak.stalk.Talk.Terp;

public class WebServer {
	Terp terp;
	Str VALUE;
	Str TITLE;
	Str TEXT;
	Str UPPER;
	Str LOWER;
	String[] TABLE_PARAMS;
	Pattern LINK_P;

	public WebServer(Terp terp) {
		super();
		this.terp = terp;
		this.VALUE = terp.newStr("value");
		this.TITLE = terp.newStr("title");
		this.TEXT = terp.newStr("text");
		this.UPPER = terp.newStr("upper");
		this.LOWER = terp.newStr("lower");
		this.TABLE_PARAMS = new String[] { "cellpadding", "8", "border", "1" };
		this.LINK_P = Pattern.compile("link:(/[-A-Za-z_0-9.]*):(.*)");
	}

	public static class Ht extends Talk { // For XSS Safety.
		private StringBuffer sb;

		public Ht() {
			this.sb = new StringBuffer();
		}
		public Ht(Ht that) {
			this.sb = new StringBuffer(that.sb.toString());
		}
		public Ht(String s) {
			this.sb = new StringBuffer(Talk.htmlEscape(s));
		}
		public String toString() {
			return sb.toString();
		}
		public Ht append(String s) {
			sb.append(htmlEscape(s));
			return this;
		}
		public Ht append(Ht that) {
			sb.append(that.toString());
			return this;
		}
		public Ht append(Pro that) {
			sb.append(htmlEscape(that.toString()));
			return this;
		}
		static public Ht tag(Ht appendMe, String type, String[] args,
				String body) {
			return tag(appendMe, type, args, new Ht(body));
		}
		static public Ht tag(Ht appendMe, String type, String[] args, Ht body) {
			Ht z = appendMe == null ? new Ht() : appendMe;
			assert htmlTagP.matcher(type).matches();
			z.sb.append(fmt("<%s ", type));
			if (args != null) {
				for (int i = 0; i < args.length; i += 2) {
					assert htmlTagP.matcher(args[i]).matches();
					z.sb.append(fmt("%s=\"%s\" ", args[i],
							htmlEscape(args[i + 1])));
				}
			}
			z.sb.append(fmt(">%s</%s>", body, type));
			return z;
		}
	}

	public static class InspectorVisitor extends Terp.Visitor {
		Ht ht;

		InspectorVisitor() {
			ht = new Ht();
		}

		void visitPro(Pro a) {
			ht.append("<Pro>:" + a);
		}
		void visitCls(Cls a) {
			ht.append("<Cls>:" + a);
		}
		void visitNum(Num a) {
			Ht.tag(ht, "span", new String[] { "style", "color=#555555" }, ""
					+ a.num);
		}
		void visitStr(Str a) {
			Ht.tag(ht, "span", new String[] { "style",
					"color: #338033; font-family: courier" }, a.str);
		}
		void visitFalseObj(FalseObj a) {
			ht.append("False");
		}
		void visitTrueObj(TrueObj a) {
			ht.append("True");
		}
		void visitNilObj(NilObj a) {
			ht.append("Nil");
		}
		void visitVec(Vec a) {
			Ht defs = new Ht();
			for (int i = 0; i < a.vec.size(); i++) {
				InspectorVisitor valueVisitor = new InspectorVisitor();
				valueVisitor.ht.append(" ( ");
				a.vec.get(i).visit(valueVisitor);
				Ht.tag(defs, "li", null, valueVisitor.ht.append(" )"));
			}
			Ht.tag(ht, "ol", null, defs);
		}
		void visitMap(Map a) {
			Ht defs = new Ht();
			Assoc[] assocs = a.sortedAssocs();
			for (int i = 0; i < assocs.length; i++) {
				InspectorVisitor keyVisitor = new InspectorVisitor();
				assocs[i].key.visit(keyVisitor);
				InspectorVisitor valueVisitor = new InspectorVisitor();
				assocs[i].value.visit(valueVisitor);
				Ht.tag(defs, "dt", null, keyVisitor.ht.append(" {"));
				Ht.tag(defs, "dd", null, valueVisitor.ht.append(" }"));
			}
			Ht.tag(ht, "dl", null, defs);
		}
		void visitAssoc(Assoc a) {
			ht.append("<Assoc>:" + a);
		}
		void visitUsr(Usr a) {
			ht.append("<Usr>:" + a);
		}
	}

	Ht maybeLink(String s) {
		Matcher m = LINK_P.matcher(s);
		if (m.matches()) {
			String link = m.group(1);
			String text = m.group(2);
			String[] params = new String[] { "href", link };
			return Ht.tag(null, "a", params, text);
		} else {
			return new Ht(s);
		}
	}
	Ht maybeLink(Pro x) {
		return maybeLink(maybeString(x, null));
	}
	String maybeString(Pro x, String defaultString) {
		if (x == null) {
			return defaultString == null ? x.toString() : defaultString;
		}
		Str s = x.asStr();
		if (s == null) {
			return defaultString == null ? x.toString() : defaultString;
		}
		return s.str;
	}

	public class TextMapRenderer {
		Map map;

		public TextMapRenderer(Map map) {
			this.map = map;
		}
		public Ht toHt() {
			String text = maybeString(map.map.get(VALUE), "ERROR: Missing value in map.");
			Ht page = new Ht();
			String title = maybeString(map.map.get(TITLE), "Untitled");
			Ht.tag(page, "title", null, title);
			Ht.tag(page, "h2", null, title);
			Ht.tag(page, "pre", null, text);
			Ht.tag(page, "p", null, map.toString());
			return page;
		}
	}

	public class ListMapRenderer {
		Map map;

		public ListMapRenderer(Map map) {
			this.map = map;
		}
		public Ht toHt() {
			Ht rows = new Ht();
			Vec list = (Vec) map.map.get(VALUE);
			int llen = list.vec.size();
			for (int i = 0; i < llen; i++) {
				Vec tuple = (Vec) list.vec.get(i);
				Ht cols = new Ht();
				for (int j = 0; j < tuple.vec.size(); j++) {
					Pro x = tuple.vec.get(j);
					Ht.tag(cols, "td", null, maybeLink(x));
				}
				Ht.tag(rows, "tr", null, cols);
			}
			Ht page = new Ht();
			String title = maybeString(map.map.get(TITLE), "Untitled");
			Ht.tag(page, "title", null, title);
			Ht.tag(page, "h2", null, title);
			Ht.tag(page, "table", TABLE_PARAMS, rows);
			Ht.tag(page, "p", null, map.toString());
			return page;
		}
	}

	public class MapRenderer {
		Map map;
		Terp terp;
		Str TYPE;
		Str LIST;

		public MapRenderer(Map map) {
			this.map = map;
			this.terp = map.terp();
			this.TYPE = terp.newStr("type");
			this.LIST = terp.newStr("list");
		}
		public Ht toHt() {
			try {
				Str type = map.map.get(TYPE).asStr();
				if (type.equals(LIST)) {
					return new ListMapRenderer(map).toHt();
				}
				if (type.equals(TEXT)) {
					return new TextMapRenderer(map).toHt();
				}
				return crudeFallbackToHt();

			} catch (Exception ex) {
				StackTraceElement[] arr = ex.getStackTrace();

				Ht pre = new Ht();
				pre.append(ex.toString());
				for (StackTraceElement x : arr) {
					pre.append("\n***  " + x.toString());
				}

				Ht z = new Ht();
				Ht.tag(z, "pre", null, pre);
				Ht.tag(z, "p", null, map.toString());
				return z;
			}
		}
		Ht crudeFallbackToHt() {
			InspectorVisitor vis = new InspectorVisitor();
			vis.visitMap(map);
			vis.ht.append(map.toString());
			return vis.ht;
		}
	}

	public class TerpHandler extends Talk implements HttpHandler {
		public TerpHandler() {
			super();
			System.err.println("Initialized Terp Hander.");
		}
		public void handle(HttpExchange t) throws IOException {
			System.err.println("handle().");
			String response = "?";
			String responseType = "text/plain";
			try {
				InputStream is = t.getRequestBody();
				String submissions = readAll(is);

				URI uri = t.getRequestURI();
				String path = uri.getPath();
				String query = uri.getQuery();
				try {
					Map map = terp.handleUrl(path, query, submissions);
					response = new MapRenderer(map).toHt().toString();
					responseType = "text/html";
				} catch (Exception ex) {
					ex.printStackTrace();
					response = "*** ERROR *** " + ex;
					System.err.println("[1] " + response);
				}
			} catch (Exception ex) {
				response = "*** (Outer handle) ERROR ***" + ex;
				System.err.println("[2] " + response);
			}

			Headers respHeads = t.getResponseHeaders();
			respHeads.set("Content-Type", responseType);
			t.sendResponseHeaders(200, response.length());
			OutputStream os = t.getResponseBody();
			os.write(response.getBytes());
			os.close();
		}
		public String readAll(InputStream is) throws IOException {
			StringBuffer b = new StringBuffer();
			while (true) {
				int c = is.read();
				if (c < 0)
					break;
				b.append((char) c);
			}
			return b.toString();
		}

	}

	public static class FavIconHandler extends Talk implements HttpHandler {

		public void handle(HttpExchange t) throws IOException {
			Headers respHeads = t.getResponseHeaders();
			respHeads.set("Content-Type", "text/plain");
			t.sendResponseHeaders(400, 0);
			OutputStream os = t.getResponseBody();
			os.close();
		}
	}

	public void run(String[] args) throws IOException {
		HttpServer server = HttpServer.create(new InetSocketAddress(8000), 5);
		server.createContext("/favicon.ico", new FavIconHandler());
		server.createContext("/", new TerpHandler());
		server.setExecutor(null); // creates a default executor
		server.start();
	}

	public static void main(String[] args) throws IOException {
		new WebServer(new Terp()).run(args);
	}
}
