/* ===================================================================
 * SetupWizard.java
 * 
 * Created Aug 16, 2005 10:33:29 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: SetupWizard.java 89 2009-05-19 01:35:24Z msqr $
 * ===================================================================
 */

package magoffin.matt.ieat.web.setup;

import java.io.File;
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.AuthorizationException;
import magoffin.matt.ieat.EatValidationException;
import magoffin.matt.ieat.biz.DomainObjectFactory;
import magoffin.matt.ieat.biz.UserBiz;
import magoffin.matt.ieat.domain.User;
import magoffin.matt.ieat.validation.AbstractPagedValidator;
import magoffin.matt.ieat.validation.PagedValidator;
import magoffin.matt.ieat.web.WebConstants;
import magoffin.matt.ieat.web.WebUtil;
import magoffin.matt.xweb.XwebParameter;
import magoffin.matt.xweb.util.AppContextSupport;
import magoffin.matt.xweb.util.XwebParamDao;

import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.validation.ObjectError;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractWizardFormController;

/**
 * Wizard for configuring iEat options, when running for the first time.
 * 
 * @author Matt Magoffin (spamsqr@msqr.us)
 * @version $Revision: 89 $ $Date: 2009-05-19 13:35:24 +1200 (Tue, 19 May 2009) $
 */
public class SetupWizard extends AbstractWizardFormController {
	
	private static final Integer ADMIN_ACCESS_LEVEL = new Integer(1);
	
	private XwebParamDao parameterDao;
	private DomainObjectFactory domainObjectFactory;
    private String successView;
    private boolean requireAdmin = false;
    private UserBiz userBiz;
    private int adminUserPage = 5;
    private String originalHibernateDialect;
    private Map<String, String> defaultSettings = new LinkedHashMap<String, String>();
    
    /** Command object for SetupWizard. */
	public static class SetupCommand {
		private Map<String, String> settings = new LinkedHashMap<String, String>();
		private User user = null;
		private boolean adminExists = false;
		private boolean skipAdminUser = false;
		private boolean changedHibernateDialect = false;
		
		/**
		 * @return the adminExists
		 */
		public boolean isAdminExists() {
			return adminExists;
		}
		
		/**
		 * @param adminExists the adminExists to set
		 */
		public void setAdminExists(boolean adminExists) {
			this.adminExists = adminExists;
		}
		
		/**
		 * @return the changedHibernateDialect
		 */
		public boolean isChangedHibernateDialect() {
			return changedHibernateDialect;
		}
		
		/**
		 * @param changedHibernateDialect the changedHibernateDialect to set
		 */
		public void setChangedHibernateDialect(boolean changedHibernateDialect) {
			this.changedHibernateDialect = changedHibernateDialect;
		}
		
		/**
		 * @return the settings
		 */
		public Map<String, String> getSettings() {
			return settings;
		}
		
		/**
		 * @param settings the settings to set
		 */
		public void setSettings(Map<String, String> settings) {
			this.settings = settings;
		}
		
		/**
		 * @return the skipAdminUser
		 */
		public boolean isSkipAdminUser() {
			return skipAdminUser;
		}
		
		/**
		 * @param skipAdminUser the skipAdminUser to set
		 */
		public void setSkipAdminUser(boolean skipAdminUser) {
			this.skipAdminUser = skipAdminUser;
		}
		
		/**
		 * @return the user
		 */
		public User getUser() {
			return user;
		}
		
		/**
		 * @param user the user to set
		 */
		public void setUser(User user) {
			this.user = user;
		}
		
	}
	
	/** Validator class for SetupCommand. */
	public static class SetupCommandValidator extends AbstractPagedValidator {

		private int featurePage = 1;
	    private int fileSystemPage = 3;
	    
