package sample;

import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.JUnit4;

import appengine.test.DatastoreRunListener;
import appengine.util.DatastoreServiceUtil;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Text;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.SortDirection;
import com.google.appengine.api.labs.taskqueue.Queue;
import com.google.appengine.api.labs.taskqueue.QueueFactory;
import com.google.appengine.api.labs.taskqueue.TaskOptions;
import com.google.appengine.api.labs.taskqueue.TaskOptions.Method;

/**
 * @author shin1ogawa
 */
public class TestRunnerServlet extends HttpServlet {

	private static final long serialVersionUID = 2743297238271120551L;


	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		resp.setCharacterEncoding("utf-8");
		resp.setContentType("text/html");

		String className = req.getParameter("class");
		String test = req.getParameter("test");
		String result = req.getParameter("result");
		if (StringUtils.isNotEmpty(className)) {
			writeHeader(resp.getWriter(), className);
			addTestTask(resp.getWriter(), className);
		} else if (StringUtils.isNotEmpty(test)) {
			writeHeader(resp.getWriter(), test);
			try {
				test(resp.getWriter(), test);
			} catch (EntityNotFoundException e) {
				Logger.getLogger(TestRunnerServlet.class.getName()).log(Level.WARNING,
						"test key=" + test, e);
			}
		} else if (StringUtils.isNotEmpty(result)) {
			writeHeader(resp.getWriter(), result);
			result(resp.getWriter(), result);
		} else {
			writeHeader(resp.getWriter(), "過去のテスト実行の結果");
			resultList(resp.getWriter());
		}

