/* ===================================================================
 * AbstractSpringEnabledTest.java
 * 
 * Copyright (c) 2004 Matt Magoffin. Created Sep 9, 2004 10:07:47 AM.
 * 
 * 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: AbstractSpringEnabledTest.java 74 2009-05-15 04:44:37Z msqr $
 * ===================================================================
 */

package magoffin.matt.ieat.test;

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 java.util.ResourceBundle;

import junit.framework.TestCase;
import magoffin.matt.ieat.biz.BizContext;
import magoffin.matt.ieat.biz.DomainObjectFactory;
import magoffin.matt.ieat.biz.RecipeBiz;
import magoffin.matt.ieat.biz.RecipeSearchBiz;
import magoffin.matt.ieat.biz.RecipeSearchIndexBiz;
import magoffin.matt.ieat.biz.UserBiz;
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.MealDao;
import magoffin.matt.ieat.dao.PrepTimeDao;
import magoffin.matt.ieat.dao.RecipeDao;
import magoffin.matt.ieat.dao.UnitDao;
import magoffin.matt.ieat.dao.UserDao;
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 magoffin.matt.xweb.util.AppContextSupport;

import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
 * Base JUnit test case for Spring related test cases.
 * 
 * <p>Code derived from Robert Newson's posts on 
 * http://opensource.atlassian.com/confluence/spring/pages/viewpage.action?pageId=146&showComments=true#comments
 * </p>
 * 
 * @author Matt Magoffin (spamsqr@msqr.us)
 * @version $Revision: 74 $ $Date: 2009-05-15 16:44:37 +1200 (Fri, 15 May 2009) $
 */
public abstract class AbstractSpringEnabledTest extends TestCase {

	/** A class logger. */
	protected final Logger log = Logger.getLogger(getClass());

	/** The test user login. */
	public static final String TEST_USER_LOGIN = "_test_";
	
	/** The test user password. */
	public static final String TEST_USER_PASS = "_foo_";
	
	/** The default load ID. */
	public static final Integer DEFAULT_LOAD_ID = new Integer(1);
	
	private static Logger LOG = Logger.getLogger(AbstractSpringEnabledTest.class);

	private static Map<URL, AbstractApplicationContext> applicationContextCache 
		= new HashMap<URL, AbstractApplicationContext>();
	
	private static final String[] DEFAULT_APP_CONTEXT_PATHS = {
			//"classpath:magoffin/matt/ieat/test/AbstractSpringEnabledTestContext.xml",
			"web/WEB-INF/applicationContext.xml"
	};
	
	private AbstractApplicationContext context;
	
	/**
	 * Constructor.
	 */
	public AbstractSpringEnabledTest() {
		super();
	}

	/**
	 * Construct with a name.
	 * @param name the name
	 */
	public AbstractSpringEnabledTest(String name) {
		super(name);
	}
	
	/**
	 * Get a test BizContext.
	 * @return the biz context
	 */
	protected BizContext getBizContext() {
		AppContextSupport appContextSupport = getAppContextSupport();
		return new TestBizContext(appContextSupport,getDummyUser());
	}

	/**
	 * Get the domain object factory.
	 * @return the domain object factory
	 */
	protected DomainObjectFactory getDomainObjectFactory() {
		return (DomainObjectFactory)getContext().getBean("domainObjectFactory");
	}
	
	/**
	 * Get the application context.
	 * @return the context
	 */
	protected ApplicationContext getContext() {
		assertNotNull(context);
		return context;
	}
	
	/**
	 * Get a test property from the test resource bundle.
	 * @param key the key
	 * @return the property value
	 */
	protected String getTestProperty(String key) {
		ResourceBundle rsrc = ResourceBundle.getBundle("test.properties");
		return rsrc.getString(key);
	}
	
	/**
	 * Get a list of file paths to add as Spring contexts.
	 * 
	 * <p>Extending test cases can override this method to provide 
	 * a different default set (or <em>null</em> for none).</p>
	 * @return the contexts
	 */
	protected String[] getDefaultContexts() {
		return DEFAULT_APP_CONTEXT_PATHS;
	}

	@Override
	protected void setUp() throws Exception {
		super.setUp();

		context = null;
		List<Class<?>> classHierarchy = getClassHierarchy();
		for (int i = classHierarchy.size() - 1; i >= 0; i--) {
			Class<?> clazz = classHierarchy.get(i);
			context = getApplicationContext(clazz,
					getClassName(clazz) + "Context.xml", context);
		}
		if ( getDefaultContexts() != null && getDefaultContexts().length > 0 ) {
			context = new FileSystemXmlApplicationContext(getDefaultContexts(),context);
		}
	}

	@Override
	protected void tearDown() throws Exception {
		closeContext();
	}

	/**
	 * Close the context.
	 */
	protected final void closeContext() {
		if (context != null) {
			context.close();
			context = null;
		}
	}

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

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

