/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.struts.apps.mailreader.actions;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.action.DynaActionForm;
import org.apache.struts.apps.mailreader.Constants;
import org.apache.struts.apps.mailreader.dao.ExpiredPasswordException;
import org.apache.struts.apps.mailreader.dao.User;
import org.apache.struts.apps.mailreader.dao.UserDatabase;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.lang.reflect.InvocationTargetException;

/**
 * <p>
 * Provide an Edit method for retrieving an existing user,
 * and a Save method for updating or inserting a user.
 * </p><p>
 * Both methods utilize a RegistrationForm to obtain or expose User details.
 * If Save is used to create a user,
 * additional validations ensure input is nominal.
 * When a user is created,
 * Save also handles the initial logon.
 * </p>
 */
public final class RegistrationAction extends BaseAction {

    // --- Public Constants --

    /**
     * <p>
     * Name of fromAddress field ["fromAddress"].
     * </p>
     */
    public final static String FROM_ADDRESS = "fromAddress";

    /**
     * <p>
     * Name of fullName field ["fullName"].
     * </p>
     */
    public final static String FULL_NAME = "fullName";

    /**
     * <p>
     * Name of password confirmation field ["password2"].
     * </p>
     */
    public final static String PASSWORD2 = "password2";

    /**
     * <p>
     * Name of replyToAddress field ["replyToAddress"].
     * </p>
     */
    public final static String REPLY_TO_ADDRESS = "replyToAddress";

    // ---- Private Methods ----

    /**
     * <p>
     * The message prefix to use when populating a Registration Form.
     * </p>
     */
    final String LOG_REGISTRATION_POPULATE = "RegistrationForm.populate";

    /**
     * <p>
     * Helper method to post error message when user already exists.
     * </p>
     *
     * @param username Existing username
     * @param errors   Our ActionMessages collection
     */
    private void errorUsernameUnique(String username,
                                     ActionMessages errors) {
        errors.add(
                USERNAME,
                new org.apache.struts.action.ActionMessage(
                        "error.username.unique", username));
    }

    /**
     * <p>
     * Verify input for creating a new user,
     * create the user, and process the login.
     * </p>
     *
     * @param form    The input form
     * @param request The HttpRequest being served
     * @param errors  The ActionMessages collection for any errors
     * @return A new User and empty Errors if create succeeds,
     *         or null and Errors if create fails
     */
    private User doCreateUser(
            ActionForm form,
            HttpServletRequest request,
            ActionMessages errors) {

        if (log.isTraceEnabled()) {
            log.trace(" Perform additional validations on Create");
        }

        UserDatabase database = doGetUserDatabase();
        String username = doGet(form, USERNAME);
        try {
            if (database.findUser(username) != null) {
                errorUsernameUnique(username, errors);
            }
        }
        catch (ExpiredPasswordException e) {
            errorUsernameUnique(username, errors);
            errors.add("errors.literal", new ActionMessage(e.getMessage()));
        }

        String password = doGet(form, PASSWORD);
        if ((password == null) || (password.length() < 1)) {
            errors.add(PASSWORD, new ActionMessage("error.password.required"));

            String password2 = doGet(form, PASSWORD2);
            if ((password2 == null) || (password2.length() < 1)) {
                errors.add(
                        PASSWORD2,
                        new ActionMessage("error.password2.required"));
            }
        }

        if (!errors.isEmpty()) {
            return null;
        }

        User user = database.createUser(username);

        // Log the user in
        HttpSession session = request.getSession();
        session.setAttribute(Constants.USER_KEY, user);
        if (log.isTraceEnabled()) {
            log.trace(
                    " User: '"
                            + user.getUsername()
                            + "' logged on in session: "
                            + session.getId());
        }

        return user;
    }

    /**
     * <p>
     * Helper method to populate the input form from the User object.
     * </p>
     *
     * @param form Form with incoming values
     * @param user User object to populate
     * @throws ServletException On any error
     */
    private void doPopulate(ActionForm form, User user)
            throws ServletException {

        final String title = Constants.EDIT;

        if (log.isTraceEnabled()) {
            log.trace(Constants.LOG_POPULATE_FORM + user);
        }

        try {
            PropertyUtils.copyProperties(form, user);
            DynaActionForm dyna = (DynaActionForm) form;
            dyna.set(TASK, title);
            dyna.set(PASSWORD, null);
            dyna.set(PASSWORD2, null);
        } catch (InvocationTargetException e) {
            Throwable t = e.getTargetException();
            if (t == null) {
                t = e;
            }
            log.error(LOG_REGISTRATION_POPULATE, t);
            throw new ServletException(LOG_REGISTRATION_POPULATE, t);
        } catch (Throwable t) {
            log.error(LOG_REGISTRATION_POPULATE, t);
            throw new ServletException(LOG_REGISTRATION_POPULATE, t);
        }
    }

