/*
 * 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.jmeter.protocol.http.config.gui;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JCheckBox;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.config.ConfigTestElement;
import org.apache.jmeter.gui.JBooleanPropertyEditor;
import org.apache.jmeter.gui.util.HorizontalPanel;
import org.apache.jmeter.gui.util.JSyntaxTextArea;
import org.apache.jmeter.gui.util.JTextScrollPane;
import org.apache.jmeter.protocol.http.gui.HTTPArgumentsPanel;
import org.apache.jmeter.protocol.http.gui.HTTPFileArgsPanel;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBaseSchema;
import org.apache.jmeter.protocol.http.util.HTTPArgument;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.property.JMeterProperty;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.gui.JFactory;
import org.apache.jorphan.gui.JLabeledChoice;
import org.apache.jorphan.gui.JLabeledTextField;

/**
 * Basic URL / HTTP Request configuration:
 * <ul>
 * <li>host and port</li>
 * <li>connect and response timeouts</li>
 * <li>path, method, encoding, parameters</li>
 * <li>redirects and keepalive</li>
 * </ul>
 */
public class UrlConfigGui extends JPanel implements ChangeListener {

    private static final long serialVersionUID = 240L;

    /**
     * Default value settings for URL Configuration GUI elements.
     */
    private static final UrlConfigDefaults URL_CONFIG_DEFAULTS = new UrlConfigDefaults();

    private static final int TAB_PARAMETERS = 0;

    private int tabRawBodyIndex = 1;

    private int tabFileUploadIndex = 2;

    private HTTPArgumentsPanel argsPanel;

    private HTTPFileArgsPanel filesPanel;

    private JLabeledTextField domain;

    private JLabeledTextField port;

    private JLabeledTextField protocol;

    private JLabeledTextField contentEncoding;

    private JLabeledTextField path;

    private JCheckBox followRedirects;

    private JCheckBox autoRedirects;

    private JBooleanPropertyEditor useKeepAlive;

    private JBooleanPropertyEditor useMultipart;

    private JBooleanPropertyEditor useBrowserCompatibleMultipartMode;

    private JLabeledChoice method;

    // set this false to suppress some items for use in HTTP Request defaults
    private final boolean notConfigOnly;

    // Body data
    private JSyntaxTextArea postBodyContent;

    // Tabbed pane that contains parameters and raw body
    private AbstractValidationTabbedPane postContentTabbedPane;

    private final boolean showRawBodyPane;
    private final boolean showFileUploadPane;

    /**
     * Constructor which is setup to show HTTP implementation, raw body pane and
     * sampler fields.
     */
    public UrlConfigGui() {
        this(true);
    }

    /**
     * Constructor which is setup to show HTTP implementation and raw body pane.
     *
     * @param showSamplerFields
     *            flag whether sampler fields should be shown.
     */
    public UrlConfigGui(boolean showSamplerFields) {
        this(showSamplerFields, true);
    }

    /**
     * @param showSamplerFields
     *            flag whether sampler fields should be shown
     * @param showRawBodyPane
     *            flag whether the raw body pane should be shown
     */
    public UrlConfigGui(boolean showSamplerFields, boolean showRawBodyPane) {
        this(showSamplerFields, showRawBodyPane, false);
    }

    /**
     * @param showSamplerFields
     *            flag whether sampler fields should be shown
     * @param showRawBodyPane
     *            flag whether the raw body pane should be shown
     * @param showFileUploadPane flag whether the file upload pane should be shown
     */
    public UrlConfigGui(boolean showSamplerFields, boolean showRawBodyPane, boolean showFileUploadPane) {
        this.notConfigOnly = showSamplerFields;
        this.showRawBodyPane = showRawBodyPane;
        this.showFileUploadPane = showFileUploadPane;
        init();
    }

