package com.shin1ogawa;

import java.io.File;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.Query;
import javax.jdo.Transaction;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.dev.LocalDatastoreService;
import com.google.appengine.tools.development.ApiProxyLocalImpl;
import com.google.apphosting.api.ApiProxy;

/**
 * Relationshipのテスト用抽象クラス。
 * <p>
 * testの実行ごとにDatastroreを空にする。<br />
 * また、JavaのLogFormatterを少しカスタマイズしている。
 * </p>
 * 
 * @author shin1ogawa
 */
public class AbstractRelasionShipTest {

	private static final Logger logger = Logger
			.getLogger(AbstractRelasionShipTest.class.getName());

	private static PersistenceManagerFactory factory;

	/**
	 * {@link PersistenceManager}
	 */
	private static PersistenceManager pm;

	static final String LINE_SEPARATOR = System.getProperty("line.separator");

	@BeforeClass
	public static void setUpBeforeClass() {
		if (factory == null) {
			factory = JDOHelper
					.getPersistenceManagerFactory("transactions-optional");
		}
		if (pm == null) {
			pm = factory.getPersistenceManager();
		}
		for (Handler handler : Logger.getAnonymousLogger().getParent()
				.getHandlers()) {
			handler.setFormatter(new Formatter() {
				DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss.SSS");

				@Override
				public String format(LogRecord record) {
					StringBuffer buffer = new StringBuffer();
					String loggerName = record.getLoggerName();
					if (StringUtils.isEmpty(loggerName)) {
						loggerName = "Anonymous logger";
					}
					buffer.append(record.getLevel().getLocalizedName()).append(
							" ").append(
							dateFormat.format(new Date(record.getMillis())))
							.append(" : ").append(loggerName).append(" : ")
							.append(record.getMessage()).append(LINE_SEPARATOR);
					return buffer.toString();
				}
			});
		}
	}

	@Before
	public void setUp() {
		ApiProxy.setEnvironmentForCurrentThread(new TestEnvironment());
		ApiProxy.setDelegate(new ApiProxyLocalImpl(new File(
				"target/relationshipTest")) {
		});
		ApiProxyLocalImpl proxy = (ApiProxyLocalImpl) ApiProxy.getDelegate();
		proxy.setProperty(LocalDatastoreService.NO_STORAGE_PROPERTY,
				Boolean.TRUE.toString());
	}

	@After
	public void tearDown() {
		ApiProxyLocalImpl proxy = (ApiProxyLocalImpl) ApiProxy.getDelegate();
		LocalDatastoreService datastoreService = (LocalDatastoreService) proxy
				.getService("datastore_v3");
		datastoreService.clearProfiles();
		ApiProxy.setDelegate(null);
		ApiProxy.setEnvironmentForCurrentThread(null);
	}

	/**
	 * Entityを保存する。
	 * 
	 * @param <T>
	 * @param entity
	 *            {@link PersistenceManager#makePersistent(Object)}
	 * @return
	 */
	protected <T> T saveEntity(T entity) {
		Transaction transaction = pm.currentTransaction();
		try {
			transaction.begin();
			T persistent = pm.makePersistent(entity);
			transaction.commit();
			return persistent;
		} finally {
			if (transaction.isActive()) {
				transaction.rollback();
			}
		}
	}

	/**
	 * Entityクラスと{@link Key}を指定して該当する一件を取得する。
	 * <p>
	 * QueryにKeyのフィールド名を{@literal key}として埋め込んでいるため、主キーのfield名が{@literal key}
	 * である必要がある。
	 * </p>
	 * 
	 * @param <T>
	 * @param entityClass
	 * @param primaryKey
	 * @return Entityクラスと{@link Key}に該当するEntity。存在しない場合は{@code null}.
	 */
	protected <T> T queryEntityByKey(Class<T> entityClass, Key primaryKey) {
		Query query = pm.newQuery(entityClass);
		query.setFilter("key == pKey");
		query.declareParameters("java.lang.String pKey");
		@SuppressWarnings("unchecked")
		List<T> list = (List<T>) query.execute(KeyFactory
				.keyToString(primaryKey));
		query.closeAll();
		T entity = list.isEmpty() ? null : list.get(0);
		return entity;
	}

	/**
	 * Entityクラスと親Entityの{@link Key}を指定して該当する一件を取得する。
	 * <p>
	 * Queryに親EntityKeyのフィールド名を{@literal ancestorKey}として埋め込んでいるため、主キーのfield名が
	 * {@literal ancestorKey} である必要がある。
	 * </p>
	 * 
	 * @param <T>
	 * @param entityClass
	 * @param ansecstorKey
	 * @return Entityクラスと{@link Key}に該当するEntity。存在しない場合は{@code null}.
	 */
	protected <T> List<T> queryEntitiesByAncestorKey(Class<T> entityClass,
			Key ancestorKey) {
		Query query = pm.newQuery(entityClass);
		query.setFilter("ancestorKey == pAncestorKey");
		query.declareParameters("java.lang.String pAncestorKey");
		@SuppressWarnings("unchecked")
		List<T> list = (List<T>) query.execute(KeyFactory
				.keyToString(ancestorKey));
		query.closeAll();
		return list;
	}

	/**
	 * Entityクラスを全件取得する。
	 * 
	 * @param <T>
	 * @param entityClass
	 * @return {@link Query#execute()}
	 */
	protected <T> List<T> queryEntities(Class<?> entityClass) {
		Query query = pm.newQuery(entityClass);
		@SuppressWarnings("unchecked")
		List<T> entities = (List<T>) query.execute();
		query.closeAll();
		return entities;
	}

	/**
	 * Entityの内容をログに出力する。
	 * <p>
	 * {@link ToStringBuilder#reflectionToString(Object)}を使う。
	 * </p>
	 * 
	 * @param level
	 * @param entity
	 */
	protected void logEntities(Level level, List<?> entities) {
		for (Object entity : entities) {
			logger.log(level, ToStringBuilder.reflectionToString(entity));
		}
	}

	/**
	 * Entityの内容をログに出力する。
	 * <p>
	 * {@link ToStringBuilder#reflectionToString(Object)}を使う。
	 * </p>
	 * 
	 * @param level
	 * @param entity
	 */
	protected void logEntity(Level level, Object entity) {
		logger.log(level, ToStringBuilder.reflectionToString(entity));
	}

	/**
	 * @param pm
	 *            the pm to set
	 */
	protected static void setPersistenceManager(PersistenceManager pm) {
		AbstractRelasionShipTest.pm = pm;
	}

	/**
	 * @return the pm
	 */
	protected static PersistenceManager getPersistenceManager() {
		return pm;
	}

	public static PersistenceManagerFactory getFactory() {
		return factory;
	}

}
