/* ===================================================================
 * AddRecipeForm.java
 * 
 * Created Aug 9, 2004 1:15:28 PM
 * 
 * Copyright (c) 2004 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: AddRecipeForm.java 79 2009-05-18 00:18:19Z msqr $
 * ===================================================================
 */

package magoffin.matt.ieat.web;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import magoffin.matt.ieat.EatValidationException;
import magoffin.matt.ieat.biz.BizContext;
import magoffin.matt.ieat.domain.Recipe;
import magoffin.matt.ieat.domain.RecipeIngredient;
import magoffin.matt.ieat.domain.UiData;
import magoffin.matt.ieat.domain.UiEdit;
import magoffin.matt.ieat.validation.PagedValidator;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.validation.ObjectError;
import org.springframework.web.servlet.ModelAndView;

/**
 * Controller for maintaining a Recipe.
 * 
 * <p>The configurable fields of this class are:</p>
 * 
 * <dl class="class-properties">
 *   <dt>displayCacheMessage</dt>
 *   <dd>If set to <em>true</em> (the default) then make the 
 *   <code>edit-recipe.cache.warning</code> message available to 
 *   the resulting view page after storing the recipe is complete.</dd>
 *   
 *   <dt>recipeModelPath</dt>
 *   <dd>The JavaBean path to the {@link Recipe} model object instance being edited 
 *   within the form command object. This property exists so extending classes can
 *   build on this form's functionality (and use a differnent command object).
 *   Defaults to {@code recipe}.</dd>
 * </dl>
 * 
 * @author Matt Magoffin (spamsqr@msqr.us)
 * @version $Revision: 79 $ $Date: 2009-05-18 12:18:19 +1200 (Mon, 18 May 2009) $
 */
public class AddRecipeForm extends AbstractEatWizardForm {
	
	/** 
	 * Flag to indicate if we should add a message to the finish 
	 * view that the recipe may not show up due to caching.
	 */
	private boolean displayCacheMessage = true;
	private String recipeModelPath = "recipe";

	@Override
	protected ModelAndView processCancel(HttpServletRequest request,
			HttpServletResponse response, Object command, BindException errors)
			throws Exception 
	{
		UiEdit edit = (UiEdit)command;
		Map<String, Object> model = new LinkedHashMap<String, Object>();
		WebUtil.saveRecipeEdit(model,edit);
		return new ModelAndView(getCancelView(),model);
	}

	@Override
	@SuppressWarnings("unchecked")
	protected Map referenceData(HttpServletRequest request, Object command,
			Errors errors, int page) throws Exception 
	{
		Map<String, Object> m =  new LinkedHashMap<String, Object>();
		UiData ui = getDomainObjectFactory().getUiDataInstance();
		m.put("uidata",ui);

		List l = null;
		
		l = recipeBiz.getAvailableBases();
		ui.getBase().addAll(l);
		
		l = recipeBiz.getAvailableCourses();
		ui.getCourse().addAll(l);
		
		l = recipeBiz.getAvailableDifficulties();
		ui.getDifficulty().addAll(l);
		
		l = recipeBiz.getAvailableEthnicities();
		ui.getEthnicity().addAll(l);
		
		l = recipeBiz.getAvailablePrepTimes();
		ui.getPrepTime().addAll(l);
		
		l = recipeBiz.getAvailableUnitSystems();
		ui.getSystem().addAll(l);
		
		Recipe r = ((UiEdit)command).getRecipe();
		
		l = recipeBiz.getAvailableUnitsForSystem(r.getSystem());
		ui.getUnit().addAll(l);
		
		// look for any recipe ingredients that must be added
		for ( Iterator itr = r.getIngredient().iterator(); itr.hasNext(); ) {
			RecipeIngredient ri = (RecipeIngredient)itr.next();
			if ( ri.getRecipe() != null && ri.getRecipe().getRecipeId() != null && ri.getRecipe().getRecipeId().intValue() > 0 ) {
				Recipe relatedRecipe = recipeBiz.getRecipeById(ri.getRecipe().getRecipeId());
				ui.getRecipe().add(relatedRecipe);
			}
		}
		
		return m;
	}
	
	@Override
	protected Object formBackingObject(HttpServletRequest request)
			throws Exception 
	{
		WebUtil.getBizContext(request, true);
		return createEditForm();
	}
	
	/**
	 * Create the edit form object to use during the life of this form.
	 * 
	 * <p>This code is factored out into its own method to support extending
	 * classes that might need to customize this form's behavior.</p>
	 * @return new UiEdit instance initialized with default empty objects
	 */
	protected UiEdit createEditForm() {
		UiEdit edit = getDomainObjectFactory().getUiEditInstance();
		Recipe recipe = getDomainObjectFactory().getRecipeInstance();
		recipe.setBase(getDomainObjectFactory().getBaseInstance());
		recipe.setCourse(getDomainObjectFactory().getCourseInstance());
		recipe.setDifficulty(getDomainObjectFactory().getDifficultyInstance());
		recipe.setEthnicity(getDomainObjectFactory().getEthnicityInstance());
		recipe.setPrepTime(getDomainObjectFactory().getPrepTimeInstance());
		recipe.setSystem(getDomainObjectFactory().getSystemInstance());
		edit.setRecipe(recipe);
		return edit;
	}
	