    public void clear() {
        domain.setText(""); // $NON-NLS-1$
        if (notConfigOnly){
            followRedirects.setSelected(getUrlConfigDefaults().isFollowRedirects());
            autoRedirects.setSelected(getUrlConfigDefaults().isAutoRedirects());
            method.setText(getUrlConfigDefaults().getDefaultMethod());
            useKeepAlive.setBooleanValue(getUrlConfigDefaults().isUseKeepAlive());
            useBrowserCompatibleMultipartMode.setBooleanValue(getUrlConfigDefaults().isUseBrowserCompatibleMultipartMode());
        }
        path.setText(""); // $NON-NLS-1$
        port.setText(""); // $NON-NLS-1$
        protocol.setText(""); // $NON-NLS-1$
        contentEncoding.setText(""); // $NON-NLS-1$
        argsPanel.clear();
        if(showFileUploadPane) {
            filesPanel.clear();
        }
        if(showRawBodyPane) {
            postBodyContent.setInitialText("");// $NON-NLS-1$
        }
        postContentTabbedPane.setSelectedIndex(TAB_PARAMETERS, false);
    }

    public TestElement createTestElement() {
        ConfigTestElement element = new ConfigTestElement();

        element.setName(this.getName());
        element.setProperty(TestElement.GUI_CLASS, this.getClass().getName());
        element.setProperty(TestElement.TEST_CLASS, element.getClass().getName());
        modifyTestElement(element);
        return element;
    }

    /**
     * Save the GUI values in the sampler.
     *
     * @param element {@link TestElement} to modify
     */
    public void modifyTestElement(TestElement element) {
        boolean useRaw = showRawBodyPane && !postBodyContent.getText().isEmpty();
        Arguments args;
        if(useRaw) {
            args = new Arguments();
            String text = postBodyContent.getText();
            /*
             * Textfield uses \n (LF) to delimit lines; we need to send CRLF.
             * Rather than change the way that arguments are processed by the
             * samplers for raw data, it is easier to fix the data.
             * On retrieval, CRLF is converted back to LF for storage in the text field.
             * See
             */
            HTTPArgument arg = new HTTPArgument("", text.replaceAll("\n","\r\n"), false);
            arg.setAlwaysEncoded(false);
            args.addArgument(arg);
        } else {
            args = (Arguments) argsPanel.createTestElement();
            HTTPArgument.convertArgumentsToHTTP(args);
        }
        if(showFileUploadPane) {
            filesPanel.modifyTestElement(element);
        }
        HTTPSamplerBaseSchema.INSTANCE httpSchema = HTTPSamplerBaseSchema.INSTANCE;
        // Treat "unset" checkbox as "property removal" for HTTP Request Defaults component
        // Regular sampler should save both true and false values
        element.set(httpSchema.getPostBodyRaw(), useRaw ? Boolean.TRUE : (notConfigOnly ? false : null));
        element.set(httpSchema.getArguments(), args);
        element.set(httpSchema.getDomain(), domain.getText());
        element.set(httpSchema.getPort(), port.getText());
        element.set(httpSchema.getProtocol(), protocol.getText());
        element.set(httpSchema.getContentEncoding(), contentEncoding.getText());
        element.set(httpSchema.getPath(), path.getText());
        if (notConfigOnly){
            element.set(httpSchema.getMethod(), method.getText());
            element.set(httpSchema.getFollowRedirects(), followRedirects.isSelected());
            element.set(httpSchema.getAutoRedirects(), autoRedirects.isSelected());
            useKeepAlive.updateElement(element);
            useMultipart.updateElement(element);
            useBrowserCompatibleMultipartMode.updateElement(element);
        }
    }

    // Just append all the parameter values, and use that as the post body
    /**
     * Compute body data from arguments
     * @param arguments {@link Arguments}
     * @return {@link String}
     */
    private static String computePostBody(Arguments arguments) {
        return computePostBody(arguments, false);
    }

    /**
     * Compute body data from arguments
     * @param arguments {@link Arguments}
     * @param crlfToLF whether to convert CRLF to LF
     * @return {@link String}
     */
    private static String computePostBody(Arguments arguments, boolean crlfToLF) {
        StringBuilder postBody = new StringBuilder();
        for (JMeterProperty argument : arguments) {
            HTTPArgument arg = (HTTPArgument) argument.getObjectValue();
            String value = arg.getValue();
            if (crlfToLF) {
                value = value.replaceAll("\r\n", "\n"); // See modifyTestElement
            }
            postBody.append(value);
        }
        return postBody.toString();
    }