    /**
     * <p>
     * Helper method to populate the User object from the input form.
     * </p>
     *
     * @param user User object to populate
     * @param form Form with incoming values
     * @throws ServletException On any error
     */
    private void doPopulate(User user, ActionForm form)
            throws ServletException {

        if (log.isTraceEnabled()) {
            log.trace(Constants.LOG_POPULATE_USER + user);
        }

        try {
            String oldPassword = user.getPassword();
            PropertyUtils.copyProperties(user, form);
            String password = doGet(form, PASSWORD);
            if ((password == null)
                    || (password.length() < 1)) {

                user.setPassword(oldPassword);
            }

        } catch (InvocationTargetException e) {
            Throwable t = e.getTargetException();
            if (t == null) {
                t = e;
            }

            log.error(LOG_REGISTRATION_POPULATE, t);
            throw new ServletException(LOG_REGISTRATION_POPULATE, t);

        } catch (Throwable t) {
            log.error(LOG_REGISTRATION_POPULATE, t);
            throw new ServletException(LOG_REGISTRATION_POPULATE, t);
        }
    }

    /**
     * <p>
     * Validate and clear the transactional token,
     * creating logging statements as needed.
     * </p>
     *
     * @param request Our HttpServletRequest
     * @param errors  ActionErrors to transfer any messages
     */
    private void doValidateToken(HttpServletRequest request,
                                 ActionMessages errors) {

        if (log.isTraceEnabled()) {
            log.trace(Constants.LOG_TOKEN_CHECK);
        }

        if (!isTokenValid(request)) {
            errors.add(
                    ActionMessages.GLOBAL_MESSAGE,
                    new ActionMessage(Constants.MSG_TRANSACTION_TOKEN));
        }

        resetToken(request);
    }

    // ----- Public Methods ----

    /**
     * <p>
     * Retrieve the User object to edit or null if the User does not exist,
     * and set an transactional token to later detect multiple Save commands.
     * </p>
     *
     * @param mapping  Our ActionMapping
     * @param form     Our ActionForm
     * @param request  Our HttpServletRequest
     * @param response Our HttpServletResponse
     * @return The "Success" result for this mapping
     * @throws Exception on any error
     */
    public ActionForward Edit(
            ActionMapping mapping,
            ActionForm form,
            HttpServletRequest request,
            HttpServletResponse response)
            throws Exception {

        final String method = Constants.EDIT;
        doLogProcess(mapping, method);

        HttpSession session = request.getSession();
        User user = doGetUser(session);
        boolean updating = (user != null);
        if (updating) {
            doPopulate(form, user);
        }

        doSaveToken(request);
        return doFindSuccess(mapping);
    }

    /**
     * <p>
     * Insert or update a User object to the persistent store.
     * </p><p>
     * If a User is not logged in,
     * then a new User is created and automatically logged in.
     * Otherwise, the existing User is updated.
     * </p>
     *
     * @param mapping  Our ActionMapping
     * @param form     Our ActionForm
     * @param request  Our HttpServletRequest
     * @param response Our HttpServletResponse
     * @return The "Success" result for this mapping
     * @throws Exception on any error
     */
    public ActionForward Save(
            ActionMapping mapping,
            ActionForm form,
            HttpServletRequest request,
            HttpServletResponse response)
            throws Exception {

        final String method = Constants.SAVE;
        doLogProcess(mapping, method);

        HttpSession session = request.getSession();
        if (isCancelled(request)) {
            doCancel(session, method, Constants.SUBSCRIPTION_KEY);
            return doFindSuccess(mapping);
        }

        ActionMessages errors = new ActionMessages();
        doValidateToken(request, errors);

        if (!errors.isEmpty()) {
            return doInputForward(mapping, request, errors);
        }

        User user = doGetUser(session);
        if (user == null) {
            user = doCreateUser(form, request, errors);
            if (!errors.isEmpty()) {
                return doInputForward(mapping, request, errors);
            }
        }

        doPopulate(user, form);
        doSaveUser(user);

        return doFindSuccess(mapping);
    }

}
