/*
 * 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.netbeans.modules.micronaut.db;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.swing.JPanel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.spi.project.ui.templates.support.Templates;
import org.openide.WizardDescriptor;
import org.openide.util.ChangeSupport;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;

/**
 *
 * @author Dusan Balek
 */
public class EntityClassesPanel extends javax.swing.JPanel {

    private final ChangeSupport changeSupport = new ChangeSupport(this);
    private final Set<String> availableEntities = new HashSet<>();
    private final Set<String> selectedEntities = new HashSet<>();

    public EntityClassesPanel() {
        initComponents();
        ListSelectionListener selectionListener = new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {
                updateButtons();
            }
        };
        availableEntitiesList.getSelectionModel().addListSelectionListener(selectionListener);
        selectedEntitiesList.getSelectionModel().addListSelectionListener(selectionListener);
    }

    public void addChangeListener(ChangeListener listener) {
        changeSupport.addChangeListener(listener);
    }

    public void initialize(Set<String> entities) {
        availableEntities.addAll(entities);
        availableEntitiesList.setListData(availableEntities.toArray(new String[availableEntities.size()]));
        selectedEntitiesList.setListData(selectedEntities.toArray(new String[selectedEntities.size()]));
        updateButtons();
    }

    public Set<String> getSelectedEntities() {
        return selectedEntities;
    }

    private void updateButtons() {
        addButton.setEnabled(availableEntitiesList.getSelectedIndices().length > 0);
        addAllButton.setEnabled(!availableEntities.isEmpty());
        removeButton.setEnabled(selectedEntitiesList.getSelectedIndices().length > 0);
        removeAllButton.setEnabled(!selectedEntities.isEmpty());
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        entitiesPanel = new EntitiesPanel();
        availableEntitiesLabel = new javax.swing.JLabel();
        availableEntitiesScrollPane = new javax.swing.JScrollPane();
        availableEntitiesList = new javax.swing.JList<>();
        selectedEntitiesLabel = new javax.swing.JLabel();
        selectedEntitiesScrollPane = new javax.swing.JScrollPane();
        selectedEntitiesList = new javax.swing.JList<>();
        buttonPanel = new javax.swing.JPanel();
        addButton = new javax.swing.JButton();
        removeButton = new javax.swing.JButton();
        addAllButton = new javax.swing.JButton();
        removeAllButton = new javax.swing.JButton();

        setMinimumSize(new java.awt.Dimension(200, 300));
        setName(org.openide.util.NbBundle.getMessage(EntityClassesPanel.class, "LBL_EntityClasses")); // NOI18N
        setPreferredSize(new java.awt.Dimension(496, 350));
        setLayout(new java.awt.GridBagLayout());

        entitiesPanel.setPreferredSize(new java.awt.Dimension(440, 174));
        entitiesPanel.setLayout(new java.awt.GridBagLayout());

        availableEntitiesLabel.setLabelFor(availableEntitiesList);
        org.openide.awt.Mnemonics.setLocalizedText(availableEntitiesLabel, org.openide.util.NbBundle.getMessage(EntityClassesPanel.class, "LBL_AvailableEntities")); // NOI18N
        availableEntitiesLabel.setToolTipText(org.openide.util.NbBundle.getMessage(EntityClassesPanel.class, "TXT_AvailableEntities")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 0);
        entitiesPanel.add(availableEntitiesLabel, gridBagConstraints);

        availableEntitiesScrollPane.setPreferredSize(new java.awt.Dimension(160, 130));

        availableEntitiesList.setNextFocusableComponent(addButton);
        availableEntitiesScrollPane.setViewportView(availableEntitiesList);
        availableEntitiesList.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(EntityClassesPanel.class, "ACSN_AvailableEntities")); // NOI18N
        availableEntitiesList.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(EntityClassesPanel.class, "ACSD_AvailableEntities")); // NOI18N

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        entitiesPanel.add(availableEntitiesScrollPane, gridBagConstraints);

        selectedEntitiesLabel.setLabelFor(selectedEntitiesList);
        org.openide.awt.Mnemonics.setLocalizedText(selectedEntitiesLabel, org.openide.util.NbBundle.getMessage(EntityClassesPanel.class, "LBL_SelectedEntities")); // NOI18N
        selectedEntitiesLabel.setToolTipText(org.openide.util.NbBundle.getMessage(EntityClassesPanel.class, "TXT_SelectedEntities")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 0);
        entitiesPanel.add(selectedEntitiesLabel, gridBagConstraints);

        selectedEntitiesScrollPane.setPreferredSize(new java.awt.Dimension(160, 130));
        selectedEntitiesScrollPane.setViewportView(selectedEntitiesList);
        selectedEntitiesList.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(EntityClassesPanel.class, "ACSN_SelectedEntities")); // NOI18N
        selectedEntitiesList.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(EntityClassesPanel.class, "ACSD_SelectedEntities")); // NOI18N

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        entitiesPanel.add(selectedEntitiesScrollPane, gridBagConstraints);

        buttonPanel.setLayout(new java.awt.GridBagLayout());

        org.openide.awt.Mnemonics.setLocalizedText(addButton, org.openide.util.NbBundle.getMessage(EntityClassesPanel.class, "LBL_Add")); // NOI18N
        addButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addButtonActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        buttonPanel.add(addButton, gridBagConstraints);

        org.openide.awt.Mnemonics.setLocalizedText(removeButton, org.openide.util.NbBundle.getMessage(EntityClassesPanel.class, "LBL_Remove")); // NOI18N
        removeButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                removeButtonActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.insets = new java.awt.Insets(5, 0, 0, 0);
        buttonPanel.add(removeButton, gridBagConstraints);

        org.openide.awt.Mnemonics.setLocalizedText(addAllButton, org.openide.util.NbBundle.getMessage(EntityClassesPanel.class, "LBL_AddAll")); // NOI18N
        addAllButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addAllButtonActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.insets = new java.awt.Insets(17, 0, 0, 0);
        buttonPanel.add(addAllButton, gridBagConstraints);

        org.openide.awt.Mnemonics.setLocalizedText(removeAllButton, org.openide.util.NbBundle.getMessage(EntityClassesPanel.class, "LBL_RemoveAll")); // NOI18N
        removeAllButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                removeAllButtonActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.insets = new java.awt.Insets(5, 0, 0, 0);
        buttonPanel.add(removeAllButton, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridheight = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.insets = new java.awt.Insets(0, 11, 0, 11);
        entitiesPanel.add(buttonPanel, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.ipady = 80;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weighty = 2.0;
        gridBagConstraints.insets = new java.awt.Insets(6, 0, 0, 0);
        add(entitiesPanel, gridBagConstraints);
    }// </editor-fold>//GEN-END:initComponents

    private void removeAllButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeAllButtonActionPerformed
        availableEntities.addAll(selectedEntities);
        selectedEntities.clear();
        availableEntitiesList.setListData(availableEntities.toArray(new String[availableEntities.size()]));
        selectedEntitiesList.setListData(selectedEntities.toArray(new String[selectedEntities.size()]));
        updateButtons();
        changeSupport.fireChange();
    }//GEN-LAST:event_removeAllButtonActionPerformed

    private void addAllButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addAllButtonActionPerformed
        selectedEntities.addAll(availableEntities);
        availableEntities.clear();
        availableEntitiesList.setListData(availableEntities.toArray(new String[availableEntities.size()]));
        selectedEntitiesList.setListData(selectedEntities.toArray(new String[selectedEntities.size()]));
        updateButtons();
        changeSupport.fireChange();
    }//GEN-LAST:event_addAllButtonActionPerformed

    private void removeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeButtonActionPerformed
        availableEntities.addAll(selectedEntitiesList.getSelectedValuesList());
        selectedEntities.removeAll(selectedEntitiesList.getSelectedValuesList());
        availableEntitiesList.setListData(availableEntities.toArray(new String[availableEntities.size()]));
        selectedEntitiesList.setListData(selectedEntities.toArray(new String[selectedEntities.size()]));
        updateButtons();
        changeSupport.fireChange();
    }//GEN-LAST:event_removeButtonActionPerformed

    private void addButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addButtonActionPerformed
        selectedEntities.addAll(availableEntitiesList.getSelectedValuesList());
        availableEntities.removeAll(availableEntitiesList.getSelectedValuesList());
        availableEntitiesList.setListData(availableEntities.toArray(new String[availableEntities.size()]));
        selectedEntitiesList.setListData(selectedEntities.toArray(new String[selectedEntities.size()]));
        updateButtons();
        changeSupport.fireChange();
    }//GEN-LAST:event_addButtonActionPerformed


    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton addAllButton;
    private javax.swing.JButton addButton;
    private javax.swing.JLabel availableEntitiesLabel;
    private javax.swing.JList<String> availableEntitiesList;
    private javax.swing.JScrollPane availableEntitiesScrollPane;
    private javax.swing.JPanel buttonPanel;
    private javax.swing.JPanel entitiesPanel;
    private javax.swing.JButton removeAllButton;
    private javax.swing.JButton removeButton;
    private javax.swing.JLabel selectedEntitiesLabel;
    private javax.swing.JList<String> selectedEntitiesList;
    private javax.swing.JScrollPane selectedEntitiesScrollPane;
    // End of variables declaration//GEN-END:variables

    private final class EntitiesPanel extends JPanel {

        @Override
        public void doLayout() {
            super.doLayout();

            Rectangle availableBounds = availableEntitiesScrollPane.getBounds();
            Rectangle selectedBounds = selectedEntitiesScrollPane.getBounds();

            if (Math.abs(availableBounds.width - selectedBounds.width) > 1) {
                GridBagConstraints buttonPanelConstraints = ((GridBagLayout)getLayout()).getConstraints(buttonPanel);
                int totalWidth = getWidth() - buttonPanel.getWidth() - buttonPanelConstraints.insets.left - buttonPanelConstraints.insets.right;
                int equalWidth = totalWidth / 2;
                int xOffset = equalWidth - availableBounds.width;

                availableBounds.width = equalWidth;
                availableEntitiesScrollPane.setBounds(availableBounds);

                Rectangle buttonBounds = buttonPanel.getBounds();
                buttonBounds.x += xOffset;
                buttonPanel.setBounds(buttonBounds);

                Rectangle labelBounds = selectedEntitiesLabel.getBounds();
                labelBounds.x += xOffset;
                selectedEntitiesLabel.setBounds(labelBounds);

                selectedBounds.x += xOffset;
                selectedBounds.width = totalWidth - equalWidth;
                selectedEntitiesScrollPane.setBounds(selectedBounds);
            }
        }
    }

    public static final class WizardPanel implements WizardDescriptor.Panel<WizardDescriptor>, ChangeListener {

        private final ChangeSupport changeSupport = new ChangeSupport(this);
        private final String title;
        private EntityClassesPanel component;
        private boolean componentInitialized;
        private WizardDescriptor wizardDescriptor;
        private Map<String, String> entities;

        public WizardPanel(String wizardTitle) {
            title = wizardTitle;
        }

        @Override
        public EntityClassesPanel getComponent() {
            if (component == null) {
                component = new EntityClassesPanel();
                component.addChangeListener(this);
            }
            return component;
        }

        @Override
        public HelpCtx getHelp() {
                return new HelpCtx(EntityClassesPanel.class);
        }

        @Override
        public void addChangeListener(ChangeListener listener) {
            changeSupport.addChangeListener(listener);
        }

        @Override
        public void removeChangeListener(ChangeListener listener) {
            changeSupport.removeChangeListener(listener);
        }

        @Override
        public void readSettings(WizardDescriptor settings) {
            wizardDescriptor = settings;
            if (title != null) {
                wizardDescriptor.putProperty("NewFileWizard_Title", title); // NOI18N
            }
            if (!componentInitialized) {
                componentInitialized = true;
                entities = (Map<String, String>) settings.getProperty(MicronautRepository.PROP_ENTITIES);
                getComponent().initialize(entities.keySet());
            }
        }

        @Override
        public boolean isValid() {
            if (entities.isEmpty()) {
                setErrorMessage(NbBundle.getMessage(EntityClassesPanel.class, "ERR_NoEntities", ProjectUtils.getInformation(Templates.getProject(wizardDescriptor)).getDisplayName()));
                return false;
            }
            if (getComponent().getSelectedEntities().isEmpty()) {
                setErrorMessage(NbBundle.getMessage(EntityClassesPanel.class, "ERR_SelectEntities"));
                return false;
            }
            setErrorMessage(" "); // NOI18N
            return true;
        }

        @Override
        public void storeSettings(WizardDescriptor settings) {
            Set<String> selected = getComponent().getSelectedEntities();
            Map<String, String> selEntities = entities.entrySet().stream().filter(entry -> selected.contains(entry.getKey())).collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue()));
            wizardDescriptor.putProperty(MicronautRepository.PROP_SELECTED_ENTITIES, selEntities);
        }

        @Override
        public void stateChanged(ChangeEvent event) {
            changeSupport.fireChange();
        }

        private void setErrorMessage(String errorMessage) {
            wizardDescriptor.putProperty(WizardDescriptor.PROP_ERROR_MESSAGE, errorMessage);
        }
    }
}