		/* (non-Javadoc)
		 * @see magoffin.matt.ieat.validation.PagedValidator#validate(java.lang.Object, org.springframework.validation.Errors, int)
		 */
		public void validate(Object obj, Errors errors, int page) {
			SetupCommand command = (SetupCommand)obj;
			
			if ( page == featurePage ) {
				// verify for all feature settings that NULL replaced by FALSE
				for ( Map.Entry<String, String> me : command.settings.entrySet() ) {
					if ( me.getKey().startsWith("feature.") && me.getValue() == null ) {
						me.setValue(Boolean.FALSE.toString());
					}
				}
			} else if ( page == fileSystemPage ) {
				// verify index directory
				String indexPath = command.getSettings().get("lucene.index.base.path");
				File indexDir = new File(indexPath);
				if ( !(indexDir.exists() || indexDir.mkdirs()) || 
						!(indexDir.isDirectory() && indexDir.canWrite() ) ) {
					errors.rejectValue("settings", "setup.fs.index.dir.error", null, "Unable to verify index directory.");
				}
				
				
				// verify if image feature enabled that have write access to directory specifed
				if ( "true".equals(command.getSettings().get("feature.recipeimages"))  ) {
					String path = command.getSettings().get("recipe.image.base.path");
					File imagePath = new File(path);
					if ( (!imagePath.exists() && !imagePath.mkdirs()) || 
							!(imagePath.isDirectory() && imagePath.canWrite() ) ) {
						errors.rejectValue("settings", "setup.fs.image.dir.error", null, "Unable to verify image directory.");
					}
				}
			}
		}

		/* (non-Javadoc)
		 * @see org.springframework.validation.Validator#supports(java.lang.Class)
		 */
		@SuppressWarnings("unchecked")
		public boolean supports(Class clazz) {
			return SetupCommand.class.isAssignableFrom(clazz);
		}

		/**
		 * @return the fileSystemPage
		 */
		public int getFileSystemPage() {
			return fileSystemPage;
		}
		
		/**
		 * @param fileSystemPage the fileSystemPage to set
		 */
		public void setFileSystemPage(int fileSystemPage) {
			this.fileSystemPage = fileSystemPage;
		}

		/**
		 * @return the featurePage
		 */
		public int getFeaturePage() {
			return featurePage;
		}

		/**
		 * @param featurePage the featurePage to set
		 */
		public void setFeaturePage(int featurePage) {
			this.featurePage = featurePage;
		}

	}

	@SuppressWarnings("unchecked")
	@Override
	protected Object formBackingObject(HttpServletRequest request) throws Exception {
		
		if ( requireAdmin ) {
			WebUtil.getAdminBizContext(request,userBiz);
		}
		
		SetupCommand command = (SetupCommand) super.formBackingObject(request);
		command.user = domainObjectFactory.getUserInstance();
		
		// copy defaults properties into settings
		command.settings.putAll(this.defaultSettings);
		
		AppContextSupport appContextSupport = WebUtil.getAppContextSupport(request);
		
		// copy environment properties into settings to use as defaults
		for ( XwebParameter param : (List<XwebParameter>)appContextSupport.getAppContext().getParam() ) {
			command.settings.put(param.getKey(),param.getValue());
		}
		
		// check if there are any admin users already
		if ( appContextSupport.isParameterTrue(WebConstants.SETTING_KEY_ADMIN_USER_EXISTS) ) {
			command.adminExists = true;
		}
		
		return command;
	}
	
	@Override
	protected int getTargetPage(HttpServletRequest request, Object command,
			Errors errors, int currentPage) {
		int targetPage = getTargetPage(request,currentPage);
		if ( targetPage == adminUserPage ) {
			SetupCommand setup = (SetupCommand)command;
			if ( !originalHibernateDialect.equals(setup.getSettings().get("hibernate.dialect")) ) {
				// can't create admin user now since switched dialects...
				setup.changedHibernateDialect = true;
				return targetPage +1;
			}
		}
		return targetPage;
	}

	@SuppressWarnings("unchecked")
	@Override
	protected ModelAndView processFinish(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws Exception {
		SetupCommand setup = (SetupCommand)command;
		for ( Iterator<Map.Entry<String, String>> itr = 
				setup.getSettings().entrySet().iterator(); itr.hasNext(); ) {
			Map.Entry<String, String> me = itr.next();
			String key = me.getKey();
			XwebParameter setting = this.domainObjectFactory.newXwebParameterInstance();
			setting.setKey(key);
			setting.setValue(me.getValue());
			this.parameterDao.updateParameter(setting);
		}
		
		if ( !(setup.skipAdminUser || setup.changedHibernateDialect) ) {
			try {
				// give user admin level access
				setup.user.setAccessLevel(ADMIN_ACCESS_LEVEL);
				
				setup.user = userBiz.storeUser(setup.user,null); // no context to avoid security check
			} catch ( AuthorizationException e ) {
				switch ( e.getReason() ) {
					case AuthorizationException.DUPLICATE_LOGIN:
						errors.rejectValue("user.login", "user.error.login.taken", 
								new Object[] {setup.user.getLogin()}, "login.displayName");
						return showPage(request,errors,this.adminUserPage);
					
					case AuthorizationException.DUPLICATE_EMAIL:
						errors.rejectValue("user.email", "user.error.email.taken", 
								new Object[] {setup.user.getEmail()}, "email.displayName");
						return showPage(request,errors,this.adminUserPage);
				}
			} 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,this.adminUserPage);
			}
		}
		
