/* ===================================================================
 * RecipeImportValidator.java
 * 
 * Created Feb 16, 2005 5:21:27 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: RecipeImportValidator.java 32 2009-05-04 03:34:21Z msqr $
 * ===================================================================
 */

package magoffin.matt.ieat.validation;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;

import magoffin.matt.ieat.biz.DomainObjectFactory;
import magoffin.matt.ieat.biz.RecipeIOBiz;
import magoffin.matt.ieat.biz.RecipeSearchBiz;
import magoffin.matt.ieat.dao.IngredientDao;
import magoffin.matt.ieat.dao.SystemDao;
import magoffin.matt.ieat.dao.UnitDao;
import magoffin.matt.ieat.domain.Ingredient;
import magoffin.matt.ieat.domain.Recipe;
import magoffin.matt.ieat.domain.RecipeIngredient;
import magoffin.matt.ieat.domain.System;
import magoffin.matt.ieat.domain.UiSearchResults;
import magoffin.matt.ieat.domain.Unit;
import magoffin.matt.ieat.util.ImportRecipeCommand;
import magoffin.matt.ieat.util.IngredientCriteriaImpl;
import magoffin.matt.util.StringUtil;

import org.springframework.orm.ObjectRetrievalFailureException;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;

/**
 * Validator for ImportRecipeCommand objects.
 * 
 * @author Matt Magoffin (spamsqr@msqr.us)
 * @version $Revision: 32 $ $Date: 2009-05-04 15:34:21 +1200 (Mon, 04 May 2009) $
 */
public class RecipeImportValidator extends RecipeValidator {
    
	/** The RecipeML type. */
	public static final int RECIPE_ML_TYPE = 1;
	
	/** The RecipeIOBiz instance for handling the data conversion. */
	private RecipeIOBiz rioBiz;
	
	/** The Ingredient DAO for getting Ingredient objects during validation. */
	private IngredientDao ingredientDao;
	
	/** The Unit DAO for getting Unit objects during validation. */
	private UnitDao unitDao;
	
	/** The System DAO for getting measurememtn systems. */
	private SystemDao systemDao;
	
	/** The search biz for finding ingredient and unit guesses. */
	private RecipeSearchBiz recipeSearchBiz;
	
	/** The DomainObjectFactory for creating new Recipe objects. */
	private DomainObjectFactory domainObjectFactory;
	
	/** The type of import this validator is handling. */
	private int importType = RECIPE_ML_TYPE;

	@Override
	public void validate(Object obj, Errors errors, int page) {
		ImportRecipeCommand irCommand = (ImportRecipeCommand)obj;
		
		if ( page == 0 && irCommand.getRecipe() == null ) {
		    validateNewImport(irCommand,errors);
		}
		
		if ( page == 1 ) {
			super.validate(irCommand.getRecipe(),errors,page);
			
			// check for empty category data
			Recipe r = irCommand.getRecipe();
			if ( r.getBase() != null && r.getBase().getBaseId() == null ) {
				r.setBase(null);
			}
			if ( r.getCourse() != null && r.getCourse().getCourseId() == null ) {
				r.setCourse(null);
			}
			if ( r.getDifficulty() != null && r.getDifficulty().getDifficultyId() == null ) {
				r.setDifficulty(null);
			}
			if ( r.getEthnicity() != null && r.getEthnicity().getEthnicityId() == null ) {
				r.setEthnicity(null);
			}
			if ( r.getPrepTime() != null && r.getPrepTime().getPrepTimeId() == null ) {
				r.setPrepTime(null);
			}
		}
	}
    
	@SuppressWarnings("unchecked")
	private void validateNewImport(ImportRecipeCommand irCommand, Errors errors) {
		// validate file was provided
		if ( irCommand.getTfile() == null ) {
		    errors.rejectValue("tfile", "error.required", null, "import.recipeml.file.displayName");
		}

		// import XML data
	    Recipe importRecipe = null;
		try {
			switch ( importType ) {
				case RECIPE_ML_TYPE:
				    importRecipe = rioBiz.createFromRecipeML(irCommand.getTfile().getInputStream());
				    break;
				    
				default:
				    throw new RuntimeException("Unknown import type specified: " +importType);
			}
		    
		    irCommand.setImportRecipe(importRecipe);
		} catch ( IOException e ) {
		    errors.reject("error.unknown",new String[] {e.toString()},"An IOException has occured.");
		    return;
		} catch ( Throwable t ) {
			errors.reject("error.import.recipe.recipeml","Unable to import recipe: " 
					+t.getCause().getMessage());
			return;
		}
        
	    Recipe recipe = (Recipe)domainObjectFactory.clone(importRecipe);
	    irCommand.setRecipe(recipe);
	    
	    // see if we can discover the imported unit system
	    if ( recipe.getSystem() != null && StringUtils.hasText(recipe.getSystem().getValue()) ) {
	    		String importedSystemValue = recipe.getSystem().getValue().trim().toLowerCase();
	    		for ( System sys : systemDao.getSystems() ) {
	    			String sysValue = sys.getValue().toLowerCase();
	    			if ( sysValue.indexOf(importedSystemValue) >= 0 || importedSystemValue.indexOf(sysValue) >= 0 ) {
	    				// take first match
	    				recipe.setSystem(sys);
	    				break;
	    			}
	    		}
	    }
	    
	    // validate ingredients
	   validateRecipeIngredients(recipe.getIngredient());
	}
    
