/*
 * $Id: ActionRedirect.java 513602 2007-03-02 02:50:23Z pbenedict $
 *
 * 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.action;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;

import org.apache.struts.config.ForwardConfig;
import org.apache.struts.util.ResponseUtils;

/**
 * <p> A subclass of {@link ActionForward} which is designed for use in
 * redirecting requests, with support for adding parameters at runtime. <br/>
 * An {@link ForwardConfig} (or subclass) can be passed to the constructor to
 * copy its configuration: </p> <p>
 * <pre>
 * public ActionForward execute(ActionMapping mapping,
 *                              ActionForm form,
 *                              HttpServletRequest request,
 *                              HttpServletResponse response)
 *         throws Exception {
 *     ActionRedirect redirect =
 *             new ActionRedirect(mapping.findForward("doRedirect"));
 *     redirect.addParameter("param1","value1");
 *     redirect.addParameter("param2","2");
 *     redirect.addParameter("param3","3.0");
 *     return redirect;
 * }
 * </pre>
 * </p>
 *
 * @version $Rev: 513602 $ $Date: 2007-03-01 20:50:23 -0600 (Thu, 01 Mar 2007) $
 */
public class ActionRedirect extends ActionForward {

    // ----------------------------------------------------- Manifest constants

    /** serialVersionUID */
    private static final long serialVersionUID = 3751872574084528837L;

    /** <p>Default allocation size for string buffers.</p> */
    private static final int DEFAULT_BUFFER_SIZE = 256;

    // ----------------------------------------------------- Instance variables

    /**
     * <p>Holds the redirect parameters. Each entry is either a String or a
     * String[] depending on whether it has one or more entries.</p>
     */
    private final Map<String, Object> parameterValues = new HashMap<>();

    // ----------------------------------------------------- Constructors

    /**
     * <p>Construct a new instance with redirect set to true and initialize
     * parameter lists.</p>
     */
    public ActionRedirect() {
        setRedirect(true);
    }

    /**
     * <p>Construct a new instance with the specified path and initialize
     * parameter lists.</p>
     *
     * @param path Path for this instance
     */
    public ActionRedirect(final String path) {
        super(path);
        setRedirect(true);
    }

    /**
     * <p>Construct a new instance with the specified values and initialize
     * parameter lists.</p>
     *
     * @param name   Name of this instance
     * @param path   Path for this instance
     * @param module Module prefix, if any
     */
    public ActionRedirect(final String name, final String path, final String module) {
        super(name, path, true);
        setModule(module);
    }

    /**
     * <p>Construct a new instance with a {@link ForwardConfig} object to copy
     * name, path, contextRelative, and arbitrary property values from.</p>
     *
     * @param baseConfig the {@link ForwardConfig} to copy configuration
     *                   values from
     */
    public ActionRedirect(final ForwardConfig baseConfig) {
        setName(baseConfig.getName());
        setPath(baseConfig.getPath());
        setModule(baseConfig.getModule());
        setRedirect(true);
        inheritProperties(baseConfig);
    }

    // ----------------------------------------------------- Public methods

    /**
     * <p>return holding parameter values.</p>
     * @return Parameter Values
     */
    Map<String, Object> getParameterValues() {
        return this.parameterValues;
    }

    /**
     * <p>Adds the object's toString() to the list of parameters if it's not
     * null, or an empty string with the given fieldName if it is.</p>
     *
     * @param fieldName the name to use for the parameter
     * @param valueObj  the value for this parameter
     * @return The ActionRedirect instance this method is called on
     */
    public ActionRedirect addParameter(final String fieldName, final Object valueObj) {

        String value = ResponseUtils.encodeURL(Objects.toString(valueObj, ""));

        Object currentValue = this.parameterValues.get(fieldName);

        if (currentValue == null) {
            // there's no value for this param yet; add it to the map
            this.parameterValues.put(fieldName, value);

        } else if (currentValue instanceof String) {
            // there's already a value; let's use an array for these parameters
            String[] newValue = new String[2];
            newValue[0] = (String) currentValue;
            newValue[1] = value;
            this.parameterValues.put(fieldName, newValue);

        } else if (currentValue instanceof String[]) {
            // add the value to the list of existing values
            List<String> newValues =
                new ArrayList<>(Arrays.asList((String[]) currentValue));
            newValues.add(value);
            this.parameterValues.put(fieldName,
                newValues.toArray(new String[newValues.size()]));

        }
        return this;
    }

    /**
     * <p>Get the original path without the parameters added at runtime.</p>
     *
     * @return the original path as configured.
     */
    public String getOriginalPath() {
        return super.getPath();
    }

    /**
     * <p>Get the path for this object, including any parameters that may have
     * been added at runtime.</p>
     *
     * @return The path for this object.
     */
    @Override
    public String getPath() {
        // get the original path and the parameter string that was formed
        String originalPath = getOriginalPath();
        String parameterString = getParameterString();

        StringBuilder result = new StringBuilder(originalPath);

        if (!parameterString.isEmpty()) {
            // the parameter separator we're going to use
            String paramSeparator = "?";

            // does the original path already have a "?"?
            int paramStartIndex = originalPath.indexOf('?');

            if (0 <= paramStartIndex) {
                // did the path end with "?"?
                if (paramStartIndex != (originalPath.length() - 1)) {
                    paramSeparator = "&";
                } else {
                    paramSeparator = "";
                }
            }

            result.append(paramSeparator);
            result.append(parameterString);
        }

        return result.toString();
    }

    /**
     * <p>Forms the string containing the parameters passed onto this object
     * through calls to addParameter().</p>
     *
     * @return a string which can be appended to the URLs.  The return string
     *         does not include a leading question mark (?).
     */
    public String getParameterString() {
        StringBuilder strParam = new StringBuilder(DEFAULT_BUFFER_SIZE);

        // loop through all parameters
        for (final Entry<String, Object> ent : this.parameterValues.entrySet()) {
            if (0 < strParam.length()) {
                strParam.append("&");
            }

            if (ent.getValue() instanceof String) {
                // just one value for this param
                strParam.append(ent.getKey()).append("=").append(ent.getValue());

            } else if (ent.getValue() instanceof String[]) {
                // loop through all values for this param
                String[] values = (String[]) ent.getValue();

                for (final String value : values) {
                    if (0 < strParam.length()) {
                        strParam.append("&");
                    }
                    strParam.append(ent.getKey()).append("=").append(value);
                }
            }
        }

        return strParam.toString();
    }

    // ----------------------------------------------------- toString()

    /**
     * <p>Return a string description of this object.</p>
     *
     * @return a string containing the original path for this object and the
     *         parameters it currently holds
     */
    @Override
    public String toString() {
        final String result = "ActionRedirect ["
                + "originalPath=" + getOriginalPath() + ";"
                + "parameterString=" + getParameterString()
                + "]";
        return result;
    }
}