		writeFooter(resp.getWriter());
		resp.getWriter().flush();
	}

	private void result(PrintWriter writer, String encodedKey) {
		Key key = KeyFactory.stringToKey(encodedKey);
		DatastoreService service = DatastoreServiceFactory.getDatastoreService();
		Query query = new Query(DatastoreRunListener.KIND, key).addSort("timestamp");
		List<Entity> list =
				service.prepare(query).asList(FetchOptions.Builder.withOffset(0).limit(5000));
		String className = null;
		Map<String, List<Entity>> map = new HashMap<String, List<Entity>>();
		for (Entity entity : list) {
			if (entity.hasProperty("root")) {
				className = (String) entity.getProperty("className");
				continue;
			}
			String displayName = (String) entity.getProperty("description.displayName");
			List<Entity> methodStatuses = map.get(displayName);
			if (methodStatuses == null) {
				methodStatuses = new ArrayList<Entity>(2);
				map.put(displayName, methodStatuses);
			}
			methodStatuses.add(entity);
		}
		writer.println("<h1>" + className + "</h1>");
		Iterator<Entry<String, List<Entity>>> i = map.entrySet().iterator();
		while (i.hasNext()) {
			Entry<String, List<Entity>> next = i.next();
			String methodName = next.getKey();
			methodName = methodName.replaceFirst("\\(" + className + "\\)", "");
			List<Entity> statuses = next.getValue();
			long start = 0, end = 0;
			boolean failure = false;
			boolean ignored = false;
			String exceptionString = null;
			String exceptionStackTrace = null;
			for (Entity entity : statuses) {
				String handler = (String) entity.getProperty("handler");
				long timestamp = (Long) entity.getProperty("timestamp");
				if (handler.equals("testStarted")) {
					start = timestamp;
				} else if (handler.equals("testFinished")) {
					end = timestamp;
				} else if (handler.equals("testAssumptionFailure") || handler.equals("testFailure")) {
					end = timestamp;
					failure = true;
					exceptionString = (String) entity.getProperty("exception");
					Text _exceptionStackTrace = (Text) entity.getProperty("exception.stackTrace");
					if (_exceptionStackTrace != null) {
						exceptionStackTrace = _exceptionStackTrace.getValue();
						exceptionStackTrace.replaceAll("<", "&lt;");
						exceptionStackTrace.replaceAll(">", "&gt;");
					}
				} else if (handler.equals("testIgnored")) {
					end = timestamp;
					ignored = true;
				}
			}
			long duration = end - start;
			if (!failure && !ignored) {
				writer.println("<li><span style=\"color:green;\">" + methodName
						+ "</span><span style=\"font-size:smaller;\">(" + duration
						+ "[ms])</span></li>");
			} else if (failure) {
				writer
					.print("<li><span style=\"color:red;\">" + methodName
							+ "<span style=\"font-size:smaller;\"></span>(" + duration
							+ "[ms])</span>");
				if (StringUtils.isNotEmpty(exceptionString)) {
					if (exceptionString.startsWith(AssertionError.class.getName())) {
						writer.print(" <span style=\"color:blue;\">" + exceptionString + "</span>");
					} else {
						writer.print(" <span style=\"color:red;\">" + exceptionString + "</span>");
					}
				}
//				if (StringUtils.isNotEmpty(exceptionStackTrace)) {
//					writer.print("<br /><pre style=\"color:red;font-size:smaller\""
//							+ exceptionStackTrace + "</pre>");
//				}
				writer.println("</li>");
			} else {
				writer.println("<li>[ignored]" + methodName + "</li>");
			}
		}
	}

	private void resultList(PrintWriter writer) {
		Query query =
				new Query(DatastoreRunListener.KIND).addFilter("root", FilterOperator.EQUAL, true)
					.addSort("timestamp", SortDirection.DESCENDING);
		List<Entity> list =
				DatastoreServiceFactory.getDatastoreService().prepare(query).asList(
						FetchOptions.Builder.withOffset(0).limit(100));
		SimpleDateFormat formatter =
				new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS", Locale.JAPANESE);
		writer.println("<h1>過去のテスト実行の結果</h1>");
		writer.println("<ul>");
		for (Entity entity : list) {
			writer
				.print("<li><a href=\"?result=" + KeyFactory.keyToString(entity.getKey()) + "\">");
			writer.print(formatter.format(new Date((Long) entity.getProperty("timestamp"))));
			writer.print(" ");
			writer.print(entity.getProperty("className"));
			writer.println("</li>");
		}
		writer.println("</ul>");
	}

	private void addTestTask(PrintWriter w, String className) {
		Key key =
				DatastoreServiceFactory.getDatastoreService().allocateIds(
						DatastoreRunListener.KIND, 1).getStart();
		Entity entity = new Entity(key);
		entity.setProperty("className", className);
		entity.setProperty("root", true);
		DatastoreServiceUtil.putWithRetry(entity, 5);
		Queue queue = QueueFactory.getQueue("junit");
		queue.add(TaskOptions.Builder.url("/testrunner").method(Method.GET).param("test",
				KeyFactory.keyToString(key)));
		w.println("<h1>" + className + "</h1>");
		w.println("<p>" + className + "の実行をキューに追加しました。結果のURLは次のとおりです。</p>");
		w.println("<p><a href=\"?result=" + KeyFactory.keyToString(key) + "\">" + className
				+ "の実行結果</a></p>");
	}

	private void test(PrintWriter w, String encodedKey) throws EntityNotFoundException {
		Key key = KeyFactory.stringToKey(encodedKey);
		DatastoreService service = DatastoreServiceFactory.getDatastoreService();
		String className = (String) service.get(key).getProperty("className");
		JUnit4 jUnit4 = null;
		try {
			Class<?> forName = Class.forName(className);
			jUnit4 = new JUnit4(forName);
		} catch (Throwable e) {
			writeException(w, e);
		}
		RunNotifier notifier = new RunNotifier();
		notifier.addListener(new DatastoreRunListener(key));
		jUnit4.run(notifier);
	}

	private void writeException(PrintWriter w, Throwable t) {
		w.println("<p>" + t.toString() + "</p>");
		w.println("<div><pre>");
		t.printStackTrace(w);
		w.println("</pre></div>");
	}

	private void writeHeader(PrintWriter w, String title) {
		w
			.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">");
		w.println("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"ja\" lang=\"ja\">");
		w.println("<head>");
		w.println("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />");
		w.println("<meta http-equiv=\"Content-Style-Type\" content=\"text/css\" />");
		w.println("<title>" + title + "</title>");
		w.println("</head>");
		w.println("<body>");
	}

	private void writeFooter(PrintWriter w) {
		w.println("</body>");
		w.println("</html>");
	}
}
