import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;

import org.json.JSONObject;

import se.rupy.http.*;

public class User extends Service {
	private String host(Event event) throws Exception {
		return event.query().string("root", Root.host());
	}

	private String head(Event event) throws Exception {
		return "Head:less\r\nHost:" + event.query().string("root", host(event));
	}

	public static void redirect(Event event) throws IOException, Event {
		String referer = event.query().header("referer");
		redirect(event, referer == null ? "/" : referer, true);
	}

	public static void redirect(Event event, String path) throws IOException, Event {
		redirect(event, path, false);
	}

	public static void redirect(Event event, String path, boolean forward) throws IOException, Event {
		if(forward) {
			HashMap query = (HashMap) event.query().clone();
			event.session().put("post", query);
		}

		event.reply().header("Location", path);
		event.reply().code("302 Found");
		Output out = event.output();
		out.finish();
		out.flush();
		throw event;
	}

	public static void refill(Event event) {
		HashMap post = (HashMap) event.session().get("post");

		if(post != null) {
			Iterator it = post.keySet().iterator();

			while(it.hasNext()) {
				Object key = it.next();
				Object value = post.get(key);

				event.query().put(key, value);
			}
		}
	}

	public String path() { return "/user"; }

	private void script(Event event) throws Exception {
		Output out = event.output();
		String salt = event.session().string("salt");
		String algo = event.query().string("algo", "sha-256");

		if(algo.equals("sha-256"))
			out.println("<script src=\"sha256.js\"></script>");
		else
			out.println("<script src=\"md5.js\"></script>");
		out.println("<script>");
		out.println(" function join(e) {");
		out.println(" e = e || window.event;");
		out.println(" var unicode = e.charCode ? e.charCode : e.keyCode ? e.keyCode : 0;");
		out.println(" if(unicode == 13) {");
		out.println(" hash('join');");
		out.println(" }");
		out.println(" }");
		out.println(" function sign(e) {");
		out.println(" e = e || window.event;");
		out.println(" var unicode = e.charCode ? e.charCode : e.keyCode ? e.keyCode : 0;");
		out.println(" if(unicode == 13) {");
		out.println(" hash('sign');");
		out.println(" }");
		out.println(" }");
		out.println(" var digits = /^\\d+$/;");
		out.println(" function hash(type) {");
		out.println(" var name = document.getElementById('name');");
		out.println(" var pass = document.getElementById('pass');");
		out.println(" var salt = document.getElementById('salt');");
		out.println(" if(pass.value.length > 0) {");
		out.println(" if(type == 'join') {");
		if(algo.equals("sha-256"))
			out.println(" pass.value = CryptoJS.SHA256(pass.value + name.value.toLowerCase());");
		else
			out.println(" pass.value = md5(pass.value + name.value.toLowerCase());");
		out.println(" } else {");
		out.println(" salt.value = '" + salt + "';");
		out.println(" if(!digits.test(name.value))");
		if(algo.equals("sha-256")) {
			out.println(" pass.value = CryptoJS.SHA256(pass.value + name.value.toLowerCase());");
			out.println(" pass.value = CryptoJS.SHA256(pass.value + salt.value);");
		}
		else {
			out.println(" pass.value = md5(pass.value + name.value.toLowerCase());");
			out.println(" pass.value = md5(pass.value + salt.value);");
		}
		out.println(" }");
		out.println(" document.forms['user'].submit();");
		out.println(" }");
		out.println(" }");
		out.println("</script>");
		out.println("<style>");
		out.println(" a:link, a:hover, a:active, a:visited { color: #6699ff; font-style: italic; }");
		out.println(" div { font-family: monospace; }");
		out.println(" input { font-family: monospace; }");
		out.println("</style>");
	}