	@SuppressWarnings("unchecked")
	@Override
	protected Object currentFormObject(HttpServletRequest request, Object sessionFormObject) throws Exception {
		int page = getCurrentPage(request);
		if ( page == getFormPageNumber() ) {
			// clear out any assigned ingredients as form will only submit one of recipe/ingredient, not both
			Recipe r = getRecipeModelObject(sessionFormObject);
			for ( RecipeIngredient ri : (List<RecipeIngredient>)r.getIngredient() ) {
				if ( ri.getRecipe() != null ) {
					ri.setRecipe(null);
				}
				if ( ri.getIngredient() != null ) {
					ri.setIngredient(null);
				}
			}
		}
		return sessionFormObject;
	}
	
	/**
	 * Get the page with the recipe form on it.
	 * 
	 * <p>This method exists so extending classes can change.</p>
	 * 
	 * @return the form page number
	 */
	protected int getFormPageNumber() {
		return 1; // could make this a class property if needed
	}

	@SuppressWarnings("unchecked")
	@Override
	protected ModelAndView processFinish(HttpServletRequest request,
			HttpServletResponse response, Object command, BindException errors)
			throws Exception 
	{
		// store recipe in backend!
		UiEdit edit = (UiEdit)command;
		Recipe r = edit.getRecipe();
		ModelAndView result = processFinish(request,response,command,errors,r);
		WebUtil.saveRecipeEdit(result.getModel(), edit);
		return result;
	}
	
	/**
	 * Store the recipe in the back end.
	 * 
	 * <p>This method is factored out so extending classes can customize the 
	 * default behavior easily.</p>
	 * 
	 * @param request the request
	 * @param response the response
	 * @param command the command object
	 * @param errors the errors
	 * @param recipe the recipe to store
	 * @return the ModelAndView result
	 * @throws Exception if an error occurs
	 */
	@SuppressWarnings("unchecked")
	protected ModelAndView processFinish(HttpServletRequest request,
			HttpServletResponse response, Object command, BindException errors, Recipe recipe)
	throws Exception 
	{
		BizContext context = WebUtil.getBizContext(request, true);
		try {
			recipe = recipeBiz.storeRecipe(recipe,context);
		} catch ( EatValidationException e ) {
			Errors err = e.getErrors();
			for ( ObjectError oe : (List<ObjectError>)err.getAllErrors() ) {
				// FIXME this is terrible
				errors.rejectValue(oe.getObjectName(),oe.getCode(),oe.getArguments(),oe.getDefaultMessage());
			}
			return showPage(request,errors,getCurrentPage(request)-1);
		}
		
		Map<String, Object> model = new LinkedHashMap<String, Object>();
		
		// save recipe ID to model
		model.put(WebConstants.REQ_PARAM_RECIPE_ID,recipe.getRecipeId());
		
		// and save warning message
		saveCacheMessage(request);
		
		return new ModelAndView(getSuccessView(),model);
	}
	
	/**
	 * Save the recipe index cache warning message for the result view.
	 * 
	 * <p>Call this method at the completing of adding or saving a recipe. 
	 * If the <code>displayCacheMessage</code> field is <em>true</em> then 
	 * the <code>edit-recipe.cache.warning</code> message will be saved 
	 * to session, for subsequent display on the resulting view page. 
	 * It is assumed the result view page is a redirect view; thus the 
	 * message is saved in session.</p>
	 * 
	 * @param request the current request
	 */
	protected void saveCacheMessage(HttpServletRequest request) {
		if ( displayCacheMessage ) {
			ObjectError error = new ObjectError(null,
					new String[] {"edit-recipe.cache.warning"},null,
					"Recipe may not appear in index due to caching.");
			WebUtil.saveError(request, error);
		}
	}
	
	@Override
	protected void validatePage(Object command, Errors errors, int page) {
		PagedValidator pv = (PagedValidator)getValidator();
		Recipe r = getRecipeModelObject(command);
		pv.validate(r,errors,page);
	}
	
	/**
	 * Get the Recipe model object being edited by this form.
	 * 
	 * @param command the command object
	 * @return Recipe instance, or a RuntimeException if not found
	 */
	protected Recipe getRecipeModelObject(Object command) {
		BeanWrapper bean = new BeanWrapperImpl(command);
		Object o = bean.getPropertyValue(this.recipeModelPath);
		if ( !(o instanceof Recipe) ) {
			throw new RuntimeException("Recipe instance not found at path [" 
					+this.recipeModelPath +"]: " +o);
		}
		return (Recipe)o;
	}

	/**
	 * @return the displayCacheMessage
	 */
	public boolean isDisplayCacheMessage() {
		return displayCacheMessage;
	}

	/**
	 * @param displayCacheMessage the displayCacheMessage to set
	 */
	public void setDisplayCacheMessage(boolean displayCacheMessage) {
		this.displayCacheMessage = displayCacheMessage;
	}

	/**
	 * @return the recipeModelPath
	 */
	public String getRecipeModelPath() {
		return recipeModelPath;
	}

	/**
	 * @param recipeModelPath the recipeModelPath to set
	 */
	public void setRecipeModelPath(String recipeModelPath) {
		this.recipeModelPath = recipeModelPath;
	}
	
}