	/**
	 * Method to validate imported recipe ingredients.
	 * 
	 * <p>Tries to locate a matching ingredient from the back end given an ingredient 
	 * name. If the RecipeIngredient's Ingredient has an <code>ingredientId</code>
	 * set, then tries to load that Ingredient by that ID.</p>
	 * 
	 * @param recipeIngredientList list of RecipeIngredient objects
	 */
	private void validateRecipeIngredients(List<RecipeIngredient> recipeIngredientList) {
		int riIdx = 0;
		for ( Iterator<RecipeIngredient> itr = recipeIngredientList.iterator(); itr.hasNext(); riIdx++ ) {
			RecipeIngredient ri = itr.next();
			if ( ri.getIngredient() != null ) {
				Ingredient ing = ri.getIngredient();
				if ( ing.getIngredientId() != null ) {
					// attempt to load ingredient from back end based from ID
					try {
					    ing = this.ingredientDao.get(ing.getIngredientId());
					    ri.setIngredient(ing);
					} catch ( ObjectRetrievalFailureException e ) {
					    // bummer, ignore and try to load via name
					    if ( this.logger.isDebugEnabled() ) {
					        this.logger.debug("Ingredient ID not found: " +e.getIdentifier());
					    }
					} catch ( Exception e ) {
						throw new RuntimeException("Unable to load ingredient",e);
					}
				}
				
				if ( ing.getIngredientId() == null ) {
					// try to load ingredient based on name, meaning so we'll do a search
					IngredientCriteriaImpl criteria = new IngredientCriteriaImpl(ing.getName());
					UiSearchResults results = recipeSearchBiz.findIngredientsSimilarlyNamed(criteria);
					if ( results.getTotalResults().intValue() > 0 ) {
						// take first result as our guess, assuming that is most relevant
						Ingredient guess = (Ingredient)results.getIngredient().get(0);
						if ( this.logger.isDebugEnabled() ) {
							logger.debug("Guessing ingredient '" +guess.getName() +"' ("
									+guess.getIngredientId() +") from '" +ing.getName() +"'");
						}
						ing = guess;
						ri.setIngredient(guess);
					}
				}
				
				// didn't find any ingredient guess :-( maybe we need a new ingredient
				if ( this.logger.isDebugEnabled() && ing.getIngredientId() == null ) {
					this.logger.debug("No guess for ingredient '" +ing.getName() +"'");
				}
			}
			
			// now handle unit

			if ( ri.getUnit() != null ) {
				Unit unit = ri.getUnit();
				if ( unit.getUnitId() != null ) {
					// attempt to load unit from back end based on ID
					Unit unitEntity = this.unitDao.get(unit.getUnitId());
					if ( unitEntity != null ) {
						ri.setUnit(unitEntity);
						unit = unitEntity;
					} else {
						unit.setUnitId(null);
					}
				}
				
				if ( unit.getUnitId() == null && StringUtil.trimToNull(unit.getValue()) != null ) {
					Unit guess = recipeSearchBiz.findUnitSimilarlyNamed(unit);
					if ( guess != null ) {
						if ( this.logger.isDebugEnabled() ) {
							logger.debug("Guessing unit '" +guess.getValue() +"' ("
									+guess.getUnitId() +") from '" +unit.getValue() +"'");
						}
						unit = guess;
						ri.setUnit(unit);
					}
				}
				
				// didn't find any unit guess
				if ( this.logger.isDebugEnabled() && unit.getUnitId() == null ) {
					this.logger.debug("No guess for unit '" +unit.getValue() +"'");
				}
			}
		}
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public boolean supports(Class clazz) {
	    return ImportRecipeCommand.class.isAssignableFrom(clazz);
	}
	
	/**
	 * @return the domainObjectFactory
	 */
	public DomainObjectFactory getDomainObjectFactory() {
		return domainObjectFactory;
	}
	
	/**
	 * @param domainObjectFactory the domainObjectFactory to set
	 */
	public void setDomainObjectFactory(DomainObjectFactory domainObjectFactory) {
		this.domainObjectFactory = domainObjectFactory;
	}
	
	/**
	 * @return the importType
	 */
	public int getImportType() {
		return importType;
	}
	
	/**
	 * @param importType the importType to set
	 */
	public void setImportType(int importType) {
		this.importType = importType;
	}
	
	/**
	 * @return the ingredientDao
	 */
	public IngredientDao getIngredientDao() {
		return ingredientDao;
	}
	
	/**
	 * @param ingredientDao the ingredientDao to set
	 */
	public void setIngredientDao(IngredientDao ingredientDao) {
		this.ingredientDao = ingredientDao;
	}
	
	/**
	 * @return the recipeSearchBiz
	 */
	public RecipeSearchBiz getRecipeSearchBiz() {
		return recipeSearchBiz;
	}
	
	/**
	 * @param recipeSearchBiz the recipeSearchBiz to set
	 */
	public void setRecipeSearchBiz(RecipeSearchBiz recipeSearchBiz) {
		this.recipeSearchBiz = recipeSearchBiz;
	}
	
	/**
	 * @return the rioBiz
	 */
	public RecipeIOBiz getRioBiz() {
		return rioBiz;
	}
	
	/**
	 * @param rioBiz the rioBiz to set
	 */
	public void setRioBiz(RecipeIOBiz rioBiz) {
		this.rioBiz = rioBiz;
	}
	
	/**
	 * @return the systemDao
	 */
	public SystemDao getSystemDao() {
		return systemDao;
	}
	
	/**
	 * @param systemDao the systemDao to set
	 */
	public void setSystemDao(SystemDao systemDao) {
		this.systemDao = systemDao;
	}
	
	/**
	 * @return the unitDao
	 */
	public UnitDao getUnitDao() {
		return unitDao;
	}
	
	/**
	 * @param unitDao the unitDao to set
	 */
	public void setUnitDao(UnitDao unitDao) {
		this.unitDao = unitDao;
	}
	
}