    /**
     * Set the text, etc. in the UI.
     *
     * @param el
     *            contains the data to be displayed
     */
    public void configure(TestElement el) {
        setName(el.getName());
        HTTPSamplerBaseSchema.INSTANCE httpSchema = HTTPSamplerBaseSchema.INSTANCE;
        Arguments arguments = el.get(httpSchema.getArguments());

        if (showRawBodyPane) {
            boolean useRaw = el.get(httpSchema.getPostBodyRaw());
            if(useRaw) {
                String postBody = computePostBody(arguments, true); // Convert CRLF to CR, see modifyTestElement
                postBodyContent.setInitialText(postBody);
                postBodyContent.setCaretPosition(0);
                argsPanel.clear();
                postContentTabbedPane.setSelectedIndex(tabRawBodyIndex, false);
            } else {
                postBodyContent.setInitialText("");
                argsPanel.configure(arguments);
                postContentTabbedPane.setSelectedIndex(TAB_PARAMETERS, false);
            }
        }

        if(showFileUploadPane) {
            filesPanel.configure(el);
        }

        domain.setText(el.getString(httpSchema.getDomain()));

        String portString = el.getString(httpSchema.getPort());

        // Only display the port number if it is meaningfully specified
        if (portString.equals(HTTPSamplerBase.UNSPECIFIED_PORT_AS_STRING)) {
            port.setText(""); // $NON-NLS-1$
        } else {
            port.setText(portString);
        }
        // We explicitly
        String protocol = el.getPropertyAsString(httpSchema.getProtocol().getName(), "");
        this.protocol.setText(protocol);
        String encoding = el.getPropertyAsString(httpSchema.getContentEncoding().getName(), "");
        contentEncoding.setText(encoding);
        path.setText(el.getString(httpSchema.getPath()));
        if (notConfigOnly){
            method.setText(el.getString(httpSchema.getMethod()));
            followRedirects.setSelected(el.get(httpSchema.getFollowRedirects()));
            autoRedirects.setSelected(el.get(httpSchema.getAutoRedirects()));
            useKeepAlive.updateUi(el);
            useMultipart.updateUi(el);
            useBrowserCompatibleMultipartMode.updateUi(el);
        }
    }

    private void init() {// called from ctor, so must not be overridable
        this.setLayout(new BorderLayout());

        // WEB REQUEST PANEL
        JPanel webRequestPanel = new JPanel();
        webRequestPanel.setLayout(new BorderLayout());
        webRequestPanel.setBorder(BorderFactory.createTitledBorder(
                JMeterUtils.getResString("web_request"))); // $NON-NLS-1$

        webRequestPanel.add(getPathPanel(), BorderLayout.NORTH);
        webRequestPanel.add(getParameterPanel(), BorderLayout.CENTER);

        this.add(getWebServerPanel(), BorderLayout.NORTH);
        this.add(webRequestPanel, BorderLayout.CENTER);
    }

    /**
     * Create a panel containing the webserver (domain+port) and scheme.
     *
     * @return the panel
     */
    protected final JPanel getWebServerPanel() {
        // PROTOCOL
        protocol = new JLabeledTextField(JMeterUtils.getResString("protocol"), 4); // $NON-NLS-1$
        port = new JLabeledTextField(JMeterUtils.getResString("web_server_port"), 7); // $NON-NLS-1$
        domain = new JLabeledTextField(JMeterUtils.getResString("web_server_domain"), 40); // $NON-NLS-1$

        JPanel webServerPanel = new HorizontalPanel();
        webServerPanel.setBorder(BorderFactory.createTitledBorder(
                JMeterUtils.getResString("web_server"))); // $NON-NLS-1$
        webServerPanel.add(protocol);
        webServerPanel.add(domain);
        webServerPanel.add(port);
        return webServerPanel;
    }