	private AbstractApplicationContext getApplicationContext(Class<?> clazz,
			String resourceName, AbstractApplicationContext parentContext) {
		/*if ( clazz == AbstractSpringEnabledTest.class ) {
			return null; // don't load this class or any parent class
		}*/
		log.debug("Attempting to locate " + resourceName);
		URL url = clazz.getResource(resourceName);

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

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

	/**
	 * Delete a recipe.
	 * @param r the recipe
	 */
	protected void cleanOutRecipe(Recipe r) {
		if ( r == null || r.getRecipeId() == null ) {
			return;
		}
		RecipeDao recipeDao = getRecipeDao();
		try {
			recipeDao.delete(recipeDao.get(r.getRecipeId()));
		} catch ( Exception e ) {
			LOG.warn("Unable to clean out recipe",e);
		}
	}

	/**
	 * Delete a user.
	 * @param u the user to delete
	 */
	protected void cleanOutUser(User u) {
		if ( u == null || u.getUserId() == null ) {
			return;
		}
		UserDao userDao = getUserDao();
		try {
			userDao.delete(userDao.get(u.getUserId()));
		} catch ( Exception e ) {
			LOG.warn("Unable to clean out user",e);
		}
	}

	/**
	 * Get a dummy recipe (non-persisted).
	 * @return recipe
	 */
	@SuppressWarnings("unchecked")
	protected Recipe getDummyRecipe() {
		DomainObjectFactory dof = getDomainObjectFactory();
		Recipe r = dof.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");
		
		System system = dof.getSystemInstance();
		system.setSystemId(getTestSystemId());
		system.setValue("US");
		r.setSystem(system);
		
		List units = getUnitDao().getUnits();
		List ingredients = getIngredientDao().getIngredients();
		for ( int i = 0; i < 3 && i < ingredients.size(); i++ ) {
			RecipeIngredient ri = dof.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 = dof.getRecipeStepInstance();
			step.setValue("Dummy step " +i);
			r.getStep().add(step);
		}
		
		return r;
	}
	
	/**
	 * API for test callback with dummy user.
	 */
	protected static interface TestWithDummyUser {
		/**
		 * Perform the test.
		 * @param user the dummy user to test with
		 * @throws Exception if any error occurs
		 */
		public void test(User user) throws Exception;
	}
	
	/**
	 * Run a test with a registered dummy user.
	 * @param callback the callback test
	 * @throws Exception if any error occurs
	 */
	protected void doTestWithRegisteredDummyUser(TestWithDummyUser callback) throws Exception {
		User u = null;
		UserBiz userBiz = getUserBiz();
		try {
			u = getDummyUser();
			u = userBiz.storeUser(u,null);
			callback.test(u);
		} finally {
			if ( u != null && u.getUserId() != null ) {
				userBiz.removeUser(u.getUserId(),null);
			}
		}
	}
	
	/**
	 * API for a test with a persisted recipe.
	 */
	protected static interface TestWithRecipe {
		/**
		 * Run the test.
		 * @param recipe the persisted recipe
		 * @param context the test context
		 * @throws Exception if an error occurs
		 */
		public void test(Recipe recipe, BizContext context) throws Exception;
	}
	
	/**
	 * Run a test with a persisted recipe.
	 * @param callback the callback
	 * @throws Exception if an error occurs
	 */
	protected void doTestWithRecipe(final TestWithRecipe callback) throws Exception {
		doTestWithRegisteredDummyUser(new TestWithDummyUser() {
			public void test(User user) throws Exception {
				Recipe r = null;
				RecipeBiz recipeBiz = getRecipeBiz();
				BizContext bizContext = new TestBizContext(getAppContextSupport(),user);
				try {
					r = getDummyRecipe();
					r = recipeBiz.storeRecipe(r,bizContext);
					callback.test(r,bizContext);
				} finally {
					if ( r != null && r.getRecipeId() != null ) {
						recipeBiz.removeRecipe(r.getRecipeId(),bizContext);
					}
				}
			}
		});
	}
	
	/**
	 * @return the AppContextSupport
	 */
	protected AppContextSupport getAppContextSupport() {
		AppContextInitializer init = (AppContextInitializer)getContext()
				.getBean("applicationInitializer",AppContextInitializer.class);
		return init.getAppContextSupport();
	}
	
	/**
	 * @return the RecipeDao
	 */
	protected RecipeDao getRecipeDao() {
		return (RecipeDao)getContext().getBean("recipeDao");
	}

	/**
	 * @return the MealDao
	 */
	protected MealDao getMealDao() {
		return (MealDao)getContext().getBean("mealDao");
	}

	/**
	 * @return the BaseDao
	 */
	protected BaseDao getBaseDao() {
		return (BaseDao)getContext().getBean("baseDao");
	}
	
	/**
	 * @return the CourseDao
	 */
	protected CourseDao getCourseDao() {
		return (CourseDao)getContext().getBean("courseDao");
	}
	
	/**
	 * @return the DifficultyDao
	 */
	protected DifficultyDao getDifficultyDao() {
		return (DifficultyDao)getContext().getBean("difficultyDao");
	}
	
	/**
	 * @return the EthnicityDao
	 */
	protected EthnicityDao getEthnicityDao() {
		return (EthnicityDao)getContext().getBean("ethnicityDao");
	}
	
	/**
	 * @return the IngredientDao
	 */
	protected IngredientDao getIngredientDao() {
		return (IngredientDao)getContext().getBean("ingredientDao");
	}
	
	/**
	 * @return the PrepTimeDao
	 */
	protected PrepTimeDao getPrepTimeDao() {
		return (PrepTimeDao)getContext().getBean("prepTimeDao");
	}
	
	/**
	 * @return the UnitDao
	 */
	protected UnitDao getUnitDao() {
		return (UnitDao)getContext().getBean("unitDao");
	}
	
	/**
	 * @return a test recipe ID
	 */
	protected Integer getTestRecipeId() {
		return getTestIdOrDefault("test.load.recipe.id",DEFAULT_LOAD_ID);
	}
	
	/**
	 * @return a test Base ID
	 */
	protected Integer getTestBaseId() {
		return getTestIdOrDefault("test.load.base.id",DEFAULT_LOAD_ID);
	}
	
	/**
	 * @return a test Base
	 */
	protected Base getTestBase() {
		return getBaseDao().getBases().get(0);
	}
	
	/**
	 * @return a test Course ID
	 */
	protected Integer getTestCourseId() {
		return getTestIdOrDefault("test.load.course.id",DEFAULT_LOAD_ID);
	}
	
	/**
	 * @return a test Difficulty
	 */
	protected Difficulty getTestDifficulty() {
		return getDifficultyDao().getDifficulties().get(0);
	}
	
	/**
	 * @return a test Difficulty ID
	 */
	protected Integer getTestDifficultyId() {
		return getTestIdOrDefault("test.load.difficulty.id",DEFAULT_LOAD_ID);
	}
	
	/**
	 * @return a test Ethnicity
	 */
	protected Ethnicity getTestEthnicity() {
		return getEthnicityDao().getEthnicities().get(0);
	}
	
	/**
	 * @return a test Ehtnicity ID
	 */
	protected Integer getTestEthnicityId() {
		return getTestIdOrDefault("test.load.ethnicity.id",DEFAULT_LOAD_ID);
	}
	
	/**
	 * @return a test PrepTime
	 */
	protected PrepTime getTestPrepTime() {
		return getPrepTimeDao().getPrepTimes().get(0);
	}
	
	/**
	 * @return a test PrepTime ID
	 */
	protected Integer getTestPrepTimeId() {
		return getTestIdOrDefault("test.load.preptime.id",DEFAULT_LOAD_ID);
	}
	
	/**
	 * @return a test System ID
	 */
	protected Integer getTestSystemId() {
		return getTestIdOrDefault("test.load.system.id",DEFAULT_LOAD_ID);
	}
	
	private Integer getTestIdOrDefault(String key, Integer defaultId) {
		try {
			return Integer.valueOf(getTestProperty(key));
		} catch ( Exception e ) {
			// nothing
		}
		return defaultId;
	}

	/**
	 * Get a dummy user.
	 * @return the dummy user.
	 */
	protected User getDummyUser() {
		User u = getDomainObjectFactory().getUserInstance();
		u.setAccessLevel(new Integer(1));
		u.setCountry(Locale.getDefault().getCountry());
		u.setCreatedDate(Calendar.getInstance());
		u.setEmail("ieat.test@msqr.us");
		u.setLanguage(Locale.getDefault().getLanguage());
		u.setLogin(TEST_USER_LOGIN);
		u.setName("Test User");
		u.setPassword(TEST_USER_PASS);
		return u;
	}

	/**
	 * @return the UserBiz
	 */
	protected UserBiz getUserBiz() {
		return (UserBiz)getContext().getBean("userBiz");
	}

	/**
	 * @return the UserDao
	 */
	protected UserDao getUserDao() {
		return (UserDao)getContext().getBean("userDao");
	}
	
	/**
	 * @return a test Course
	 */
	protected Course getTestCourse() {
		return getCourseDao().getCourses().get(0);
	}

	/**
	 * @return the RecipeBiz
	 */
	protected RecipeBiz getRecipeBiz() {
		return (RecipeBiz)getContext().getBean("recipeBiz");
	}

	/**
	 * @return the RecipeSearchBiz
	 */
	protected RecipeSearchBiz getRecipeSearchBiz() {
		return (RecipeSearchBiz)getContext().getBean("recipeSearchBiz");
	}

	/**
	 * @return the RecipeSearchIndexBiz
	 */
	protected RecipeSearchIndexBiz getRecipeSearchIndexBiz() {
		return (RecipeSearchIndexBiz)getContext().getBean("recipeSearchIndexBiz");
	}
	
}