	private void print(Event event, String feedback) throws Event, Exception {
		Output out = event.output();
		String name = event.string("name");
		String mail = event.string("mail");
		String salt = event.string("salt");
		String fail = event.string("fail");
		String bare = event.query().string("bare", "");
		String host = event.query().header("host");
		String url = event.query().string("url", host);

		if(bare.length() == 0) {
			out.println("<!doctype html>");
			out.println("<html>");
			out.println("<head>");
			out.println("<meta name=\"viewport\" content=\"width=300, initial-scale=1.0, maximum-scale=1.0, user-scalable=0\">");
		}

		script(event);

		if(bare.length() == 0) {
			out.println("</head>");
			out.println("<body>");
		}

		out.println("<div><table width=\"100\">");

		if(fail != null) {
			out.println("<tr><td colspan=\"2\"><i><font color=\"#ff3300\">" + fail + "</font></i></td></tr>");
		}

		out.println("<tr>");
		out.println("<form action=\"user\" method=\"post\" name=\"user\"><input type=\"hidden\" name=\"salt\" id=\"salt\" value=\"" + salt + "\"><input type=\"hidden\" name=\"url\" value=\"" + url + "\">");
		out.println("<td><i>name</i> </td><td><input type=\"text\" style=\"width: 100px;\" name=\"name\" id=\"name\" value=\"" + name + "\"></td></tr>");
		out.println("<tr><td><i>pass</i></font> </td><td><input type=\"password\" style=\"width: 100px;\" name=\"pass\" id=\"pass\" onkeypress=\"sign(event);\"></td></tr>");
		out.println("<tr><td><font color=\"#00cc33\"><i>mail*</i></font></td><td><input type=\"text\" style=\"width: 100px;\" name=\"mail\" value=\"" + mail + "\" onkeypress=\"join(event);\"></td></tr>");
		out.println("<tr><td></td><td><a href=\"javascript:hash('sign');\">login</a> <a href=\"javascript:hash('join');\">register</a></td></tr>");
		out.println("<tr><td></td><td><font color=\"#ff9900\"><i>*optional</i></font></td></tr>");
		out.println("</form>");
		out.println("</table></div>");

		if(bare.length() == 0) {
			out.println("</body>");
			out.println("</html>");
		}
	}

