/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2021 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed 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.jboss.installer.screens;

import org.jboss.installer.common.FontResources;
import org.jboss.installer.core.InstallationData;
import org.jboss.installer.core.LanguageUtils;
import org.jboss.installer.core.Screen;
import org.jboss.installer.core.ScreenManager;
import org.jboss.installer.core.ValidationResult;
import org.jboss.installer.postinstall.PostInstallTask;
import org.jboss.installer.postinstall.task.PortBindingConfig;
import org.jboss.installer.validators.PortOffsetValidator;

import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import java.awt.Component;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ItemEvent;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

public class PortConfigurationScreen extends DefaultScreen{

    public static final String NAME = "PortConfigurationScreen";
    public static final String TITLE_KEY="port_bindings.title";
    public static final String SIDE_NAV_NAME_KEY = "port_bindings.side_nav_name";
    public static final String MAIN_DESCRIPTION_KEY = "port_bindings.main_description";
    public static final String CONFIG_DESCRIPTION_KEY = "port_bindings.config_description";
    public static final String FIRST_CONFIG_CHOICE_KEY = "port_bindings.first_config_choice";
    public static final String SECOND_CONFIG_CHOICE_KEY = "port_bindings.second_config_choice";
    public static final String THIRD_CONFIG_CHOICE_KEY = "port_bindings.third_config_choice";
    public static final String OFFSET_SELECTION_KEY = "port_bindings.offset_selection";
    public static final String FIRST_OFFSET_TYPE_KEY = "port_bindings.first_offset_type";
    public static final String SECOND_OFFSET_TYPE_KEY = "port_bindings.second_offset_type";
    public static final String THIRD_OFFSET_TYPE_KEY = "port_bindings.third_offset_type";
    public static final String OFFSET_VALUE_KEY = "port_bindings.offset_value";
    public static final String CUSTOM_STANDALONE_CONFIG_KEY = "port_bindings.custom_standalone_config";
    public static final String CUSTOM_DOMAIN_CONFIG_KEY = "port_bindings.custom_domain_config";
    public static final String IPV6_SUPPORT_KEY = "port_bindings.ipv6_support";
    public static final String ENABLE_IPV6_KEY = "port_bindings.enable_ipv6";
    public static final String MODE_NOT_SELECTED_ERROR_KEY = "port_bindings.mode_not_selected_error";
    public static final List<String> DOMAIN_PORT_SCREEN_NAMES = Arrays.asList(DomainHostPortConfigurationScreen.NAME,
            DomainDefaultPortConfigurationScreen.NAME, DomainHaPortConfigurationScreen.NAME,
            DomainFullPortConfigurationScreen.NAME, DomainFullHaPortConfigurationScreen.NAME);
    public static final List<String> STANDALONE_PORT_SCREEN_NAMES = Arrays.asList(StandalonePortConfigurationScreen.NAME,
            StandaloneHaPortConfigurationScreen.NAME, StandaloneFullPortConfigurationScreen.NAME,
            StandaloneFullHaPortConfigurationScreen.NAME);
    public static final Insets RADIO_BUTTON_INSET = new Insets(0, 40, 0, 0);
    public static final Insets PORT_CONFIG_CHOICE_INSET = new Insets(0, 60, 0, 0);
    private final JLabel configDescription = createFieldLabel(CONFIG_DESCRIPTION_KEY);
    private final JLabel mainDescription = createFieldLabel(MAIN_DESCRIPTION_KEY);
    private final JRadioButton defaultConfigButton = createRadioButton(langUtils.getString(FIRST_CONFIG_CHOICE_KEY), true);
    private final JRadioButton offsetConfigButton = createRadioButton(langUtils.getString(SECOND_CONFIG_CHOICE_KEY), false);
    private final JRadioButton customConfigButton = createRadioButton(langUtils.getString(THIRD_CONFIG_CHOICE_KEY), false);
    private final JLabel offsetChoiceDescription = createFieldLabel(OFFSET_SELECTION_KEY);
    private final JRadioButton firstOffsetType = createRadioButton(langUtils.getString(FIRST_OFFSET_TYPE_KEY), true);
    private final JRadioButton secondOffsetType = createRadioButton(langUtils.getString(SECOND_OFFSET_TYPE_KEY), false);
    private final JRadioButton customOffsetType = createRadioButton(langUtils.getString(THIRD_OFFSET_TYPE_KEY), false);
    private final JLabel offsetLabel = createFieldLabel(OFFSET_VALUE_KEY);
    private final JFormattedTextField offsetValue = new JFormattedTextField(getIntegerValueFormatter(0, PortOffsetValidator.MAX_PORT_VALUE)); //this is only theoretical maximum, the field is further restricted by computed max offset given current max port value used in server configs
    private final JCheckBox standaloneCustomConfig = createCheckBox(CUSTOM_STANDALONE_CONFIG_KEY, false, false);
    private final JCheckBox domainCustomConfig = createCheckBox(CUSTOM_DOMAIN_CONFIG_KEY, false, false);
    private final JLabel ipv6Label = createFieldLabel(IPV6_SUPPORT_KEY);
    private final JCheckBox ipv6Enable = createCheckBox(ENABLE_IPV6_KEY, true, false);

