/* ===================================================================
 * AbstractSpringEnabledTransactionalTest.java
 * 
 * Created Oct 3, 2005 12:02:08 PM
 * 
 * Copyright (c) 2005 Matt Magoffin (spamsqr@msqr.us)
 * 
 * This program is free software; you can redistribute it and/or 
 * modify it under the terms of the GNU General Public License as 
 * published by the Free Software Foundation; either version 2 of 
 * the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License 
 * along with this program; if not, write to the Free Software 
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 
 * 02111-1307 USA
 * ===================================================================
 * $Id: AbstractSpringEnabledTransactionalTest.java 39 2009-05-06 03:30:47Z msqr $
 * ===================================================================
 */

package magoffin.matt.ieat;

import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import magoffin.matt.ieat.biz.DomainObjectFactory;
import magoffin.matt.ieat.dao.BaseDao;
import magoffin.matt.ieat.dao.CourseDao;
import magoffin.matt.ieat.dao.DifficultyDao;
import magoffin.matt.ieat.dao.EthnicityDao;
import magoffin.matt.ieat.dao.IngredientDao;
import magoffin.matt.ieat.dao.PrepTimeDao;
import magoffin.matt.ieat.dao.SystemDao;
import magoffin.matt.ieat.dao.UnitDao;
import magoffin.matt.ieat.domain.Base;
import magoffin.matt.ieat.domain.Course;
import magoffin.matt.ieat.domain.Difficulty;
import magoffin.matt.ieat.domain.Ethnicity;
import magoffin.matt.ieat.domain.Ingredient;
import magoffin.matt.ieat.domain.PrepTime;
import magoffin.matt.ieat.domain.Recipe;
import magoffin.matt.ieat.domain.RecipeIngredient;
import magoffin.matt.ieat.domain.RecipeStep;
import magoffin.matt.ieat.domain.System;
import magoffin.matt.ieat.domain.Unit;
import magoffin.matt.ieat.domain.User;

import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;

/**
 * Extension of Spring's AbstractTransactionalDataSourceSpringContextTests 
 * with helper methods for the LMS project.
 * 
 * @author matt.magoffin
 * @version $Revision: 39 $ $Date: 2009-05-06 15:30:47 +1200 (Wed, 06 May 2009) $
 */