    /**
     * Return the {@link UrlConfigDefaults} instance to be used when configuring the UI elements and default values.
     * @return the {@link UrlConfigDefaults} instance to be used when configuring the UI elements and default values
     */
    protected UrlConfigDefaults getUrlConfigDefaults() {
        return URL_CONFIG_DEFAULTS;
    }

    /**
     * This method defines the Panel for:
     *  the HTTP path, Method and Content Encoding
     *  'Follow Redirects', 'Use KeepAlive', and 'Use multipart for HTTP POST' elements.
     *
     * @return JPanel The Panel for the path, 'Follow Redirects' and 'Use
     *         KeepAlive' elements.
     */
    protected Component getPathPanel() {
        path = new JLabeledTextField(JMeterUtils.getResString("path"), 80); //$NON-NLS-1$
        // CONTENT_ENCODING
        contentEncoding = new JLabeledTextField(JMeterUtils.getResString("content_encoding"), 7); // $NON-NLS-1$

        if (notConfigOnly){
            method = new JLabeledChoice(JMeterUtils.getResString("method"), // $NON-NLS-1$
                    getUrlConfigDefaults().getValidMethods(), true, false);
            method.addChangeListener(this);
        }

        if (notConfigOnly){
            followRedirects = new JCheckBox(JMeterUtils.getResString("follow_redirects")); // $NON-NLS-1$
            JFactory.small(followRedirects);
            followRedirects.setSelected(getUrlConfigDefaults().isFollowRedirects());
            followRedirects.addChangeListener(this);
            followRedirects.setVisible(getUrlConfigDefaults().isFollowRedirectsVisible());

            autoRedirects = new JCheckBox(JMeterUtils.getResString("follow_redirects_auto")); //$NON-NLS-1$
            JFactory.small(autoRedirects);
            autoRedirects.addChangeListener(this);
            autoRedirects.setSelected(getUrlConfigDefaults().isAutoRedirects());// Default changed in 2.3 and again in 2.4
            autoRedirects.setVisible(getUrlConfigDefaults().isAutoRedirectsVisible());

            useKeepAlive = new JBooleanPropertyEditor(
                    HTTPSamplerBaseSchema.INSTANCE.getUseKeepalive(),
                    JMeterUtils.getResString("use_keepalive"));
            JFactory.small(useKeepAlive);
            useKeepAlive.setBooleanValue(getUrlConfigDefaults().isUseKeepAlive());
            useKeepAlive.setVisible(getUrlConfigDefaults().isUseKeepAliveVisible());

            useMultipart = new JBooleanPropertyEditor(
                    HTTPSamplerBaseSchema.INSTANCE.getUseMultipartPost(),
                    JMeterUtils.getResString("use_multipart_for_http_post")); // $NON-NLS-1$
            JFactory.small(useMultipart);
            useMultipart.setBooleanValue(getUrlConfigDefaults().isUseMultipart());
            useMultipart.setVisible(getUrlConfigDefaults().isUseMultipartVisible());

            useBrowserCompatibleMultipartMode = new JBooleanPropertyEditor(
                    HTTPSamplerBaseSchema.INSTANCE.getUseBrowserCompatibleMultipart(),
                    JMeterUtils.getResString("use_multipart_mode_browser")); // $NON-NLS-1$
            JFactory.small(useBrowserCompatibleMultipartMode);
            useBrowserCompatibleMultipartMode.setBooleanValue(getUrlConfigDefaults().isUseBrowserCompatibleMultipartMode());
            useBrowserCompatibleMultipartMode.setVisible(getUrlConfigDefaults().isUseBrowserCompatibleMultipartModeVisible());
        }

        JPanel pathPanel =  new HorizontalPanel();
        if (notConfigOnly){
            pathPanel.add(method);
        }
        pathPanel.add(path);
        pathPanel.add(contentEncoding);
        JPanel panel = new JPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
        panel.add(pathPanel);
        if (notConfigOnly){
            JPanel optionPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
            optionPanel.add(autoRedirects);
            optionPanel.add(followRedirects);
            optionPanel.add(useKeepAlive);
            optionPanel.add(useMultipart);
            optionPanel.add(useBrowserCompatibleMultipartMode);
            optionPanel.setMinimumSize(optionPanel.getPreferredSize());
            panel.add(optionPanel);
        }

        return panel;
    }

