/**
 * Copyright (c) 2009--2014 Red Hat, Inc.
 *
 * This software is licensed to you under the GNU General Public License,
 * version 2 (GPLv2). There is NO WARRANTY for this software, express or
 * implied, including the implied warranties of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
 * along with this software; if not, see
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
 *
 * Red Hat trademarks are not licensed under GPLv2. No permission is
 * granted to use or replicate Red Hat trademarks that are incorporated
 * in this software or its documentation.
 */
package com.redhat.rhn.frontend.struts;

import com.redhat.rhn.common.util.Asserts;
import com.redhat.rhn.common.util.DatePicker;
import com.redhat.rhn.common.util.ServletUtils;
import com.redhat.rhn.common.validator.ValidationMessage;
import com.redhat.rhn.common.validator.ValidatorError;
import com.redhat.rhn.common.validator.ValidatorResult;
import com.redhat.rhn.common.validator.ValidatorWarning;
import com.redhat.rhn.frontend.context.Context;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.struts.Globals;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.action.DynaActionForm;
import org.apache.struts.upload.FormFile;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * StrutsDelegate defines a set of helper operations for working with the Struts API.
 * Implementations should conform to the same design as servlets an actions classes
 * in that they should be thread-safe and should not maintain client state.
 *
 * @version $Rev$
 */
public class StrutsDelegate {

    private static final Logger  LOG = Logger.getLogger(StrutsDelegate.class);
    private static final StrutsDelegate INSTANCE = new StrutsDelegate();

    /**
     * Retuns an instance of the struts delegate factory
     * @return an instance
     */
    public static StrutsDelegate getInstance() {
        return INSTANCE;
    }

    protected StrutsDelegate() {
    }

