/*
 * 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.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;

import org.apache.jmeter.config.gui.AbstractConfigGui;
import org.apache.jmeter.gui.TestElementMetadata;
import org.apache.jmeter.gui.util.PowerTableModel;
import org.apache.jmeter.protocol.http.control.DNSCacheManager;
import org.apache.jmeter.protocol.http.control.StaticHost;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.property.JMeterProperty;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.gui.GuiUtils;
import org.apache.jorphan.gui.layout.VerticalLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This gui part of @see
 * {@link org.apache.jmeter.protocol.http.control.DNSCacheManager}. Using
 * radiobuttons, user can switch between using system DNS resolver and custom
 * resolver. Custom resolver functionality is provided by dnsjava library.
 * "DNS servers" may contain one or more IP/Name of dns server for resolving
 * name DNS servers are chosen via round-robin. If table is empty - system
 * resolver is used.
 *
 * @since 2.12
 */
@TestElementMetadata(labelResource = "dns_cache_manager_title")
public class DNSCachePanel extends AbstractConfigGui implements ActionListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(DNSCachePanel.class);

    private static final long serialVersionUID = 2120L;

    public static final String OPTIONS = JMeterUtils.getResString("option");

    private static final String ADD_COMMAND = JMeterUtils.getResString("add"); // $NON-NLS-1$

    private static final String ADD_HOST_COMMAND = JMeterUtils.getResString("add_host"); // $NON-NLS-1$

    private static final String DELETE_COMMAND = JMeterUtils.getResString("delete"); // $NON-NLS-1$

    private static final String DELETE_HOST_COMMAND = JMeterUtils.getResString("delete_host"); // $NON-NLS-1$

    private static final String SYS_RES_COMMAND = JMeterUtils.getResString("use_system_dns_resolver"); // $NON-NLS-1$

    private static final String CUST_RES_COMMAND = JMeterUtils.getResString("use_custom_dns_resolver"); // $NON-NLS-1$

    private JTable dnsHostsTable;

    private JPanel dnsHostsPanel;
    private JPanel dnsHostsButPanel;

    private JTable dnsServersTable;

    private JPanel dnsServersPanel;

    private JPanel dnsServButPanel;

    private PowerTableModel dnsServersTableModel;
    private PowerTableModel dnsHostsTableModel;

    private JRadioButton sysResButton;

    private JRadioButton custResButton;

    private JButton deleteButton;

    private JButton addButton;

    private JButton addHostButton;
    private JButton deleteHostButton;

    private final ButtonGroup providerDNSradioGroup = new ButtonGroup();

    private static final String[] COLUMN_RESOURCE_NAMES = {
        JMeterUtils.getResString("dns_hostname_or_ip"), //$NON-NLS-1$
    };
    private static final Class<?>[] columnClasses = {
        String.class };

    private static final String[] HOSTS_COLUMN_RESOURCE_NAMES = { JMeterUtils.getResString("dns_host"), JMeterUtils.getResString("dns_hostname_or_ip") };
    private static final Class<?>[] HOSTS_COLUMN_CLASSES = { String.class, String.class };

    private JCheckBox clearEachIteration;

    /**
     * Default constructor.
     */
    public DNSCachePanel() {
        init();
    }

    @Override
    public String getLabelResource() {
        return "dns_cache_manager_title";
    }

    /**
     * Modifies a given TestElement to mirror the data in the gui components.
     *
     * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(org.apache.jmeter.testelement.TestElement)
     */
    @Override
    public void modifyTestElement(TestElement dnsRes) {
        GuiUtils.stopTableEditing(dnsServersTable);
        dnsRes.clear();
        configureTestElement(dnsRes);
        if (dnsRes instanceof DNSCacheManager) {
            DNSCacheManager dnsCacheManager = (DNSCacheManager) dnsRes;
            for (int i = 0; i < dnsServersTableModel.getRowCount(); i++) {
                String server = (String) dnsServersTableModel.getRowData(i)[0];
                dnsCacheManager.addServer(server);
            }
            for (int i = 0; i < dnsHostsTableModel.getRowCount(); i++) {
                String host = (String) dnsHostsTableModel.getRowData(i)[0];
                String addresses = (String) dnsHostsTableModel.getRowData(i)[1];
                dnsCacheManager.addHost(host, addresses);
            }
            dnsCacheManager.setClearEachIteration(clearEachIteration.isSelected());
            if (providerDNSradioGroup.isSelected(custResButton.getModel())) {
                dnsCacheManager.setCustomResolver(true);
            } else {
                dnsCacheManager.setCustomResolver(false);
            }
        }
    }

    /**
     * Implements JMeterGUIComponent.clearGui
     */
    @Override
    public void clearGui() {
        super.clearGui();
        clearEachIteration.setSelected(DNSCacheManager.DEFAULT_CLEAR_CACHE_EACH_ITER);
        providerDNSradioGroup.setSelected(sysResButton.getModel(), true);
        dnsServersTableModel.clearData();
        deleteButton.setEnabled(false);
        dnsHostsTableModel.clearData();
        deleteHostButton.setEnabled(false);

    }

    private void populateTable(DNSCacheManager resolver) {
        dnsServersTableModel.clearData();
        for (JMeterProperty jMeterProperty : resolver.getServers()) {
            addServerToTable((String) jMeterProperty.getObjectValue());
        }
    }

    private void populateHostsTable(DNSCacheManager resolver) {
        dnsHostsTableModel.clearData();
        for (JMeterProperty hostEntry : resolver.getHosts()) {
            addHostToTable((StaticHost) hostEntry.getObjectValue());
        }
    }

    @Override
    public TestElement createTestElement() {
        DNSCacheManager dnsCacheManager = new DNSCacheManager();
        modifyTestElement(dnsCacheManager);
        return dnsCacheManager;
    }

    @Override
    public void configure(TestElement el) {
        super.configure(el);

        DNSCacheManager dnsCacheManager = (DNSCacheManager) el;
        populateTable(dnsCacheManager);
        populateHostsTable(dnsCacheManager);
        clearEachIteration.setSelected(dnsCacheManager.isClearEachIteration());
        if (dnsCacheManager.isCustomResolver()) {
            providerDNSradioGroup.setSelected(custResButton.getModel(), true);
            deleteButton.setEnabled(dnsServersTable.getColumnCount() > 0);
            deleteHostButton.setEnabled(dnsHostsTable.getColumnCount() > 0);
            addButton.setEnabled(true);
            addHostButton.setEnabled(true);
        } else {
            providerDNSradioGroup.setSelected(sysResButton.getModel(), true);
        }
    }

    private void init() { // WARNING: called from ctor so must not be overridden (i.e. must be private or final)
        dnsServersTableModel = new PowerTableModel(COLUMN_RESOURCE_NAMES, columnClasses);
        dnsHostsTableModel = new PowerTableModel(HOSTS_COLUMN_RESOURCE_NAMES, HOSTS_COLUMN_CLASSES);

        clearEachIteration = new JCheckBox(JMeterUtils.getResString("clear_cache_each_iteration"), true); //$NON-NLS-1$
        setLayout(new BorderLayout());
        setBorder(makeBorder());
        JPanel northPanel = new JPanel();
        northPanel.setLayout(new VerticalLayout(5, VerticalLayout.BOTH));
        northPanel.add(makeTitlePanel());
        JPanel optionsPane = new JPanel();
        optionsPane.setBorder(BorderFactory.createTitledBorder(OPTIONS)); // $NON-NLS-1$
        optionsPane.setLayout(new VerticalLayout(5, VerticalLayout.BOTH));
        optionsPane.add(clearEachIteration, BorderLayout.WEST);
        optionsPane.add(createChooseResPanel(), BorderLayout.SOUTH);
        northPanel.add(optionsPane);
        add(northPanel, BorderLayout.NORTH);

        JPanel tables = new JPanel();
        tables.setLayout(new VerticalLayout(2, VerticalLayout.BOTH));
        dnsServersPanel = createDnsServersTablePanel();
        dnsHostsPanel = createDnsHostsTablePanel();
        tables.add(dnsServersPanel);
        tables.add(dnsHostsPanel);
        add(tables, BorderLayout.CENTER);


    }

    public JPanel createDnsServersTablePanel() {
        // create the JTable that holds header per row
        dnsServersTable = new JTable(dnsServersTableModel);
        JMeterUtils.applyHiDPI(dnsServersTable);
        dnsServersTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        dnsServersTable.setPreferredScrollableViewportSize(new Dimension(400, 100));

        JPanel panel = new JPanel(new BorderLayout(0, 5));
        panel.setBorder(BorderFactory.createTitledBorder(
                JMeterUtils.getResString("dns_servers"))); // $NON-NLS-1$
        JScrollPane dnsServScrollPane = GuiUtils.emptyBorder(new JScrollPane(dnsServersTable));
        panel.add(dnsServScrollPane, BorderLayout.CENTER);
        dnsServButPanel = createButtonPanel();
        panel.add(dnsServButPanel, BorderLayout.SOUTH);
        return panel;
    }

    public JPanel createDnsHostsTablePanel() {
        // create the JTable that holds header per row
        dnsHostsTable = new JTable(dnsHostsTableModel);
        JMeterUtils.applyHiDPI(dnsHostsTable);
        dnsHostsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        dnsHostsTable.setPreferredScrollableViewportSize(new Dimension(400, 100));

        JPanel panel = new JPanel(new BorderLayout(0, 5));
        panel.setBorder(BorderFactory.createTitledBorder(
                JMeterUtils.getResString("dns_hosts"))); // $NON-NLS-1$
        JScrollPane dnsHostsScrollPane = GuiUtils.emptyBorder(new JScrollPane(dnsHostsTable));
        panel.add(dnsHostsScrollPane, BorderLayout.CENTER);
        dnsHostsButPanel = createHostsButtonPanel();
        panel.add(dnsHostsButPanel, BorderLayout.SOUTH);
        return panel;
    }

    private JPanel createChooseResPanel() {
        JPanel chooseResPanel = new JPanel(new BorderLayout(0, 5));
        sysResButton = new JRadioButton();
        sysResButton.setSelected(true);
        sysResButton.setText(SYS_RES_COMMAND);
        sysResButton.setToolTipText(SYS_RES_COMMAND);
        sysResButton.setEnabled(true);
        sysResButton.addActionListener(this);

        custResButton = new JRadioButton();
        custResButton.setSelected(false);
        custResButton.setText(CUST_RES_COMMAND);
        custResButton.setToolTipText(CUST_RES_COMMAND);
        custResButton.setEnabled(true);
        custResButton.addActionListener(this);

        providerDNSradioGroup.add(sysResButton);
        providerDNSradioGroup.add(custResButton);

        chooseResPanel.add(sysResButton, BorderLayout.WEST);
        chooseResPanel.add(custResButton, BorderLayout.CENTER);
        return chooseResPanel;
    }

    private JPanel createButtonPanel() {
        boolean tableEmpty = dnsServersTableModel.getRowCount() == 0;

        addButton = createButton("add", 'A', ADD_COMMAND, custResButton.isSelected()); // $NON-NLS-1$
        deleteButton = createButton("delete", 'D', DELETE_COMMAND, !tableEmpty); // $NON-NLS-1$

        JPanel buttonPanel = new JPanel();
        buttonPanel.add(addButton, BorderLayout.WEST);
        buttonPanel.add(deleteButton, BorderLayout.LINE_END);
        return buttonPanel;
    }

    private JPanel createHostsButtonPanel() {
        boolean tableEmpty = dnsHostsTableModel.getRowCount() == 0;

        addHostButton = createButton("add_host", 'H', ADD_HOST_COMMAND, custResButton.isSelected()); // $NON-NLS-1$
        deleteHostButton = createButton("delete_host", 'X', DELETE_HOST_COMMAND, !tableEmpty); // $NON-NLS-1$

        JPanel buttonPanel = new JPanel();
        buttonPanel.add(addHostButton, BorderLayout.WEST);
        buttonPanel.add(deleteHostButton, BorderLayout.LINE_END);
        return buttonPanel;
    }

    private JButton createButton(String resName, char mnemonic, String command, boolean enabled) {
        JButton button = new JButton(JMeterUtils.getResString(resName));
        button.setMnemonic(mnemonic);
        button.setActionCommand(command);
        button.setEnabled(enabled);
        button.addActionListener(this);
        return button;
    }

    private void addServerToTable(String dnsServer) {
        dnsServersTableModel.addRow(new Object[] {
            dnsServer });
    }

    private void addHostToTable(StaticHost hostEntry) {
        LOGGER.debug("Adding entry {}", hostEntry);
        dnsHostsTableModel.addRow(new Object[] {
            hostEntry.getName(), hostEntry.getAddress() });
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        String action = e.getActionCommand();

        enableTable(custResButton.isSelected(), sysResButton.isSelected(), dnsServersTable, dnsServersTableModel,
                addButton, deleteButton);
        enableTable(custResButton.isSelected(), sysResButton.isSelected(), dnsHostsTable, dnsHostsTableModel,
                addHostButton, deleteHostButton);

        if (action.equals(DELETE_COMMAND)) {
            deleteTableRow(dnsServersTable, dnsServersTableModel, deleteButton);
        } else if (action.equals(ADD_COMMAND)) {
            addTableRow(dnsServersTable, dnsServersTableModel, deleteButton);
        } else if (DELETE_HOST_COMMAND.equals(action)) {
            deleteTableRow(dnsHostsTable, dnsHostsTableModel, deleteHostButton);
        } else if (ADD_HOST_COMMAND.equals(action)) {
            addTableRow(dnsHostsTable, dnsHostsTableModel, deleteHostButton);
        }
    }

    private static void enableTable(boolean custEnabled, boolean sysEnabled, JTable table, PowerTableModel model,
            JButton addButton, JButton deleteButton) {
        table.setEnabled(custEnabled);
        Color greyColor = new Color(240, 240, 240);
        Color blueColor = new Color(184, 207, 229);
        table.setBackground(sysEnabled ? greyColor : Color.WHITE);
        table.setSelectionBackground(sysEnabled ? greyColor : blueColor);
        addButton.setEnabled(custEnabled);
        deleteButton.setEnabled(custEnabled);
        if (custEnabled && (model.getRowCount() > 0)) {
            deleteButton.setEnabled(true);
            addButton.setEnabled(true);
        }
    }

    private static void addTableRow(JTable table, PowerTableModel model, JButton button) {
        // If a table cell is being edited, we should accept the current
        // value and stop the editing before adding a new row.
        GuiUtils.stopTableEditing(table);

        model.addNewRow();
        model.fireTableDataChanged();

        if (!button.isEnabled()) {
            button.setEnabled(true);
        }

        // Highlight (select) the appropriate row.
        int rowToSelect = model.getRowCount() - 1;
        table.setRowSelectionInterval(rowToSelect, rowToSelect);
    }

    private static void deleteTableRow(JTable table, PowerTableModel model, JButton button) {
        if (model.getRowCount() > 0) {
            // If a table cell is being edited, we must cancel the editing
            // before deleting the row.
            GuiUtils.cancelEditing(table);

            int rowSelected = table.getSelectedRow();

            if (rowSelected != -1) {
                model.removeRow(rowSelected);
                model.fireTableDataChanged();

                if (model.getRowCount() == 0) {
                    button.setEnabled(false);
                }

                else {
                    int rowToSelect = Math.min(rowSelected, model.getRowCount() - 1);
                    table.setRowSelectionInterval(rowToSelect, rowToSelect);
                }
            }
        }
    }
}