public abstract class AbstractSpringEnabledTransactionalTest extends
		AbstractTransactionalDataSourceSpringContextTests {

	/** The name of the JdbcTemplate bean in the Spring configuration file. */
	private static final String JDBC_TEMPLATE_BEAN_NAME = "myJdbcTemplate";

	/** The base Spring context. */
	private static AbstractApplicationContext baseContext = null;

	/** A test login. */
	public static final String TEST_USER_LOGIN = "_test_";
	
	/** A test password. */
	public static final String TEST_USER_PASS = "_foo_";
	
	/** A test ID. */
	public static final Integer DEFAULT_LOAD_ID = new Integer(1);

	/** Logger instance for extending classes to use. */
	protected final Logger log = Logger.getLogger(getClass());
	
	/** The DomainObjectFactory to test with. */
	protected DomainObjectFactory domainObjectFactory;
	
	/** The BaseDao. */
	protected BaseDao baseDao;
	
	/** The CourseDao. */
	protected CourseDao courseDao;
	
	/** The DifficultyDao. */
	protected DifficultyDao difficultyDao;
	
	/** The EthnicityDao. */
	protected EthnicityDao ethnicityDao;
	
	/** The IngredientDao. */
	protected IngredientDao ingredientDao;
	
	/** The PrepTimeDao. */
	protected PrepTimeDao prepTimeDao;
	
	/** The SystemDao. */
	protected SystemDao systemDao;
	
	/** The UnitDao. */
	protected UnitDao unitDao;
	
	/**
	 * A cache of ApplicationContext objects created from individual URLs, used
	 * to keep from having to recreate ApplicationContext objects between test
	 * case invocations.
	 */
	private static final Map<URL, ApplicationContext> APP_CONTEXT_CACHE 
		= new HashMap<URL, ApplicationContext>();

	/** The paths to the default Spring context files. */
	private static final String[] DEFAULT_APP_CONTEXT_PATHS = {
			//"classpath:magoffin/matt/ieat/AbstractSpringEnabledTestContext.xml",
			"web/WEB-INF/applicationContext.xml",
			//"web/WEB-INF/dataAccessContext.xml",
			//"web/WEB-INF/ieat-servlet.xml",
	};

	/**
	 * Default constructor.
	 */
	public AbstractSpringEnabledTransactionalTest() {
		super();
		setPopulateProtectedVariables(true);
		if ( log.isInfoEnabled() ) {
			log.info("\n\n\n*** Constructing test class [" +getClass().getName() +"] ***\n");
		}
	}
		
	@Override
	protected final String[] getConfigLocations() {
		return DEFAULT_APP_CONTEXT_PATHS;
	}

	@Override
	protected Object contextKey() {
		return getClass();
	}

	@SuppressWarnings("unchecked")
	@Override
	protected ConfigurableApplicationContext loadContext(Object key) {
		ConfigurableApplicationContext context = getBaseContext(getConfigLocations());

		List classHierarchy = getClassHierarchy();
		for (int i = 0; i < classHierarchy.size(); i++) {
			Class clazz = (Class)classHierarchy.get(i);
			context = getApplicationContext(clazz,
					getClassName(clazz) + "Context.xml", context);
		}
		
		return context;
	}

	private List<Class<?>> getClassHierarchy() {
		List<Class<?>> result = new ArrayList<Class<?>>();
		Class<?> superclass = getClass();
		do {
			result.add(superclass);
		} while ((superclass = superclass.getSuperclass()) != null
				&& superclass != AbstractSpringEnabledTransactionalTest.class);
		return result;
	}

	private String getClassName(Class<?> clazz) {
		String fullClassName = clazz.getName();
		String result = fullClassName
				.substring(fullClassName.lastIndexOf(".") + 1);
		return result;
	}

	private ConfigurableApplicationContext getApplicationContext(Class<?> clazz,
			String resourceName, ConfigurableApplicationContext parentContext) {
		log.debug("Attempting to locate " + resourceName);
		URL url = clazz.getResource(resourceName);

		if (url == null) {
			return parentContext;
		}

		if (!APP_CONTEXT_CACHE.containsKey(url)) {
			log.info("Loading " + url);
			APP_CONTEXT_CACHE.put(url,
					new ClassPathXmlApplicationContext(new String[] { url
							.toString() }, parentContext));
		}
		return (AbstractApplicationContext) APP_CONTEXT_CACHE.get(url);
	}

	private synchronized AbstractApplicationContext getBaseContext(String[] configLocations) {
		if ( baseContext == null ) {
			if ( configLocations != null && configLocations.length > 0 ) {
				// configure env location first
				ConfigurableApplicationContext envContext = 
					getApplicationContext( AbstractSpringEnabledTransactionalTest.class, 
							getClassName( AbstractSpringEnabledTransactionalTest.class) + "Context.xml", 
							null);
				baseContext = new FileSystemXmlApplicationContext(configLocations,
						envContext);
			}
			
			jdbcTemplate = (JdbcTemplate)baseContext.getBean(JDBC_TEMPLATE_BEAN_NAME);

		} else {
			jdbcTemplate = (JdbcTemplate)baseContext.getBean(JDBC_TEMPLATE_BEAN_NAME);
		}
		return baseContext;
	}

	@Override
	protected void onTearDownInTransaction() throws Exception {
		super.onTearDownInTransaction();
		java.lang.System.gc();
		java.lang.System.runFinalization();
	}

	/**
	 * @return Returns the baseContext.
	 */
	public static AbstractApplicationContext getBaseContext() {
		return baseContext;
	}
	
	/**
	 * Get a dummy user.
	 * @return the dummy user.
	 */
	protected User getDummyUser() {
		User u = domainObjectFactory.getUserInstance();
		u.setAccessLevel(new Integer(1));
		u.setCountry(Locale.getDefault().getCountry());
		u.setCreatedDate(Calendar.getInstance());
		u.setEmail("ieat.test@localhost");
		u.setLanguage(Locale.getDefault().getLanguage());
		u.setLogin(TEST_USER_LOGIN);
		u.setName("Test User");
		u.setPassword(TEST_USER_PASS);
		return u;
	}
	
	/**
	 * Get a dummy recipe (non-persisted).
	 * @return recipe
	 */
	@SuppressWarnings("unchecked")
	protected Recipe getDummyRecipe() {
		Recipe r = domainObjectFactory.getRecipeInstance();
		
		r.setCreatedDate(Calendar.getInstance());
		
		r.setBase(getTestBase());
		r.setCourse(getTestCourse());
		r.setDifficulty(getTestDifficulty());
		
		r.setEthnicity(getTestEthnicity());
		
		r.setExcerpt("This is the test recipe description.");
		
		r.setName("My Test Recipe " +r.getCreatedDate().getTime() );
		
		r.setPrepTime(getTestPrepTime());
		
		r.setServingSize(new Integer(1));
		
		r.setSource("iEat Test Case");
		
		List<System> systems = systemDao.getSystems();
		for ( System system : systems ) {
			if ( "US".equals(system.getValue()) ) {
				r.setSystem(system);
				break;
			}
		}
		
		List units = unitDao.getUnits();
		List ingredients = ingredientDao.getIngredients();
		for ( int i = 0; i < 3 && i < ingredients.size(); i++ ) {
			RecipeIngredient ri = domainObjectFactory.getRecipeIngredientInstance();
			ri.setQuantity(1.5);
			ri.setIngredient((Ingredient)ingredients.get(i));
			ri.setUnit((Unit)units.get(0));
			r.getIngredient().add(ri);
		}
		
		for ( int i = 0; i < 3; i++ ) {
			RecipeStep step = domainObjectFactory.getRecipeStepInstance();
			step.setValue("Dummy step " +i);
			r.getStep().add(step);
		}
		
		return r;
	}
	
	/**
	 * @return a test Base
	 */
	protected Base getTestBase() {
		return baseDao.getBases().get(0);
	}
	
	/**
	 * @return a test Course
	 */
	protected Course getTestCourse() {
		return courseDao.getCourses().get(0);
	}
	
	/**
	 * @return a test Difficulty
	 */
	protected Difficulty getTestDifficulty() {
		return difficultyDao.getDifficulties().get(0);
	}
		
	/**
	 * @return a test Ethnicity
	 */
	protected Ethnicity getTestEthnicity() {
		return ethnicityDao.getEthnicities().get(0);
	}
	
	/**
	 * @return a test PrepTime
	 */
	protected PrepTime getTestPrepTime() {
		return prepTimeDao.getPrepTimes().get(0);
	}
}