		if ( setup.changedHibernateDialect ) {
			// add "we're NOT setup" into db
			XwebParameter setting = this.domainObjectFactory.newXwebParameterInstance();
			setting.setKey(WebConstants.SETTING_KEY_SETUP_COMPLETE);
			setting.setValue(Boolean.FALSE.toString());
			this.parameterDao.updateParameter(setting);
			
			// add the "do NOT require admin for setup" key into the database
			setting = this.domainObjectFactory.newXwebParameterInstance();
			setting.setKey(WebConstants.SETTING_KEY_SETUP_REQUIRE_ADMIN);
			setting.setValue(Boolean.FALSE.toString());
			this.parameterDao.updateParameter(setting);
		} else {
			// add the "we're setup" key into the database
			XwebParameter setting = this.domainObjectFactory.newXwebParameterInstance();
			setting.setKey(WebConstants.SETTING_KEY_SETUP_COMPLETE);
			setting.setValue(Boolean.TRUE.toString());
			this.parameterDao.updateParameter(setting);
			
			// add the "require admin for setup" key into the database
			setting = this.domainObjectFactory.newXwebParameterInstance();
			setting.setKey(WebConstants.SETTING_KEY_SETUP_REQUIRE_ADMIN);
			setting.setValue(Boolean.TRUE.toString());
			this.parameterDao.updateParameter(setting);
		}
		
		Map<String, Object> model = new LinkedHashMap<String, Object>();
		model.put(getCommandName(),setup);
		return new ModelAndView(getSuccessView(),model);
	}

	@Override
	protected void validatePage(Object command, Errors errors, int page) {
		PagedValidator pv = (PagedValidator)getValidator();
		pv.validate(command,errors,page);
	}
	
	/**
	 * @return the adminUserPage
	 */
	public int getAdminUserPage() {
		return adminUserPage;
	}
	
	/**
	 * @param adminUserPage the adminUserPage to set
	 */
	public void setAdminUserPage(int adminUserPage) {
		this.adminUserPage = adminUserPage;
	}
	
	/**
	 * @return the domainObjectFactory
	 */
	public DomainObjectFactory getDomainObjectFactory() {
		return domainObjectFactory;
	}
	
	/**
	 * @param domainObjectFactory the domainObjectFactory to set
	 */
	public void setDomainObjectFactory(DomainObjectFactory domainObjectFactory) {
		this.domainObjectFactory = domainObjectFactory;
	}
	
	/**
	 * @return the originalHibernateDialect
	 */
	public String getOriginalHibernateDialect() {
		return originalHibernateDialect;
	}
	
	/**
	 * @param originalHibernateDialect the originalHibernateDialect to set
	 */
	public void setOriginalHibernateDialect(String originalHibernateDialect) {
		this.originalHibernateDialect = originalHibernateDialect;
	}
	
	/**
	 * @return the parameterDao
	 */
	public XwebParamDao getParameterDao() {
		return parameterDao;
	}
	
	/**
	 * @param parameterDao the parameterDao to set
	 */
	public void setParameterDao(XwebParamDao parameterDao) {
		this.parameterDao = parameterDao;
	}
	
	/**
	 * @return the requireAdmin
	 */
	public boolean isRequireAdmin() {
		return requireAdmin;
	}
	
	/**
	 * @param requireAdmin the requireAdmin to set
	 */
	public void setRequireAdmin(boolean requireAdmin) {
		this.requireAdmin = requireAdmin;
	}
	
	/**
	 * @return the successView
	 */
	public String getSuccessView() {
		return successView;
	}
	
	/**
	 * @param successView the successView to set
	 */
	public void setSuccessView(String successView) {
		this.successView = successView;
	}
	
	/**
	 * @return the userBiz
	 */
	public UserBiz getUserBiz() {
		return userBiz;
	}
	
	/**
	 * @param userBiz the userBiz to set
	 */
	public void setUserBiz(UserBiz userBiz) {
		this.userBiz = userBiz;
	}

	/**
	 * @return the defaultSettings
	 */
	public Map<String, String> getDefaultSettings() {
		return defaultSettings;
	}

	/**
	 * @param defaultSettings the defaultSettings to set
	 */
	public void setDefaultSettings(Map<String, String> defaultSettings) {
		this.defaultSettings = defaultSettings;
	}
	
}
