package it.mice.voila.runtime.springmvc.controller;

import it.mice.voila.runtime.security.DefaultDaoAuthenticationProvider;
import it.mice.voila.runtime.security.UserDetailsJdbcDao;
import it.mice.voila.runtime.springmvc.form.LoginForm;
import it.mice.voila.runtime.springmvc.taglibs.MenuTag;
import it.mice.voila.runtime.springmvc.util.SpringMvcUtil;
import it.mice.voila.runtime.web.menu.MenuDefinitionFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.util.HtmlUtils;

/**
 * Default spring mvc controller for the user login to spring security.
 * The controller maps the default request to /login. Please extends this class to change its behaviour.
 */
@Controller
@RequestMapping("/login*")
public class LoginController extends DefaultFormController<LoginForm> {
	/**
	 * Default logger.
	 */
	private static Log logger = LogFactory.getLog(LoginController.class);

	public static final String DISABLE_JAVASCRIPT = "disableJavascript";
	
	@Autowired(required=false)
	private DefaultDaoAuthenticationProvider daoAuthenticationProvider;

	public DefaultDaoAuthenticationProvider getDaoAuthenticationProvider() {
		return daoAuthenticationProvider;
	}

	public void setDaoAuthenticationProvider(
			DefaultDaoAuthenticationProvider daoAuthenticationProvider) {
		this.daoAuthenticationProvider = daoAuthenticationProvider;
	}

	/**
	 * Default action activated when the login was requested.
	 * @param loginForm login form
	 * @param model the current spring mvc model
	 * @return the next tiles view; normally the login page.
	 */
	@RequestMapping(method=RequestMethod.GET)
	public String initAction(LoginForm loginForm, Model model) {
		String loginError = getRequest().getParameter("login_error");
		SpringMvcUtil.cleanupSessionAttrsStartingWith(MenuTag.SAVED_MENU_DEFINITION);
		if ("1".equals(loginError)) {
			loginForm.setJ_username(HtmlUtils.htmlUnescape((String) getSession().getAttribute(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_LAST_USERNAME_KEY)));
			String message = ((AuthenticationException)getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION)).getMessage();
			logger.warn("Login error:" + message);
			addErrorMessage("message.accessDeniedParam", message);
		}

		return finalizeModelAndView("loginPage", model, loginForm);
	}

	/**
	 * gestisce la conferma successiva all'inserimento di utenza e password.
	 * @param loginForm login form
	 * @param errorResult error binding results
	 * @param model spring mvc model
	 * @return redirect to the spring authentication security check providers.
	 */
	@RequestMapping(value = "confirm", method = RequestMethod.POST)
	public String confirm(LoginForm loginForm, BindingResult errorResult, Model model) {
		String password = "";
		if (loginForm.getJ_password() != null) {
			password = loginForm.getJ_password();
		}
		getSession().setAttribute(MenuDefinitionFactory.SELECTED_MENU_ID, null);
		// TODO Trovare un altro sistema per fare il posting senza
		// passare in chiaro userid e password.
		getSession().removeAttribute(getFormName());
		getSession().setAttribute(DISABLE_JAVASCRIPT, loginForm.getDisableJavascript());
		
		String url = "/j_spring_security_check?j_username=" + loginForm.getJ_username() + "&j_password=" + password;
		return redirectTo(url);
	}

	/**
	 * gestisce la conferma successiva all'inserimento di utenza e password.
	 * @param loginForm login form
	 * @param errorResult error binding results
	 * @param model spring mvc model
	 * @return redirect to the spring authentication security check providers.
	 */
	@RequestMapping(value = "changePassword")
	public String changePassword(LoginForm loginForm, BindingResult errorResult, Model model) {
		loginForm.setChangePassword(true);
		return finalizeModelAndView("loginPage", model, loginForm);
	}

	/**
	 * gestisce la conferma successiva all'inserimento di utenza e password.
	 * @param loginForm login form
	 * @param errorResult error binding results
	 * @param model spring mvc model
	 * @return redirect to the spring authentication security check providers.
	 */
	@RequestMapping(value = "doChangePassword", method = RequestMethod.POST)
	public String doChangePassword(LoginForm loginForm, BindingResult errorResult, Model model) {
		loginForm.setChangePassword(true);
		if (loginForm.getNewPassword() == null) {
			loginForm.setNewPassword("");
		}
		if (loginForm.getNewPassword2() == null) {
			loginForm.setNewPassword2("");
		}
		if (!checkLoginParameter(loginForm)) {
			addErrorMessage("message.accessDenied");
		} else {
			if (loginForm.getNewPassword().equals(loginForm.getNewPassword2())) {
				if (loginForm.getNewPassword().equals(loginForm.getJ_password())) {
					addErrorMessage("message.newPasswordEqualToOldOne");
				} else {
					if (updateNewPassword(loginForm)) {
						addInfoMessage("message.newPasswordUpdateOK");
						if (loginForm.getNewPassword().equals("")) {
							addWarningMessage("message.newPasswordIsBlank");
						}
						loginForm.setChangePassword(false);
						loginForm.setJ_password(null);
						loginForm.setNewPassword(null);
						loginForm.setNewPassword2(null);
					} else {
						addErrorMessage("message.newPasswordUpdateFail");
					}
				}
			} else {
				addErrorMessage("message.newPasswordConfirmMismatch");
			}
		}
		return finalizeModelAndView("loginPage", model, loginForm);
	}
	/**
	 * Effettua il controllo di tutti i parametri di login.
	 * 
	 * @param ctx
	 *            di tipo {@link it.skyit.jf.actions.JfActionContext} - Rappresenta il
	 *            context dell'azione, che racchiude gli elementi critici per la gestione
	 *            dell'oggetto request,response, actionMessages.
	 * @return true se tutto ok, altrimenti false.
	 */
	private boolean checkLoginParameter(LoginForm loginForm) {
		try {
			UserDetails userDetail = getDaoAuthenticationProvider().getUserDetailsService().loadUserByUsername(loginForm.getJ_username());
			if (userDetail != null) {
				String encPassword = userDetail.getPassword();
				if (encPassword == null) {
					encPassword = "";
				}
				String rawPassword = loginForm.getJ_password();
				if (rawPassword == null) {
					rawPassword = "";
				}
				boolean passwordCheck = getDaoAuthenticationProvider().getPasswordEncoder().isPasswordValid(encPassword, rawPassword,
								getDaoAuthenticationProvider().getSaltSource());
				return passwordCheck;
			}
			return false;
		} catch (UsernameNotFoundException e) {
			return false;
		}
	}

	/**
	 * Effettua l'aggiornamento della password in base dati.
	 * 
	 * @param ctx
	 *            di tipo {@link it.skyit.jf.actions.JfActionContext} - Rappresenta il
	 *            context dell'azione, che racchiude gli elementi critici per la gestione
	 *            dell'oggetto request,response, actionMessages.
	 * @return true se aggiornamento password ok, altrimenti false.
	 */
	private boolean updateNewPassword(LoginForm loginForm) {
		UserDetailsJdbcDao userDetailsJdbcDao = (UserDetailsJdbcDao) getDaoAuthenticationProvider()
				.getUserDetailsService();
		return userDetailsJdbcDao.updateNewPassword(loginForm.getJ_username(), loginForm
				.getNewPassword(), getDaoAuthenticationProvider());
	}
}