    public PortConfigurationScreen(Screen parent, LanguageUtils langUtils, boolean isActive) {
        super(parent, langUtils, isActive);
    }

    @Override
    public String getTitle() {
        return langUtils.getString(TITLE_KEY);
    }

    @Override
    public String getSideNavName() {
        return langUtils.getString(SIDE_NAV_NAME_KEY);
    }

    @Override
    public String getName() {
        return NAME;
    }

    @Override
    public JPanel getContent() {
        JPanel content = new JPanel();
        content.setLayout(new GridBagLayout());
        GridBagConstraints c = initializeConstraints();
        c.gridwidth = 3;

        setFont();
        configRadioButtonsSetup();
        offsetRadioButtonSetup();

        addComponent(content, mainDescription, c);
        addComponent(content, emptyRow(), c);
        addComponent(content, configDescription, c);

        c.insets = RADIO_BUTTON_INSET;
        addComponent(content, defaultConfigButton, c);
        addComponent(content, offsetConfigButton, c);
        addComponent(content, customConfigButton, c);

        addComponent(content, emptyRow(), c);
        c.insets = RADIO_BUTTON_INSET;
        addComponent(content, offsetChoiceDescription,c);

        c.insets = PORT_CONFIG_CHOICE_INSET;
        addComponent(content, firstOffsetType, c);
        addComponent(content, secondOffsetType, c);
        addComponent(content, customOffsetType, c);

        c.insets = new Insets(0, 100, 10, 0);
        c.gridwidth = 1;
        c.weightx = 0.1;
        content.add(offsetLabel, c);
        offsetLabel.setToolTipText(langUtils.getString(OFFSET_VALUE_KEY + ".tooltip"));

        c.gridx = 1;
        c.insets = new Insets(0, 0, 10, 0);
        c.weightx = 0.1;
        content.add(offsetValue, c);
        offsetValue.setToolTipText(langUtils.getString(OFFSET_VALUE_KEY + ".tooltip"));
        c.gridy++;

        c.gridx = 2;
        c.weightx = 0.8;
        content.add(Box.createHorizontalBox(), c);
        offsetValue.setToolTipText(langUtils.getString(OFFSET_VALUE_KEY + ".tooltip"));
        c.gridy++;

        c.gridwidth = 3;
        c.insets = PORT_CONFIG_CHOICE_INSET;
        c.gridx=0;
        addComponent(content, standaloneCustomConfig, c);
        addComponent(content, domainCustomConfig, c);

        addComponent(content, emptyRow(), c);
        c.insets = NO_INSET;
        addComponent(content, ipv6Label, c);
        c.insets = SUBSECTION_INSET;
        addComponent(content, ipv6Enable, c);
        c.gridy++;
        fillEmptySpace(content, c);

        return content;
    }

    @Override
    public JComponent getDefaultFocusComponent() {
        return defaultConfigButton;
    }

    private void addComponent(JPanel content, Component component , GridBagConstraints c) {
        content.add(component, c);
        c.gridy++;
    }

    private void setFont() {
        Font font = FontResources.getOpenSansRegular();
        configDescription.setFont(font);
        mainDescription.setFont(font);
        defaultConfigButton.setFont(font);
        offsetConfigButton.setFont(font);
        customConfigButton.setFont(font);
        offsetChoiceDescription.setFont(font);
        firstOffsetType.setFont(font);
        secondOffsetType.setFont(font);
        customOffsetType.setFont(font);
        offsetLabel.setFont(font);
        offsetValue.setFont(font);
        standaloneCustomConfig.setFont(font);
        domainCustomConfig.setFont(font);
        ipv6Label.setFont(font);
        ipv6Enable.setFont(font);
    }

    private void configRadioButtonsSetup() {
        ButtonGroup buttonGroup = new ButtonGroup();
        buttonGroup.add(defaultConfigButton);
        buttonGroup.add(offsetConfigButton);
        buttonGroup.add(customConfigButton);

        defaultConfigButton.addItemListener(itemEvent -> {
            if (itemEvent.getStateChange() == ItemEvent.SELECTED) {
                enableCustomPortConfig(false);
                enableOffsetPortConfig(false);
            }
        });

        offsetConfigButton.addItemListener(itemEvent -> {
            enableOffsetPortConfig(itemEvent.getStateChange() == ItemEvent.SELECTED);
        });

        customConfigButton.addItemListener(itemEvent -> {
            enableCustomPortConfig(itemEvent.getStateChange() == ItemEvent.SELECTED);
        });
    }