    protected JTabbedPane getParameterPanel() {
        postContentTabbedPane = new ValidationTabbedPane();
        argsPanel = new HTTPArgumentsPanel();
        postContentTabbedPane.add(JMeterUtils.getResString("post_as_parameters"), argsPanel);// $NON-NLS-1$

        int indx = TAB_PARAMETERS;
        if(showRawBodyPane) {
            tabRawBodyIndex = ++indx;
            postBodyContent = JSyntaxTextArea.getInstance(30, 50);// $NON-NLS-1$
            postContentTabbedPane.add(JMeterUtils.getResString("post_body"), JTextScrollPane.getInstance(postBodyContent));// $NON-NLS-1$
        }

        if(showFileUploadPane) {
            tabFileUploadIndex = ++indx;
            filesPanel = new HTTPFileArgsPanel();
            postContentTabbedPane.add(JMeterUtils.getResString("post_files_upload"), filesPanel);
        }
        return postContentTabbedPane;
    }

    /**
     * Create a new {@link Arguments} instance associated with the specific GUI used in this component.
     * @return a new {@link Arguments} instance associated with the specific GUI used in this component
     */
    protected Arguments createHTTPArgumentsTestElement() {
        return (Arguments) argsPanel.createTestElement();
    }

    class ValidationTabbedPane extends AbstractValidationTabbedPane {

        private static final long serialVersionUID = 7014311238367882881L;

        @Override
        protected int getValidatedTabIndex(int currentTabIndex, int newTabIndex) {
            if (newTabIndex == tabFileUploadIndex) { // We're going to File, no problem
                return newTabIndex;
            }

            // We're moving to Raw or Parameters
            if (newTabIndex != currentTabIndex) {
                // If the Parameter data can be converted (i.e. no names)
                // we switch
                if (newTabIndex == tabRawBodyIndex) {
                    if (canSwitchToRawBodyPane()) {
                        convertParametersToRaw();
                        return newTabIndex;
                    } else {
                        return TAB_PARAMETERS;
                    }
                }
                else {
                    // If the Parameter data cannot be converted to Raw, then the user should be
                    // prevented from doing so raise an error dialog
                    if (canSwitchToParametersTab()) {
                        return newTabIndex;
                    } else {
                        return tabRawBodyIndex;
                    }
                }
            }

            return newTabIndex;
        }

        /**
         * @return false if one argument has a name
         */
        private boolean canSwitchToRawBodyPane() {
            Arguments arguments = (Arguments) argsPanel.createTestElement();
            for (int i = 0; i < arguments.getArgumentCount(); i++) {
                if(!StringUtils.isEmpty(arguments.getArgument(i).getName())) {
                    return false;
                }
            }
            return true;
        }

        /**
         * @return true if postBodyContent is empty
         */
        private boolean canSwitchToParametersTab() {
            return showRawBodyPane && postBodyContent.getText().isEmpty();
        }
    }

    // autoRedirects and followRedirects cannot both be selected
    @Override
    public void stateChanged(ChangeEvent e) {
        Object source = e.getSource();
        if (source == autoRedirects && autoRedirects.isSelected()) {
            followRedirects.setSelected(false);
        }
        else if (source == followRedirects && followRedirects.isSelected()) {
            autoRedirects.setSelected(false);
        }
    }


    /**
     * Convert Parameters to Raw Body
     */
    void convertParametersToRaw() {
        if (showRawBodyPane && postBodyContent.getText().isEmpty()) {
            postBodyContent.setInitialText(computePostBody((Arguments)argsPanel.createTestElement()));
            postBodyContent.setCaretPosition(0);
        }
    }
}
