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

import java.awt.EventQueue;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.swing.AbstractListModel;
import javax.swing.JPanel;
import javax.swing.ListSelectionModel;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.netbeans.modules.cordova.updatetask.CordovaPlugin;

/**
 * UI for selecting Cordova Plugins.
 */
public final class PluginsPanel extends JPanel {

    // folder path is accessed outside of EDT thread
    private volatile boolean panelEnabled = true;

    private PluginsListModel selectedPluginsModel;
    private PluginsListModel allPluginsModel;

    public PluginsPanel() {
        checkUiThread();
        initComponents();
    }
   
    public void init(List<CordovaPlugin> current, List<CordovaPlugin> all) {
        checkUiThread();
        initComponents();
        pluginsFilterTextField.setVisible(false);
        selectedPluginsModel = new PluginsListModel(current);
        selectedPluginsModel.sortLibraries();
        all.removeAll(current); //remove already added plugins from available list
        allPluginsModel = new PluginsListModel(all);
        allPluginsModel.sortLibraries();
        initPlugins();
    }

    /**
     * Get the list of selected Plugins.
     * @return list of selected Plugins
     */
    public List<CordovaPlugin> getSelectedPlugins() {
        return selectedPluginsModel.plugins;
    }


    /**
     * Lock this panel, it means no user changes can be done.
     * <p>
     * This method must be run in the UI thread.
     * @see #unlockPanel()
     */
    public void lockPanel() {
        checkUiThread();
        enablePanel(false);
    }

    /**
     * Unlock this panel, it means no user changes can be done.
     * <p>
     * This method must be run in the UI thread.
     * @see #lockPanel()
     */
    public void unlockPanel() {
        checkUiThread();
        enablePanel(true);
    }


    private void checkUiThread() {
        if (!EventQueue.isDispatchThread()) {
            throw new IllegalStateException("Must be run in UI thread");
        }
    }

    private void initPlugins() {
        initAllPluginsList();
        initSelectedPluginsList();
        updateButtonsEnabled();
    }
    

    private void initAllPluginsList() {
        assert EventQueue.isDispatchThread();
        pluginsList.setModel(allPluginsModel);
        pluginsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        pluginsFilterTextField.getDocument().addDocumentListener(new DocumentListener() {
            @Override
            public void insertUpdate(DocumentEvent e) {
                processChange();
            }
            @Override
            public void removeUpdate(DocumentEvent e) {
                processChange();
            }
            @Override
            public void changedUpdate(DocumentEvent e) {
                processChange();
            }
            private void processChange() {
                //filterLibrariesTable();
            }
        });
    }

    private void initSelectedPluginsList() {
        assert EventQueue.isDispatchThread();
        selectedPluginsList.setModel(selectedPluginsModel);
        selectedPluginsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    }

    private boolean isUpdateRunning() {
        return !panelEnabled;
    }

    private void startUpdate() {
        lockPanel();
    }

    void finishUpdate() {
        unlockPanel();
    }

    private void enablePanel(boolean enabled) {
        assert EventQueue.isDispatchThread();
        panelEnabled = enabled;
        pluginsFilterTextField.setEnabled(enabled);
        pluginsList.setEnabled(enabled);
        selectSelectedButton.setEnabled(enabled);
        deselectSelectedButton.setEnabled(enabled);
        selectedPluginsList.setEnabled(enabled);
    }