    private void offsetRadioButtonSetup() {
        ButtonGroup buttonGroup = new ButtonGroup();
        buttonGroup.add(firstOffsetType);
        buttonGroup.add(secondOffsetType);
        buttonGroup.add(customOffsetType);

        customOffsetType.addItemListener(itemEvent -> {
            enableOffsetPortConfig(true);
        });
    }

    private void enableCustomPortConfig(boolean flag) {
        standaloneCustomConfig.setEnabled(flag);
        domainCustomConfig.setEnabled(flag);
    }

    private void enableOffsetPortConfig(boolean flag) {
        firstOffsetType.setEnabled(flag);
        secondOffsetType.setEnabled(flag);
        customOffsetType.setEnabled(flag);
        offsetLabel.setEnabled(flag && customOffsetType.isSelected());
        offsetValue.setEnabled(flag && customOffsetType.isSelected());
    }


    private void setPortOffset(InstallationData installationData) {
        if (firstOffsetType.isSelected()) {
            installationData.putConfig(new PortBindingConfig(1));
        }
        if (secondOffsetType.isSelected()) {
            installationData.putConfig(new PortBindingConfig(100));
        }
        if (customOffsetType.isSelected()) {
            installationData.putConfig(new PortBindingConfig(Integer.parseInt(offsetValue.getText())));
        }
        installationData.addPostInstallTask(PostInstallTask.ChangePortConfiguration);
        installationData.addPostInstallTask(PostInstallTask.UpdateJBossCliPort);
    }

    private void activateSelectedCustomConfigs(ScreenManager screenManager, InstallationData installationData) {
        if (standaloneCustomConfig.isSelected()) {
            screenManager.setScreensActive(STANDALONE_PORT_SCREEN_NAMES);
        }
        if (domainCustomConfig.isSelected()) {
            screenManager.setScreensActive(DOMAIN_PORT_SCREEN_NAMES);
        }
        installationData.addPostInstallTask(PostInstallTask.ChangePortConfiguration);
        installationData.putConfig(new PortBindingConfig(0));
        installationData.addPostInstallTask(PostInstallTask.UpdateJBossCliPort);
    }

    @Override
    public void load(InstallationData installationData, ScreenManager screenManager) {
        screenManager.setScreensInactive(STANDALONE_PORT_SCREEN_NAMES);
        screenManager.setScreensInactive(DOMAIN_PORT_SCREEN_NAMES);

        enableOffsetPortConfig(offsetConfigButton.isSelected());
        enableCustomPortConfig(customConfigButton.isSelected());
    }

    @Override
    public void rollback(InstallationData installationData) {
        installationData.removePostInstallTask(PostInstallTask.EnableIpv6);
        installationData.removePostInstallTask(PostInstallTask.ChangePortConfiguration);
        installationData.removePostInstallTask(PostInstallTask.UpdateJBossCliPort);
        installationData.removeConfig(PortBindingConfig.class);
    }

    @Override
    public ValidationResult validate() {
        if (customConfigButton.isSelected() && !domainCustomConfig.isSelected() && !standaloneCustomConfig.isSelected()) {
            return ValidationResult.error(langUtils.getString(MODE_NOT_SELECTED_ERROR_KEY));
        }
        if (customOffsetType.isSelected() && customOffsetType.isEnabled()) {
            return new PortOffsetValidator(langUtils).validate(offsetValue.getText());
        }
        return ValidationResult.ok();
    }

    @Override
    public void record(InstallationData installationData, ScreenManager screenManager) {
        if (offsetConfigButton.isSelected()) {
            setPortOffset(installationData);
        } else if (customConfigButton.isSelected()){
            activateSelectedCustomConfigs(screenManager, installationData);
        }
        if (ipv6Enable.isSelected()) {
            installationData.addPostInstallTask(PostInstallTask.EnableIpv6);
        }

        final PortBindingConfig config = installationData.getConfig(PortBindingConfig.class);
        final HashMap<String, String> attrs = new HashMap<>();

        if (defaultConfigButton.isSelected()) {
            attrs.put(langUtils.getString("port_bindings.summary.config_type"), langUtils.getString(FIRST_CONFIG_CHOICE_KEY));
        } else if (offsetConfigButton.isSelected()) {
            attrs.put(langUtils.getString("port_bindings.summary.config_type"), langUtils.getString(SECOND_CONFIG_CHOICE_KEY));
            attrs.put(langUtils.getString(OFFSET_VALUE_KEY), config.getOffset() + "");
        } else {
            attrs.put(langUtils.getString("port_bindings.summary.config_type"), langUtils.getString(THIRD_CONFIG_CHOICE_KEY));
            attrs.put(ConfigSummaryPanel.NOTICE, langUtils.getString("summary.too_many_items"));
        }
        attrs.put(langUtils.getString(ENABLE_IPV6_KEY), "" + ipv6Enable.isSelected());
        installationData.updateSummary(PortConfigurationScreen.NAME, langUtils.getString(ConfigureRuntimeEnvironmentScreen.PORT_BINDINGS_KEY), attrs);
    }
}