	public void filter(Event event) throws Event, Exception {
		event.query().parse();

		String algo = event.query().string("algo", "sha-256");

		if(event.push()) {
			String name = event.query().string("success");
			String fail = event.query().string("fail");
			String host = event.query().header("host");
			String url = event.query().string("url", host);

			JSONObject user = (JSONObject) event.query().get("user");

			if(user != null) {
				String pass = event.string("pass");
				String salt = event.string("salt");
				String hash = Deploy.hash(Deploy.hash(user.getString("pass") + user.getString("name"), algo) + salt, algo);

				if(hash.equals(pass)) {
					user.remove("pass");
					event.output().print(user);
				}
				else {
					event.query().put("fail", "pass didn't match");
					redirect(event);
				}
			}
			else if(name.length() > 0) {
				redirect(event, "http://" + url + "?name=" + name);
			}
			else if(fail.length() > 0) {
				Output out = event.output();
				out.println("<meta http-equiv=\"refresh\" content=\"0;URL=http://" + url + "?fail=" + URLEncoder.encode(fail, "UTF-8") + "\">");
				out.finish();
				out.flush();
				throw event;
			}
			else if(event.bit("redirect")) {
				Output out = event.output();
				out.println("<meta http-equiv=\"refresh\" content=\"0;URL=http://" + url + "?name=" + name + "\">");
				out.finish();
				out.flush();
				throw event;
			}
		}
		else {
			if(event.query().method() == Query.GET) {
				refill(event);

				String salt = event.session().string("salt");

				if(salt.length() == 0) {
					event.hold();

					Async.Work work = new Async.Work(event) {
						public void send(Async.Call call) throws Exception {
							call.get("/salt", head(event));
						}

						public void read(String host, String body) throws Exception {
							event.query().put("redirect", "true");
							event.session().put("salt", body);
							event.reply().wakeup(true);
						}

						public void fail(String host, Exception e) throws Exception {
							e.printStackTrace();
							event.reply().wakeup(true);
						}
					};

					event.daemon().client().send("localhost", work, 30);
					throw event;
				}
				else {
					Output out = event.output();
					print(event, null);
					out.finish();
					out.flush();
				}
			}
			else if(event.query().method() == Query.POST) {
				final String name = event.string("name").toLowerCase();
				final String salt = event.string("salt");
				final String pass = event.string("pass");
				String host = event.string("host");

				if(name.length() < 2) {
					event.query().put("fail", "name too short (2)");
					redirect(event);
				}

				if(salt.length() > 0) {
					if(event.session() != null)
						event.session().put("salt", null);

					if(host.equals(Root.host())) {
						if(Root.Salt.salt.containsKey(salt)) {
							Root.Salt.salt.remove(salt);
						}
						else {
							event.reply().code("400 Bad Request");
							event.output().print("salt not found");
							throw event;
						}

						File file = null;

						if(name.indexOf("@") > -1) { // this doesen't work because name is used as salt!
							file = new File(Root.home() + "/node/user/mail" + Root.path(name));
						}
						else if(name.matches("[0-9]+")) {
							file = new File(Root.home() + "/node/user/id" + Root.path(Long.parseLong(name)));
						}
						else {
							file = new File(Root.home() + "/node/user/name" + Root.path(name));
						}

						if(!file.exists()) {
							event.output().print("name not found");
							throw event;
						}

						JSONObject object = new JSONObject(Root.file(file));
						String secret = object.optString("pass");
						boolean key = false;

						if(secret.length() == 0) {
							secret = object.optString("key");
							key = true;
						}

						String hash = Deploy.hash(secret + salt, algo);

						if(hash.equals(pass)) {
							object.remove("pass");
							event.output().print(object);
						}
						else if(key == false) {
							secret = object.optString("key");
							hash = Deploy.hash(secret + salt, algo);

							if(hash.equals(pass)) {
								object.remove("pass");
								event.output().print(object);
							}
							else {
								event.output().print("wrong pass");
							}
						}
						else {
							event.output().print("wrong pass");
						}

						throw event;
					}
					else {
						Async.Work work = new Async.Work(event) {
							public void send(Async.Call call) throws Exception {
								String body = "name=" + name + "&pass=" + pass + "&salt=" + salt + "&host=" + host(event);
								call.post("/user", head(event), body.getBytes());
							}

							public void read(String host, String body) throws Exception {
								try {
									JSONObject user = new JSONObject(body);
																																				System.out.println(event.session() + " " + user);
																																				event.session().put("user", user);
									event.query().put("success", user.getString("name"));
								}
								catch(Exception e) {
												e.printStackTrace();
									event.query().put("fail", body);
								}

								event.reply().wakeup(true);
							}

							public void fail(String host, Exception e) throws Exception {
								e.printStackTrace();
								event.query().put("fail", "something snapped");
								event.reply().wakeup(true);
							}
						};

						event.daemon().client().send("localhost", work, 30);
						throw event;
					}
				}
				else {
					String mail = event.string("mail").toLowerCase();

					/* The distributed name service I'm building
								 * is going to use 5-bit letters so to fit inside
								 * an integer we can only have 6 letters.
								 */
					if(name.length() > 6) {
						event.query().put("fail", "name too long (6)");
						redirect(event);
					}

					if(!name.matches("[a-zA-Z1-6.\\-]+")) {
									event.query().put("fail", "name invalid (a-z/1-6)");
									redirect(event);
																				}

								if(name.matches("[0-9]+")) {
									event.query().put("fail", "name alpha missing"); // [0-9]+ reserved for <id>
									redirect(event);
																				}

					if(mail.length() > 0 && mail.indexOf("@") == -1) {
						event.query().put("fail", "mail @ missing");
						redirect(event);
					}

					String user = "{\"name\":\"" + name + "\",\"pass\":\"" + pass + "\"";
					String list = "key,name";

					if(mail.length() > 0) {
						user += ",\"mail\":\"" + mail + "\"";
						list += ",mail";
					}

					user += "}";

					final String json = user;
					final String sort = list;

					Async.Work work = new Async.Work(event) {
						public void send(Async.Call call) throws Exception {
							call.post("/node", head(event), ("json=" + json + "&sort=" + sort + "&create").getBytes("utf-8"));
						}

						public void read(String host, String body) throws Exception {
							boolean invalid = body.indexOf("Validation") > 0;
							boolean collide = body.indexOf("Collision") > 0;

							if(invalid || collide) {
								String message = body.substring(body.indexOf("[") + 1, body.indexOf("]"));
								event.query().put("fail", message.substring(0, 4) + " " +
										(invalid ? "contains bad characters" : "") + " " +
										(collide ? "already registered" : ""));
							}
							else {
								JSONObject user = new JSONObject(body);
								event.session().put("user", user);
								event.query().put("success", user.getString("name"));
							}

							event.reply().wakeup(true);
						}

						public void fail(String host, Exception e) throws Exception {
							e.printStackTrace();
							event.query().put("fail", e.toString() + "[" + Root.local() + "]");
							event.reply().wakeup(true);
						}
					};

					event.daemon().client().send("localhost", work, 30);
					throw event;
				}
			}
		}
	}
}