package appengine.test;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.io.IOUtils;
import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;

import appengine.util.DatastoreServiceUtil;

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.Key;
import com.google.appengine.api.datastore.Text;

/**
 * Servlet上でのTest実行の結果をDatastoreに保存する。
 * <p>一回の実行単位でひとつのEntityGroupを作成する。</p>
 * @author shin1ogawa
 */
public class DatastoreRunListener extends RunListener {

	static final Logger LOGGER = Logger.getLogger(DatastoreRunListener.class.getName());

	/** テストのハンドル結果を保存するためのKINDの名称 */
	public static final String KIND = "_junit4_servlet_runner";

	final Key rootKey;

	final String className;


	/**
	 * the constructor.
	 * @param key JUnit実行単位で用意されたルートEntityのKey
	 * @throws EntityNotFoundException 
	 * @category constructor
	 */
	public DatastoreRunListener(Key key) throws EntityNotFoundException {
		Entity entity = DatastoreServiceFactory.getDatastoreService().get(key);
		this.rootKey = key;
		this.className = (String) entity.getProperty("className");
		entity.setProperty("timestamp", System.currentTimeMillis());
		DatastoreServiceUtil.putWithRetry(entity, 5);
		LOGGER.log(Level.FINE, getRootKey().toString());
	}

	@Override
	public void testAssumptionFailure(Failure failure) {
		Entity entity = newEntity("testAssumptionFailure");
		failureToProperty(failure, entity);
		DatastoreServiceUtil.putWithRetry(entity, 5);
	}

	@Override
	public void testFailure(Failure failure) throws Exception {
		Entity entity = newEntity("testFailure");
		failureToProperty(failure, entity);
		DatastoreServiceUtil.putWithRetry(entity, 5);
	}

	@Override
	public void testFinished(Description description) throws Exception {
		Entity entity = newEntity("testFinished");
		descriptionToProperty(description, entity);
		DatastoreServiceUtil.putWithRetry(entity, 5);
	}

	@Override
	public void testIgnored(Description description) throws Exception {
		Entity entity = newEntity("testIgnored");
		descriptionToProperty(description, entity);
		DatastoreServiceUtil.putWithRetry(entity, 5);
	}

	@Override
	public void testRunFinished(Result result) throws Exception {
		Entity entity = newEntity("testRunFinished");
		resultToProperty(result, entity);
		DatastoreServiceUtil.putWithRetry(entity, 5);
	}

	@Override
	public void testRunStarted(Description description) throws Exception {
		Entity entity = newEntity("testRunStarted");
		descriptionToProperty(description, entity);
		DatastoreServiceUtil.putWithRetry(entity, 5);
	}

	@Override
	public void testStarted(Description description) throws Exception {
		Entity entity = newEntity("testStarted");
		descriptionToProperty(description, entity);
		DatastoreServiceUtil.putWithRetry(entity, 5);
	}

	private Entity newEntity(String handler) {
		Key key =
				DatastoreServiceFactory.getDatastoreService().allocateIds(getRootKey(), KIND, 1)
					.getStart();
		LOGGER.log(Level.FINE, rootKey.toString() + ":" + handler + " entity Key=" + key);
		Entity entity = new Entity(key);
		entity.setProperty("className", className);
		entity.setProperty("timestamp", System.currentTimeMillis());
		entity.setProperty("handler", handler);
		return entity;
	}

	private void failureToProperty(Failure failure, Entity entity) {
		if (failure == null) {
			return;
		}
		Description description = failure.getDescription();
		descriptionToProperty(description, entity);
		Throwable exception = failure.getException();
		exceptionToProperty(exception, entity);
	}

	private void exceptionToProperty(Throwable exception, Entity entity) {
		if (exception == null) {
			return;
		}
		entity.setProperty("exception", exception.toString());
		StringWriter writer = new StringWriter();
		PrintWriter w = new PrintWriter(writer);
		exception.printStackTrace(w);
		IOUtils.closeQuietly(w);
		entity
			.setUnindexedProperty("exception.stackTrace", new Text(writer.getBuffer().toString()));
	}

	private void descriptionToProperty(Description description, Entity entity) {
		if (description == null) {
			return;
		}
		entity.setProperty("description.displayName", description.getDisplayName());
	}

	private void resultToProperty(Result result, Entity entity) {
		if (result == null) {
			return;
		}
		entity.setProperty("result.failureCount", result.getFailureCount());
		entity.setProperty("result.ignoreCount", result.getIgnoreCount());
		entity.setProperty("result.runCount", result.getRunCount());
		entity.setProperty("result.runTime", result.getRunTime());
	}

	/**
	 * @return the rootKey
	 * @category accessor
	 */
	public Key getRootKey() {
		return rootKey;
	}
}