    /**
     * Take an action forward and toss on a form variable.
     * @param base Base ActionForward
     *
     * @param param Parameter to be added to the ActionForward url.
     *
     * @param value Value of parameter to be added.
     *
     * @return a new ActionForward with the path of the base appended with the
     * param and value.
     */
    public ActionForward forwardParam(ActionForward base, String param, String value) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put(param, value);
        return forwardParams(base, params);
    }

    /**
     * Take an action forward and toss on a set of form variables.
     *
     * @param base Base ActionForward
     *
     * @param params Parameters to be added to the ActionForward url.
     *
     * @return a new ActionForward with the path of the base appended with the
     * param and value.
     */
    public ActionForward forwardParams(ActionForward base, Map params) {
        Asserts.assertNotNull(base, "base");
        String newPath = ServletUtils.pathWithParams(base.getPath(), params);

        ActionForward af = new ActionForward(newPath, base.getRedirect());
        af.setName(base.getName());
        return af;
    }

    /**
     * Add a message to an existing set of ActionErrors. Useful to add stuff to
     * an already populated ActionErrors instance
     * @param msgKey to add
     * @param errors to add too
     */
    // TODO Write unit tests for addError(String, ActionErrors)
    public void addError(String msgKey, ActionErrors errors) {
        addError(errors, msgKey, new Object[0]);
    }

    /**
     * Add a message to an existing set of ActionErrors. Useful to add stuff to
     * an already populated ActionErrors instance
     * @param errors to add too
     * @param msgKey to add
     * @param params key params
     */
    // TODO Write unit tests for addError(String, ActionErrors)
    public void addError(ActionErrors errors, String msgKey, Object...params) {
        ActionMessages msg = new ActionMessages();
        msg.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(msgKey,
                params));
        errors.add(msg);
    }

    /**
     * Add a UI message to the Request.
     * @param msgKey of the string you want to display
     * @param req used to store the message in.
     */
    public void saveMessage(String msgKey, HttpServletRequest req) {
        saveMessage(msgKey, null, req);
    }

    /**
     * Add a UI message to the Request.
     * @param msgKey of the string you want to display
     * @param params formatted params for the localized message
     * @param req used to store the message in.
     */
    // TODO Write unit tests for saveMessage(String, String[], HttpServletRequest)
    public void saveMessage(String msgKey, String[] params, HttpServletRequest req) {
        if (params == null) {
            params = new String[0];
        }
        ActionMessages msg = new ActionMessages();
        msg.add(ActionMessages.GLOBAL_MESSAGE,
                new ActionMessage(msgKey, params));
        saveMessages(req, msg);
    }

    /**
     * Add messages to the request
     * @param request Request where messages will be saved.
     * @param messages Messages to be saved.
     */
    // TODO Write unit tests for saveMessages(HttpServletRequest, ActionMessages)
    public void saveMessages(HttpServletRequest request, ActionMessages messages) {
        HttpSession session = request.getSession();

        if ((messages == null) || messages.isEmpty()) {
            session.removeAttribute(Globals.ERROR_KEY);
            session.removeAttribute(Globals.MESSAGE_KEY);
            return;
        }
        String key = Globals.MESSAGE_KEY;
        if (messages instanceof ActionErrors) {
            key = Globals.ERROR_KEY;
        }

        ActionMessages newMessages = new ActionMessages();

        // Check for existing messages
        ActionMessages sessionExisting =
            (ActionMessages) session.getAttribute(key);

        if (sessionExisting != null) {
            newMessages.add(sessionExisting);
        }
        newMessages.add(messages);

        session.setAttribute(key, newMessages);
        request.setAttribute(key, newMessages);
    }

    /**
     * Add messages to the request
     * @param request Request where messages will be saved.
     * @param errors List of ValidatorError objects.
     * @param warnings List of ValidatorWarning objects.
     */
    public void saveMessages(HttpServletRequest request,
            List<ValidatorError> errors,
            List<ValidatorWarning> warnings) {

        bindMessage(request, Globals.ERROR_KEY, errors, new ActionErrors());
        bindMessage(request, Globals.MESSAGE_KEY, warnings, new ActionMessages());
    }

    /**
     * Add messages to the request
     * @param request Request where messages will bed saved.
     * @param result the validator result object..
     */
    public void saveMessages(HttpServletRequest request, ValidatorResult result) {
        saveMessages(request, result.getErrors(), result.getWarnings());
    }

    /**
     * Util to get the String file name of a file upload form.
     *
     * @param form to get the contents from
     * @param paramName of the FormFile
     * @return String file name of the upload.
     */
    public String getFormFileName(DynaActionForm form, String paramName) {
        if (form.getDynaClass().getDynaProperty(paramName) == null) {
            return "";
        }

        FormFile f = (FormFile)form.get(paramName);
        return f.getFileName();
    }

    /**
     * Util to get the String version of a file upload form. Not useful if the
     * upload is binary.
     *
     * @param form to get the contents from
     * @param paramName of the FormFile
     * @return String version of the upload.
     */
    // TODO Write unit tests for getFormFileString(DynaActionForm, String)
    public String getFormFileString(DynaActionForm form, String paramName) {
        if (form.getDynaClass().getDynaProperty(paramName) == null) {
            return "";
        }

        FormFile f = (FormFile)form.get(paramName);
        return extractString(f);
    }


    /**
     * Util to get the String version of a file upload form. Not useful if the
     * upload is binary.
     * @param f  the  formfile to extract data off....
     * @return String version of the upload.
     */
    public  String extractString(FormFile f) {
        String retval = null;
        try {
            if (f != null && f.getFileData() != null) {
                String fileString = new String(f.getFileData(), "UTF-8");
                if (!StringUtils.isEmpty(fileString)) {
                    retval = fileString;
                }
            }
        }
        catch (UnsupportedEncodingException e) {
            LOG.error(e);
            throw new RuntimeException(e);
        }
        catch (FileNotFoundException e) {
            LOG.error(e);
            throw new RuntimeException(e);
        }
        catch (IOException e) {
            LOG.error(e);
            throw new RuntimeException(e);
        }
        return retval;
    }

    /**
     * Use this for every textarea that we use in our UI.  Otherwise you will get ^M
     * in your file showing up.
     * @param form to fetch from
     * @param name of value in form
     * @return String without CR in them.
     */
    public String getTextAreaValue(DynaActionForm form, String name) {
        String value = form.getString(name);
        return StringUtils.replaceChars(value, "\r", "");
    }

    /**
     * Reads the earliest date from a form populated by a datepicker.
     * Your dyna action picker must either be a struts datePickerForm, or
     * possess all of datePickerForm's fields.
     * @param form The datePickerForm
     * @param name The prefix for the date picker form fields, usually "date"
     * @param yearDirection One of DatePicker's year range static variables.
     * @return The earliest date to schedule actions.
     * @see com.redhat.rhn.common.util.DatePicker
     */
    public Date readDatePicker(DynaActionForm form, String name, int yearDirection) {
        //use date is not required for date picker forms.
        //if it is not there, then that means we should always evaluate the
        //date picker.  Otherwise, we evaluate if it tells us to do so.
        if (!form.getMap().containsKey(DatePicker.USE_DATE) ||
                form.get(DatePicker.USE_DATE) == null ||
                ((Boolean)form.get(DatePicker.USE_DATE)).booleanValue()) {
            DatePicker p = getDatePicker(name, yearDirection);
            p.readForm(form);
            return p.getDate();
        }
        return new Date();
    }

    /**
     * Writes the values of a date picker form to the <code>requestParams</code>
     * for remembering form values across requests.
     * Your dyna action picker must either be a struts datePickerForm, or
     * possess all of datePickerForm's fields.
     * @param requestParams The map to which to copy form fields
     * @param form The datePickerForm
     * @param name The prefix for the date picker form fields, usually "date"
     * @param yearDirection One of DatePicker's year range static variables.
     * @see com.redhat.rhn.common.util.DatePicker
     */
    public void rememberDatePicker(Map requestParams,
            DynaActionForm form, String name, int yearDirection) {
        //Write the option use_date field if it is there.
        if (form.get(DatePicker.USE_DATE) != null) {
            requestParams.put(DatePicker.USE_DATE,
                    form.get(DatePicker.USE_DATE));
        }

        //The datepicker itself can write the rest.
        DatePicker p = getDatePicker(name, yearDirection);
        p.readForm(form);
        p.writeToMap(requestParams);
    }

    /**
     * Creates a date picker object with the given name and prepopulates the date
     * with values from the given request's parameters. Prepopulates the form with
     * these values as well.
     * Your dyna action picker must either be a struts datePickerForm, or
     * possess all of datePickerForm's fields.
     * @param request The request from which to get initial form field values.
     * @param form The datePickerForm
     * @param name The prefix for the date picker form fields, usually "date"
     * @param yearDirection One of DatePicker's year range static variables.
     * @return The created and prepopulated date picker object
     * @see com.redhat.rhn.common.util.DatePicker
     */
    public DatePicker prepopulateDatePicker(HttpServletRequest request, DynaActionForm form,
            String name, int yearDirection) {
        //Create the date picker.
        DatePicker p = getDatePicker(name, yearDirection);

        //prepopulate the date for this picker
        p.readMap(request.getParameterMap());

        //prepopulate the form for this picker
        p.writeToForm(form);
        if (!StringUtils.isEmpty(request.getParameter(DatePicker.USE_DATE))) {
            Boolean preset = Boolean.valueOf(request.getParameter(DatePicker.USE_DATE));
            form.set(DatePicker.USE_DATE, preset);
        }
        else if (form.getMap().containsKey(DatePicker.USE_DATE)) {
            form.set(DatePicker.USE_DATE, Boolean.FALSE);
        }
        request.setAttribute(name, p);
        //give back the date picker
        return p;
    }

    private DatePicker getDatePicker(String name, int yearDirection) {
        Context ctx = Context.getCurrentContext();
        if (ctx == null) {
            return new DatePicker(name, TimeZone.getDefault(), Locale.getDefault(),
                    yearDirection);
        }
        return new DatePicker(name, ctx.getTimezone(), ctx.getLocale(), yearDirection);
    }


    private  void bindMessage(HttpServletRequest request, String key,
                        List<? extends ValidationMessage> messages,
                        ActionMessages actMsgs) {
        if (!messages.isEmpty()) {
            for (ValidationMessage msg : messages) {
                actMsgs.add(ActionMessages.GLOBAL_MESSAGE,
                        new ActionMessage(msg.getKey(), msg.getValues()));
            }

            ActionMessages requestMsg = (ActionMessages)request.
                                                getAttribute(key);
            if (requestMsg == null) {
                requestMsg = new ActionMessages();
            }
            requestMsg.add(actMsgs);
            if (requestMsg.isEmpty()) {
                request.removeAttribute(key);
            }
            else {
                request.setAttribute(key, requestMsg);
                request.getSession().setAttribute(key, requestMsg);
            }
        }

    }
}