    /**
     * 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.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        generalInfoLabel = new javax.swing.JLabel();
        librariesLabel = new javax.swing.JLabel();
        pluginsFilterTextField = new javax.swing.JTextField();
        selectSelectedButton = new javax.swing.JButton();
        deselectSelectedButton = new javax.swing.JButton();
        selectedLabel = new javax.swing.JLabel();
        selectedLibrariesScrollPane = new javax.swing.JScrollPane();
        selectedPluginsList = new javax.swing.JList();
        pluginsScrollPane = new javax.swing.JScrollPane();
        pluginsList = new javax.swing.JList();
        jLabel1 = new javax.swing.JLabel();

        org.openide.awt.Mnemonics.setLocalizedText(generalInfoLabel, org.openide.util.NbBundle.getMessage(PluginsPanel.class, "PluginsPanel.generalInfoLabel.text")); // NOI18N

        org.openide.awt.Mnemonics.setLocalizedText(librariesLabel, org.openide.util.NbBundle.getMessage(PluginsPanel.class, "PluginsPanel.librariesLabel.text")); // NOI18N

        org.openide.awt.Mnemonics.setLocalizedText(selectSelectedButton, org.openide.util.NbBundle.getMessage(PluginsPanel.class, "PluginsPanel.selectSelectedButton.text")); // NOI18N
        selectSelectedButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                selectSelectedButtonActionPerformed(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(deselectSelectedButton, org.openide.util.NbBundle.getMessage(PluginsPanel.class, "PluginsPanel.deselectSelectedButton.text")); // NOI18N
        deselectSelectedButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                deselectSelectedButtonActionPerformed(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(selectedLabel, org.openide.util.NbBundle.getMessage(PluginsPanel.class, "PluginsPanel.selectedLabel.text")); // NOI18N

        selectedPluginsList.addListSelectionListener(new javax.swing.event.ListSelectionListener() {
            public void valueChanged(javax.swing.event.ListSelectionEvent evt) {
                selectedPluginsListValueChanged(evt);
            }
        });
        selectedLibrariesScrollPane.setViewportView(selectedPluginsList);

        pluginsList.addListSelectionListener(new javax.swing.event.ListSelectionListener() {
            public void valueChanged(javax.swing.event.ListSelectionEvent evt) {
                pluginsListValueChanged(evt);
            }
        });
        pluginsScrollPane.setViewportView(pluginsList);

        org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(PluginsPanel.class, "PluginsPanel.jLabel1.text")); // NOI18N

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(generalInfoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 591, Short.MAX_VALUE)
                    .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addGroup(layout.createSequentialGroup()
                                .addComponent(librariesLabel)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(pluginsFilterTextField))
                            .addGroup(layout.createSequentialGroup()
                                .addContainerGap()
                                .addComponent(pluginsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(selectSelectedButton)
                            .addComponent(deselectSelectedButton))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(selectedLabel)
                            .addComponent(selectedLibrariesScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))))
                .addContainerGap())
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {deselectSelectedButton, selectSelectedButton});

        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addComponent(generalInfoLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(librariesLabel)
                    .addComponent(selectedLabel)
                    .addComponent(pluginsFilterTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(selectedLibrariesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 285, Short.MAX_VALUE)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(selectSelectedButton)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(deselectSelectedButton)
                        .addGap(0, 0, Short.MAX_VALUE))
                    .addComponent(pluginsScrollPane))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        );
    }// </editor-fold>//GEN-END:initComponents

    private void selectSelectedButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectSelectedButtonActionPerformed
        selectedPluginsModel.add(pluginsList.getSelectedValuesList());
        allPluginsModel.remove(pluginsList.getSelectedValuesList());
        updateButtonsEnabled();
    }//GEN-LAST:event_selectSelectedButtonActionPerformed

    private void deselectSelectedButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deselectSelectedButtonActionPerformed
        allPluginsModel.add(selectedPluginsList.getSelectedValuesList());
        selectedPluginsModel.remove(selectedPluginsList.getSelectedValuesList());
        updateButtonsEnabled();
    }//GEN-LAST:event_deselectSelectedButtonActionPerformed

    private void pluginsListValueChanged(javax.swing.event.ListSelectionEvent evt) {//GEN-FIRST:event_pluginsListValueChanged
        updateButtonsEnabled();
    }//GEN-LAST:event_pluginsListValueChanged

    private void selectedPluginsListValueChanged(javax.swing.event.ListSelectionEvent evt) {//GEN-FIRST:event_selectedPluginsListValueChanged
        updateButtonsEnabled();
    }//GEN-LAST:event_selectedPluginsListValueChanged

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton deselectSelectedButton;
    private javax.swing.JLabel generalInfoLabel;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel librariesLabel;
    private javax.swing.JTextField pluginsFilterTextField;
    private javax.swing.JList pluginsList;
    private javax.swing.JScrollPane pluginsScrollPane;
    private javax.swing.JButton selectSelectedButton;
    private javax.swing.JLabel selectedLabel;
    private javax.swing.JScrollPane selectedLibrariesScrollPane;
    private javax.swing.JList selectedPluginsList;
    // End of variables declaration//GEN-END:variables

    private void updateButtonsEnabled() {
        selectSelectedButton.setEnabled(allPluginsModel.getSize() > 0 && pluginsList.getSelectedIndex() != -1 && pluginsList.getSelectedIndex() < allPluginsModel.getSize());
        deselectSelectedButton.setEnabled(selectedPluginsModel.getSize() > 0 && selectedPluginsList.getSelectedIndex() != -1 && selectedPluginsList.getSelectedIndex() < selectedPluginsModel.getSize());
    }

    private static final class PluginsListModel extends AbstractListModel {

        private static final Comparator<CordovaPlugin> SELECTED_PLUGINS_COMPARATOR = new Comparator<CordovaPlugin>() {
            @Override
            public int compare(CordovaPlugin left, CordovaPlugin right) {
                return left.getName().compareToIgnoreCase(right.getName());
            }
        };

        private final List<CordovaPlugin> plugins;


        public PluginsListModel(List<CordovaPlugin> plugins) {
            this.plugins = plugins;
        }

        @Override
        public int getSize() {
            return plugins.size();
        }

        @Override
        public CordovaPlugin getElementAt(int index) {
            return plugins.get(index);
        }

        public void fireContentsChanged() {
            sortLibraries();
            fireContentsChanged(this, 0, plugins.size() - 1);
        }
        
        public void add(List<CordovaPlugin> add) {
            for (CordovaPlugin p:add) {
                if (!plugins.contains(p)) {
                    plugins.add(p);
                }
            }
            sortLibraries();
            fireContentsChanged();
        }
        
        public void remove(List<CordovaPlugin> rem) {
            plugins.removeAll(rem);
            sortLibraries();
            fireContentsChanged();
        }

        /**
         * Make selected plugins unique and sort them.
         */
        private void sortLibraries() {
            Collections.sort(plugins, SELECTED_PLUGINS_COMPARATOR);
        }

    }
}
