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

import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.beans.BeanInfo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.api.fileinfo.NonRecursiveFolder;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectInformation;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.api.project.ui.OpenProjects;
import org.netbeans.modules.analysis.spi.Analyzer.AnalyzerFactory;
import org.netbeans.modules.analysis.spi.Analyzer.Context;
import org.netbeans.modules.analysis.spi.Analyzer.MissingPlugin;
import org.netbeans.modules.analysis.spi.Analyzer.WarningDescription;
import org.netbeans.modules.analysis.ui.AdjustConfigurationPanel;
import org.netbeans.modules.analysis.ui.AdjustConfigurationPanel.ErrorListener;
import org.netbeans.modules.analysis.ui.ConfigurationsComboModel;
import org.netbeans.modules.refactoring.api.Scope;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.NotificationLineSupport;
import org.openide.NotifyDescriptor;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataFolder;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.nodes.Node;
import org.openide.util.ImageUtilities;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.NbBundle.Messages;
import org.openide.util.NbPreferences;
import org.openide.util.RequestProcessor;

/**
 *
 * @author lahvac
 */
public final class RunAnalysisPanel extends javax.swing.JPanel implements LookupListener {

    private static final String COMBO_PROTOTYPE = "999999999999999999999999999999999999999999999999999999999999";
    private static final RequestProcessor WORKER = new RequestProcessor(RunAnalysisPanel.class.getName(), 1, false, false);
    private final JPanel progress;
    private final RequiredPluginsPanel requiredPlugins;
    //GuardedBy(AWT)
    private       Collection<? extends AnalyzerFactory> analyzers;
    //GuardedBy(AWT)
    private       FutureWarnings analyzer2Warnings;
    private final Lookup.Result<AnalyzerFactory> analyzersResult;
    private final Map<String, AnalyzerAndWarning> warningId2Description = new HashMap<String, AnalyzerAndWarning>();
    private final JButton runAnalysis;
    //GuardedBy(AWT)
    private       boolean inspectionsReady;
    private       boolean started;

    public RunAnalysisPanel(ProgressHandle handle, Lookup context, JButton runAnalysis) {
        this(handle, context, runAnalysis, null);
    }
    
    public RunAnalysisPanel(ProgressHandle handle, Lookup context, JButton runAnalysis, DialogState state) {
        this.runAnalysis = runAnalysis;
        this.analyzersResult = Lookup.getDefault().lookupResult(AnalyzerFactory.class);
        this.analyzersResult.addLookupListener(this);
        
        initComponents();

        List<ScopeDescription> scopes = new ArrayList<ScopeDescription>();
        Icon currentProjectIcon = null;
        NonRecursiveFolder pack = context.lookup(NonRecursiveFolder.class);
        FileObject currentFile = context.lookup(FileObject.class);
        DataFolder folder = context.lookup(DataFolder.class);

        if (currentFile != null && currentFile.isData()) {
            scopes.add(new FileScopeDescription(currentFile));
        }
        
        if (folder != null) {
            scopes.add(new FolderScopeDescription(folder));
        }
        
        if (pack != null && currentFile == null) {
            currentFile = pack.getFolder();
        }

        if (currentFile != null) {
            Project p = FileOwnerQuery.getOwner(currentFile);

            if (p != null) {
                ProjectInformation pi = ProjectUtils.getInformation(p);

                scopes.add(0, new CurrentProjectScopeDescription(p));
                currentProjectIcon = pi.getIcon();

                if (pack == null) {
                    ClassPath bootCP = ClassPath.getClassPath(currentFile, ClassPath.BOOT);

                    if (bootCP != null) {
                        final FileObject packFO = !currentFile.isFolder() ? currentFile.getParent() : currentFile;
                        pack = new NonRecursiveFolder() {
                            @Override public FileObject getFolder() {
                                return packFO;
                            }
                        };
                    }
                }
            }
        } else {
            Project selected = context.lookup(Project.class);

            if (selected == null) {
                SourceGroup sg = context.lookup(SourceGroup.class);

                if (sg != null) {
                    selected = FileOwnerQuery.getOwner(sg.getRootFolder());
                }
            }

            if (selected == null) {
                DataFolder df = context.lookup(DataFolder.class);

                if (df != null) {
                    selected = FileOwnerQuery.getOwner(df.getPrimaryFile());
                }
            }

            if (selected != null) {
                ProjectInformation pi = ProjectUtils.getInformation(selected);

                scopes.add(0, new CurrentProjectScopeDescription(selected));
                currentProjectIcon = pi.getIcon();
            }
        }

        if (pack != null) {
            ClassPath source = ClassPath.getClassPath(pack.getFolder(), ClassPath.SOURCE);

            if (source != null) {
                String packName = source.getResourceName(pack.getFolder(), '.', false);

                scopes.add(1, new PackageScopeDescription(pack, packName));
            }
        }

        scopes.add(0, new AllProjectsScopeDescription(currentProjectIcon));

        scopeCombo.setModel(new DefaultComboBoxModel(scopes.toArray(new ScopeDescription[0])));
        scopeCombo.setRenderer(new ScopeRenderer());
        scopeCombo.setSelectedIndex(scopes.size() - 1);

        GridBagConstraints gridBagConstraints = new java.awt.GridBagConstraints();

        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.gridwidth = 4;
        gridBagConstraints.gridheight = 1;
        gridBagConstraints.weightx = 1;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(12, 0, 0, 0);
        progress = new JPanel(new CardLayout());
        progress.add(new JPanel(), "empty");
        progress.add(ProgressHandleFactory.createProgressComponent(handle), "progress");
        progress.add(requiredPlugins = new RequiredPluginsPanel(), "plugins");
        add(progress, gridBagConstraints);
        ((CardLayout) progress.getLayout()).show(progress, "empty");

        if (state == null) state = DialogState.load();
        updateConfigurations(state);
        updateEnableDisable();

        setBorder(new EmptyBorder(12, 12, 12, 12));
        
        ConfigurationsManager.getDefault().addChangeListener(new ChangeListener() {
            @Override public void stateChanged(ChangeEvent e) {
                resultChanged(null);
            }
        });
        
        configurationCombo.addActionListener(new ActionListener() {
            Object currentItem = configurationCombo.getSelectedItem();
            @Override
            public void actionPerformed(ActionEvent e) {
                Object tempItem = configurationCombo.getSelectedItem();
                if (tempItem instanceof String) {
                    configurationCombo.setSelectedItem(currentItem);
                } else {
                    currentItem = tempItem;
                    updateEnableDisable();
                }
            }
        });
        
        inspectionCombo.addActionListener(new ActionListener() {
            Object currentItem = inspectionCombo.getSelectedItem();
            @Override
            public void actionPerformed(ActionEvent e) {
                Object tempItem = inspectionCombo.getSelectedItem();
                if (!(tempItem instanceof AnalyzerAndWarning)) {
                    inspectionCombo.setSelectedItem(currentItem);
                    updatePlugins();
                } else {
                    currentItem = tempItem;
                }
            }
        });

        inspectionCombo.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                int direction = 1;
                switch (e.getKeyCode()) {
                    case KeyEvent.VK_DOWN:
                        direction = 1;
                        break;
                    case KeyEvent.VK_UP:
                        direction = -1;
                        break;
                }
                int currentIndex = inspectionCombo.getSelectedIndex() + direction;
                Object tempItem = inspectionCombo.getItemAt(currentIndex);
                if (tempItem instanceof AnalyzerAndWarning) {
                    return;
                } else {
                    currentIndex += direction;
                }
                Object nextItem = inspectionCombo.getItemAt(currentIndex);
                while (!(nextItem instanceof AnalyzerAndWarning)) {
                    currentIndex += direction;
                    nextItem = inspectionCombo.getItemAt(currentIndex);
                    if (nextItem == null) {
                        return;
                    }
                }
                inspectionCombo.setSelectedItem((AnalyzerAndWarning) nextItem);
                e.consume();
            }
        });

        if (state != null && state.scope != null) {
            for (int i = 0; i < scopeCombo.getItemCount(); i++) {
                ScopeDescription sd = (ScopeDescription)scopeCombo.getItemAt(i);
                if (sd != null && sd.getId().equals(state.scope)) {
                    scopeCombo.setSelectedIndex(i);
                    break;
                }
            }
        }
    }

    void started() {
        started = true;
        
        ((CardLayout) progress.getLayout()).show(progress, "progress");
        progress.invalidate();

        //disable all elements in the dialog:
        List<JComponent> todo = new LinkedList<JComponent>();

        todo.add(this);

        while (!todo.isEmpty()) {
            JComponent c = todo.remove(0);

            if (c == progress) continue;

            c.setEnabled(false);

            for (Component child : c.getComponents()) {
                if (child instanceof JComponent) todo.add((JComponent) child);
            }
        }
    }

    @Messages({"LBL_Predefined=Predefined", "LBL_Custom=Custom", "LBL_PleaseWait=Computing..."})
    public void updateConfigurations(final DialogState state) {
        analyzers = analyzersResult.allInstances();
        
        Object selectedConfiguration = null;
        DefaultComboBoxModel configurationModel = new DefaultComboBoxModel();
        configurationModel.addElement(Bundle.LBL_Predefined());
        configurationModel.addElement(null);
        
        for (AnalyzerFactory analyzer : analyzers) {
            if (SPIAccessor.ACCESSOR.getAnalyzerId(analyzer).equals(state.selectedAnalyzer)) {
                selectedConfiguration = analyzer;
            }
            configurationModel.addElement(analyzer);
        }

        configurationModel.addElement(Bundle.LBL_Custom());

        for (Configuration c : ConfigurationsManager.getDefault().getConfigurations()) {
            if (c.id().equals(state.selectedConfiguration)) {
                selectedConfiguration = c;
            }
            configurationModel.addElement(c);
        }

        configurationCombo.setModel(configurationModel);
        configurationCombo.setSelectedItem(selectedConfiguration);

        configurationRadio.setSelected(state.configurationsSelected);
        configurationCombo.setRenderer(new ConfigurationRenderer(true));

        DefaultComboBoxModel inspectionModel = new DefaultComboBoxModel();
        
        inspectionModel.addElement(Bundle.LBL_PleaseWait());
        
        inspectionCombo.setModel(inspectionModel);
        inspectionCombo.setRenderer(new InspectionRenderer());
        inspectionCombo.setSelectedItem(0);
        singleInspectionRadio.setSelected(!state.configurationsSelected);

        updatePlugins();
        
        final Collection<? extends AnalyzerFactory> analyzersCopy = new ArrayList<AnalyzerFactory>(analyzers);
        
        inspectionsReady = false;
        analyzer2Warnings = new FutureWarnings();
        
        WORKER.post(new Runnable() {
            @Override public void run() {
                final Map<AnalyzerFactory, Iterable<? extends WarningDescription>> analyzer2Warnings = new HashMap<AnalyzerFactory, Iterable<? extends WarningDescription>>();
                
                for (AnalyzerFactory a : analyzersCopy) {
                    List<WarningDescription> warnings = new ArrayList<WarningDescription>();
                    
                    for (WarningDescription wd : a.getWarnings()) {
                        warnings.add(wd);
                    }
                    
                    analyzer2Warnings.put(a, warnings);
                }
                
                final Map<AnalyzerFactory, Map<String, WarningDescription>> analyzer2Id2DescriptionVal = computeAnalyzerId2Description(analyzer2Warnings);
                
                SwingUtilities.invokeLater(new Runnable() {
                    @Override public void run() {
                        RunAnalysisPanel.this.analyzer2Warnings.set(analyzer2Id2DescriptionVal);
                        fillInspectionCombo(state, analyzer2Warnings);
                        inspectionsReady = true;
                        updatePlugins();
                    }
                });
            }
        });
    }
    
    private void fillInspectionCombo(DialogState state, Map<? extends AnalyzerFactory, Iterable<? extends WarningDescription>> analyzer2Warnings) {
        DefaultComboBoxModel inspectionModel = new DefaultComboBoxModel();
        AnalyzerAndWarning firstInspection = null;
        AnalyzerAndWarning preselectInspection = null;

        for (Entry<? extends AnalyzerFactory, Iterable<? extends WarningDescription>> e : analyzer2Warnings.entrySet()) {
            inspectionModel.addElement(SPIAccessor.ACCESSOR.getAnalyzerDisplayName(e.getKey()));

            Map<String, Collection<WarningDescription>> cat2Warnings = new TreeMap<String, Collection<WarningDescription>>();

            for (WarningDescription wd : e.getValue()) {
                String cat = SPIAccessor.ACCESSOR.getWarningCategoryDisplayName(wd); //TODO: should be based on the id rather than on the display name
                Collection<WarningDescription> warnings = cat2Warnings.get(cat);

                if (warnings == null) {
                    cat2Warnings.put(cat, warnings = new TreeSet<WarningDescription>(new Comparator<WarningDescription>() {
                        @Override public int compare(WarningDescription o1, WarningDescription o2) {
                            return SPIAccessor.ACCESSOR.getWarningDisplayName(o1).compareToIgnoreCase(SPIAccessor.ACCESSOR.getWarningDisplayName(o2));
                        }
                    }));
                }

                warnings.add(wd);
            }

            for (Entry<String, Collection<WarningDescription>> catE : cat2Warnings.entrySet()) {
                inspectionModel.addElement("  " + catE.getKey());

                for (WarningDescription wd : catE.getValue()) {
                    AnalyzerAndWarning aaw = new AnalyzerAndWarning(e.getKey(), wd);
                    inspectionModel.addElement(aaw);
                    warningId2Description.put(SPIAccessor.ACCESSOR.getWarningId(wd), aaw);
                    
                    if (firstInspection == null) firstInspection = aaw;
                    if (SPIAccessor.ACCESSOR.getWarningId(wd).equals(state.selectedInspection)) {
                        preselectInspection = aaw;
                    }
                }
            }
        }

        inspectionCombo.setModel(inspectionModel);
        inspectionCombo.setSelectedItem(preselectInspection != null ? preselectInspection : firstInspection);
        
    }

    private void updatePlugins() {
        if (started) return ;
        
        Collection<? extends AnalyzerFactory> toRun;

        if (singleInspectionRadio.isSelected()) {
            if (!inspectionsReady) {
                runAnalysis.setEnabled(false);
                return ;
            }
            
            Object selectedInspection = inspectionCombo.getSelectedItem();
            
            if (selectedInspection instanceof AnalyzerAndWarning) {
                toRun = Collections.singleton(((AnalyzerAndWarning) selectedInspection).analyzer);
            } else {
                toRun = Collections.emptyList();
            }
        } else {
            if (!(configurationCombo.getSelectedItem() instanceof AnalyzerFactory)) {
                toRun = analyzers;
            } else {
                toRun = Collections.singleton((AnalyzerFactory) configurationCombo.getSelectedItem());
            }
        }
        Set<MissingPlugin> plugins = new HashSet<MissingPlugin>();
        boolean someOk = false;

        for (AnalyzerFactory a : toRun) {
            Configuration configuration = getConfiguration();
            Preferences settings = configuration != null ? configuration.getPreferences().node(SPIAccessor.ACCESSOR.getAnalyzerId(a)) : null;
            Context ctx = SPIAccessor.ACCESSOR.createContext(getSelectedScope(new AtomicBoolean(false)), settings, null, null, -1, -1);
            Collection<? extends MissingPlugin> req = a.requiredPlugins(ctx);
            plugins.addAll(req);
            someOk |= req.isEmpty();
        }

        if (plugins.isEmpty()) {
            ((CardLayout) progress.getLayout()).show(progress, "empty");
        } else {
            requiredPlugins.setRequiredPlugins(plugins, !someOk);
            ((CardLayout) progress.getLayout()).show(progress, "plugins");
        }
        
        runAnalysis.setEnabled(someOk);
    }

    public Scope getSelectedScope(AtomicBoolean cancel) {
        return ((ScopeDescription) scopeCombo.getSelectedItem()).getScope(cancel);
    }
    
    public AnalyzerFactory getSelectedAnalyzer() {
        if (singleInspectionRadio.isSelected()) {
            Object singleInspection = inspectionCombo.getSelectedItem();
            
            if (singleInspection instanceof AnalyzerAndWarning) {
                return ((AnalyzerAndWarning) singleInspection).analyzer;
            }
            
            return null;
        }
        if (!(configurationCombo.getSelectedItem() instanceof AnalyzerFactory)) return null;
        return (AnalyzerFactory) configurationCombo.getSelectedItem();
    }

    public Configuration getConfiguration() {
        if (inspectionCombo.isEnabled()) return ConfigurationsManager.getDefault().getTemporaryConfiguration();
        Object selected = configurationCombo.getSelectedItem();

        if (selected instanceof Configuration) return (Configuration) selected;
        else return null;
    }

    public Collection<? extends AnalyzerFactory> getAnalyzers() {
        return analyzers;
    }

    /**
     * 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() {
        java.awt.GridBagConstraints gridBagConstraints;

        radioButtons = new javax.swing.ButtonGroup();
        jLabel1 = new javax.swing.JLabel();
        scopeCombo = new javax.swing.JComboBox();
        jLabel2 = new javax.swing.JLabel();
        configurationCombo = new javax.swing.JComboBox();
        manage = new javax.swing.JButton();
        configurationRadio = new javax.swing.JRadioButton();
        singleInspectionRadio = new javax.swing.JRadioButton();
        inspectionCombo = new javax.swing.JComboBox();
        browse = new javax.swing.JButton();

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

        org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(RunAnalysisPanel.class, "RunAnalysisPanel.jLabel1.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.ABOVE_BASELINE_LEADING;
        add(jLabel1, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridwidth = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.ABOVE_BASELINE_LEADING;
        gridBagConstraints.insets = new java.awt.Insets(0, 12, 0, 0);
        add(scopeCombo, gridBagConstraints);

        org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(RunAnalysisPanel.class, "RunAnalysisPanel.jLabel2.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.ABOVE_BASELINE_LEADING;
        gridBagConstraints.insets = new java.awt.Insets(12, 0, 0, 0);
        add(jLabel2, gridBagConstraints);

        configurationCombo.setPrototypeDisplayValue(COMBO_PROTOTYPE);
        configurationCombo.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                configurationComboActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.ABOVE_BASELINE_LEADING;
        gridBagConstraints.insets = new java.awt.Insets(12, 12, 0, 0);
        add(configurationCombo, gridBagConstraints);

        org.openide.awt.Mnemonics.setLocalizedText(manage, org.openide.util.NbBundle.getMessage(RunAnalysisPanel.class, "RunAnalysisPanel.manage.text")); // NOI18N
        manage.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                manageActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.insets = new java.awt.Insets(12, 12, 0, 0);
        add(manage, gridBagConstraints);

        radioButtons.add(configurationRadio);
        org.openide.awt.Mnemonics.setLocalizedText(configurationRadio, org.openide.util.NbBundle.getMessage(RunAnalysisPanel.class, "RunAnalysisPanel.configurationRadio.text")); // NOI18N
        configurationRadio.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                configurationRadioActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(12, 12, 0, 0);
        add(configurationRadio, gridBagConstraints);

        radioButtons.add(singleInspectionRadio);
        org.openide.awt.Mnemonics.setLocalizedText(singleInspectionRadio, org.openide.util.NbBundle.getMessage(RunAnalysisPanel.class, "RunAnalysisPanel.singleInspectionRadio.text")); // NOI18N
        singleInspectionRadio.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                singleInspectionRadioActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.BASELINE_LEADING;
        gridBagConstraints.insets = new java.awt.Insets(12, 12, 0, 0);
        add(singleInspectionRadio, gridBagConstraints);

        inspectionCombo.setPrototypeDisplayValue(COMBO_PROTOTYPE);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.BASELINE;
        gridBagConstraints.insets = new java.awt.Insets(12, 12, 0, 0);
        add(inspectionCombo, gridBagConstraints);

        org.openide.awt.Mnemonics.setLocalizedText(browse, org.openide.util.NbBundle.getMessage(RunAnalysisPanel.class, "RunAnalysisPanel.browse.text")); // NOI18N
        browse.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                browseActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.BASELINE;
        gridBagConstraints.insets = new java.awt.Insets(12, 12, 0, 0);
        add(browse, gridBagConstraints);
    }// </editor-fold>//GEN-END:initComponents

    private void configurationComboActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_configurationComboActionPerformed
        updatePlugins();
    }//GEN-LAST:event_configurationComboActionPerformed

    private AdjustConfigurationPanel showConfigurationPanel(AnalyzerFactory preselectedAnalyzer, String preselected, Configuration configurationToSelect) {
        final NotificationLineSupport[] nls = new NotificationLineSupport[1];
        final DialogDescriptor[] dd = new DialogDescriptor[1];
        ErrorListener errorListener = new ErrorListener() {
            @Override public void setError(String error) {
                nls[0].setErrorMessage(error);
                dd[0].setValid(error == null);
            }
        };
        AdjustConfigurationPanel panel = new AdjustConfigurationPanel(analyzers, preselectedAnalyzer, preselected, configurationToSelect, errorListener);
        DialogDescriptor nd = new DialogDescriptor(panel, Bundle.LBL_Configurations(), true, NotifyDescriptor.OK_CANCEL_OPTION, NotifyDescriptor.OK_OPTION, null);
        
        nls[0] = nd.createNotificationLineSupport();
        dd[0] = nd;

        if (DialogDisplayer.getDefault().notify(nd) == NotifyDescriptor.OK_OPTION) {
            return panel;
        }
        
        return null;
    }

    @Messages("LBL_Configurations=Configurations")
    private void manageActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_manageActionPerformed
        AdjustConfigurationPanel panel = showConfigurationPanel(null, null, (Configuration) configurationCombo.getSelectedItem());
        if (panel != null) {
            panel.save();
            configurationCombo.setSelectedItem(panel.getSelectedConfiguration());
        }
    }//GEN-LAST:event_manageActionPerformed

    private void configurationRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_configurationRadioActionPerformed
        updateEnableDisable();
    }//GEN-LAST:event_configurationRadioActionPerformed

    private void singleInspectionRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_singleInspectionRadioActionPerformed
        updateEnableDisable();
    }//GEN-LAST:event_singleInspectionRadioActionPerformed

    @Messages("LBL_Browse=Browse Inspections")
    private void browseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseActionPerformed
        Object selectedInspection = inspectionCombo.getSelectedItem();
        AnalyzerFactory analyzerToSelect;
        String warningToSelect;
        
        if (selectedInspection instanceof AnalyzerAndWarning) {
            analyzerToSelect = ((AnalyzerAndWarning) selectedInspection).analyzer;
            warningToSelect = SPIAccessor.ACCESSOR.getWarningId(((AnalyzerAndWarning) selectedInspection).wd);
        } else {
            analyzerToSelect = null;
            warningToSelect = "";
        }

        AdjustConfigurationPanel panel = showConfigurationPanel(analyzerToSelect, warningToSelect, null);

        if (panel != null) {
            inspectionCombo.setSelectedItem(warningId2Description.get(panel.getIdToRun()));
        }
    }//GEN-LAST:event_browseActionPerformed

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton browse;
    private javax.swing.JComboBox configurationCombo;
    private javax.swing.JRadioButton configurationRadio;
    private javax.swing.JComboBox inspectionCombo;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JButton manage;
    private javax.swing.ButtonGroup radioButtons;
    private javax.swing.JComboBox scopeCombo;
    private javax.swing.JRadioButton singleInspectionRadio;
    // End of variables declaration//GEN-END:variables

    private void updateEnableDisable() {
        if (started) return ;
        
        boolean configuration = configurationRadio.isSelected();

        configurationCombo.setEnabled(configuration);
        manage.setEnabled(configuration && configurationCombo.getSelectedItem() instanceof Configuration);
        inspectionCombo.setEnabled(!configuration);
        browse.setEnabled(!configuration);
        
        updatePlugins();
    }

    String getSingleWarningId() {
        return inspectionCombo.isEnabled() ? SPIAccessor.ACCESSOR.getWarningId(((AnalyzerAndWarning) inspectionCombo.getSelectedItem()).wd) : null;
    }

    @Override
    public void resultChanged(LookupEvent ev) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override public void run() {
                updateConfigurations(getDialogState());
            }
        });
    }
    
    public DialogState getDialogState() {
        Object selectedConfiguration = configurationCombo.getSelectedItem();
        Object selectedInspection = inspectionCombo.getSelectedItem();
        return new DialogState(configurationRadio.isSelected(),
                               selectedConfiguration instanceof AnalyzerFactory ? SPIAccessor.ACCESSOR.getAnalyzerId((AnalyzerFactory) selectedConfiguration) : null,
                               selectedConfiguration instanceof Configuration ? ((Configuration) selectedConfiguration).id() : null,
                               selectedInspection instanceof AnalyzerAndWarning ? SPIAccessor.ACCESSOR.getWarningId(((AnalyzerAndWarning) selectedInspection).wd) : null,
                               ((ScopeDescription) scopeCombo.getSelectedItem()).getId());
    }

    FutureWarnings getAnalyzerId2Description() {
        return analyzer2Warnings;
    }
    
    private static Map<AnalyzerFactory, Map<String, WarningDescription>> computeAnalyzerId2Description(Map<? extends AnalyzerFactory, Iterable<? extends WarningDescription>> analyzer2Warnings) {
        Map<AnalyzerFactory, Map<String, WarningDescription>> result = new HashMap<AnalyzerFactory, Map<String, WarningDescription>>();
        
        for (Entry<? extends AnalyzerFactory, Iterable<? extends WarningDescription>> e : analyzer2Warnings.entrySet()) {
            Map<String, WarningDescription> perAnalyzer = new HashMap<String, WarningDescription>();
            
            result.put(e.getKey(), perAnalyzer);
            
            for (WarningDescription wd : e.getValue()) {
                perAnalyzer.put(SPIAccessor.ACCESSOR.getWarningId(wd), wd);
            }
        }
        
        return result;
    }

    public static final class ConfigurationRenderer extends DefaultListCellRenderer {

        private final boolean indent;

        public ConfigurationRenderer(boolean indent) {
            this.indent = indent;
        }

        @Messages({"LBL_RunAllAnalyzers=All Analyzers", "# {0} - the analyzer that should be run", "LBL_RunAnalyzer={0}"})
        @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            if (value == null) {
                value = Bundle.LBL_RunAllAnalyzers();
            } else if (value instanceof AnalyzerFactory) {
                value = Bundle.LBL_RunAnalyzer(SPIAccessor.ACCESSOR.getAnalyzerDisplayName((AnalyzerFactory) value));
            } else if (value instanceof Configuration) {
                value = ((Configuration) value).getDisplayName();
            } else if (value instanceof String) {
                setFont(getFont().deriveFont(Font.ITALIC));
                setText((String) value);
                setEnabled(false);
                setBackground(list.getBackground());
                setForeground(UIManager.getColor("Label.disabledForeground"));

                return this;
            }

            if (index == list.getModel().getSize()-5 && list.getModel() instanceof ConfigurationsComboModel && ((ConfigurationsComboModel) list.getModel()).canModify()) {
                setBorder(new Separator(list.getForeground()));
            } else {
                setBorder(null);
            }

            return super.getListCellRendererComponent(list, (indent ? "  " : "") + value, index, isSelected, cellHasFocus);
        }
    }

    private static final class InspectionRenderer extends DefaultListCellRenderer {

        @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            if (value instanceof AnalyzerAndWarning) {
                value = "    " + SPIAccessor.ACCESSOR.getWarningDisplayName(((AnalyzerAndWarning) value).wd);
            } else if (value instanceof String) {
                setFont(getFont().deriveFont(Font.ITALIC));
                setText((String) value);
                setEnabled(false);
                setBackground(list.getBackground());
                setForeground(UIManager.getColor("Label.disabledForeground"));

                return this;
            }

            return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
        }
    }

    private interface ScopeDescription {
        public String getDisplayName();
        public Icon   getIcon();
        public Scope  getScope(AtomicBoolean cancel);
        public String getId();
    }

    private static final class AllProjectsScopeDescription implements ScopeDescription {
        private final Icon icon;
        public AllProjectsScopeDescription(Icon icon) {
            this.icon = icon;
        }
        @Override
        @Messages("DN_OpenProjects=Open Projects")
        public String getDisplayName() {
            return Bundle.DN_OpenProjects();
        }
        @Override
        public Icon getIcon() {
            return icon;
        }

        @Override
        public Scope getScope(AtomicBoolean cancel) {
            Map<Project, Map<FileObject, ClassPath>> projects2RegisteredContent = RunAnalysis.projects2RegisteredContent(cancel);

            if (cancel.get()) return null;

            Scope target = Scope.create(null, null, null);

            for (Project p : OpenProjects.getDefault().getOpenProjects()) {
                if (cancel.get()) return null;
                target = RunAnalysis.addProjectToScope(p, target, cancel, projects2RegisteredContent);
            }

            return target;
        }
        
        public String getId() {
            return "*allProjects"; // NOI18N
        }
    }

    private static final class CurrentProjectScopeDescription implements ScopeDescription {
        private final Project project;
        public CurrentProjectScopeDescription(Project project) {
            this.project = project;
        }
        @Override
        @Messages({"# {0} - project display name", "DN_CurrentProject=Current Project ({0})"})
        public String getDisplayName() {
            return Bundle.DN_CurrentProject(ProjectUtils.getInformation(project).getDisplayName());
        }
        @Override
        public Icon getIcon() {
            return ProjectUtils.getInformation(project).getIcon();
        }

        @Override
        public Scope getScope(AtomicBoolean cancel) {
            Map<Project, Map<FileObject, ClassPath>> projects2RegisteredContent = RunAnalysis.projects2RegisteredContent(cancel);

            if (cancel.get()) return null;
            
            return RunAnalysis.addProjectToScope(project, Scope.create(null, null, null), cancel, projects2RegisteredContent);
        }
        
        public String getId() {
            return "*currentProject"; // NOI18N
        }
    }

    private static final class PackageScopeDescription implements ScopeDescription {
        private final NonRecursiveFolder pack;
        private final String packName;
        public PackageScopeDescription(NonRecursiveFolder pack, String packName) {
            this.pack = pack;
            this.packName = packName;
        }
        @Override
        @Messages({"# {0} - package display name", "DN_CurrentPackage=Current Package ({0})"})
        public String getDisplayName() {
            return Bundle.DN_CurrentPackage(packName);
        }
        @Override
        public Icon getIcon() {
            return ImageUtilities.loadImageIcon("org/netbeans/modules/analysis/ui/resources/package.gif", false);
        }

        @Override
        public Scope getScope(AtomicBoolean cancel) {
            return Scope.create(null, Collections.singletonList(pack), null);
        }
        
        public String getId() {
            return "*currentPackage"; // NOI18N
        }
    }

    private static final class FolderScopeDescription implements ScopeDescription {
        private final DataFolder folder;
        public FolderScopeDescription(DataFolder folder) {
            this.folder = folder;
        }
        @Override
        @Messages({"# {0} - folder display name", "DN_CurrentFolder=Current Folder ({0})"})
        public String getDisplayName() {
            return Bundle.DN_CurrentFolder(folder.getName());
        }
        @Override
        public Icon getIcon() {
            return ImageUtilities.image2Icon(folder.getNodeDelegate().getIcon(1));
        }

        @Override
        public Scope getScope(AtomicBoolean cancel) {
            return Scope.create(null, null, folder.files());
        }
        
        public String getId() {
            return "*currentFolder"; // NOI18N
        }
    }

    private static final class FileScopeDescription implements ScopeDescription {
        private static final Logger LOG = Logger.getLogger(FileScopeDescription.class.getName());
        private final FileObject file;
        public FileScopeDescription(FileObject file) {
            this.file = file;
        }
        @Override
        @Messages({"# {0} - file display name", "DN_CurrentFile=Current File ({0})"})
        public String getDisplayName() {
            return Bundle.DN_CurrentFile(file.getNameExt());
        }
        @Override
        public Icon getIcon() {
            try {
                DataObject d = DataObject.find(file);
                Node n = d.getNodeDelegate();
                return ImageUtilities.image2Icon(n.getIcon(BeanInfo.ICON_COLOR_16x16));
            } catch (DataObjectNotFoundException ex) {
                LOG.log(Level.FINE, null, ex);
                return null;
            }
        }

        @Override
        public Scope getScope(AtomicBoolean cancel) {
            return Scope.create(null, null, Collections.singletonList(file));
        }
        
        public String getId() {
            return "*currentFile";
        }
    }

    private static final class ScopeRenderer extends DefaultListCellRenderer {

        @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            if (value instanceof ScopeDescription) {
                ScopeDescription sd = (ScopeDescription) value;

                try {
                    return super.getListCellRendererComponent(list, sd.getDisplayName(), index, isSelected, cellHasFocus);
                } finally {
                    setIcon(sd.getIcon());
                }
            }

            return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
        }
    }

    private static class Separator implements Border {

        private Color fgColor;

        Separator(Color color) {
            fgColor = color;
        }

        public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
            Graphics gr = g.create();
            if (gr != null) {
                try {
                    gr.translate(x, y);
                    gr.setColor(fgColor);
                    gr.drawLine(0, height - 1, width - 1, height - 1);
                } finally {
                    gr.dispose();
                }
            }
        }

        public boolean isBorderOpaque() {
            return true;
        }

        public Insets getBorderInsets(Component c) {
            return new Insets(0, 0, 1, 0);
        }
    }

    private static final class AnalyzerAndWarning {
        private final AnalyzerFactory analyzer;
        private final WarningDescription wd;
        public AnalyzerAndWarning(AnalyzerFactory analyzer, WarningDescription wd) {
            this.analyzer = analyzer;
            this.wd = wd;
        }
    }
    
    public static final class DialogState {
        private final boolean configurationsSelected;
        private final String  selectedAnalyzer;
        private final String  selectedConfiguration;
        private final String  selectedInspection;
        private final String  scope;

        private DialogState(boolean configurationsSelected, String selectedAnalyzer, String selectedConfiguration, String selectedInspection, String scope) {
            this.configurationsSelected = configurationsSelected;
            this.selectedAnalyzer = selectedAnalyzer;
            this.selectedConfiguration = selectedConfiguration;
            this.selectedInspection = selectedInspection;
            this.scope = scope;
        }
        
        public void save() {
            Preferences prefs = NbPreferences.forModule(RunAnalysisPanel.class).node("RunAnalysisPanel");
            
            prefs.putBoolean("configurationsSelected", configurationsSelected);
            if (selectedAnalyzer != null)
                prefs.put("selectedAnalyzer", selectedAnalyzer);
            else
                prefs.remove("selectedAnalyzer");
            if (selectedConfiguration != null)
                prefs.put("selectedConfiguration", selectedConfiguration);
            else
                prefs.remove("selectedConfiguration");
            if (selectedInspection != null)
                prefs.put("selectedInspection", selectedInspection);
            else
                prefs.remove("selectedInspection");
        }
        
        private static DialogState load() {
            Preferences prefs = NbPreferences.forModule(RunAnalysisPanel.class).node("RunAnalysisPanel");
            return new DialogState(prefs.getBoolean("configurationsSelected", true),
                                   prefs.get("selectedAnalyzer", null),
                                   prefs.get("selectedConfiguration", null),
                                   prefs.get("selectedInspection", null),
                                   null);
        }
        
        public static DialogState from(WarningDescription wd) {
            return new DialogState(false, null, null, SPIAccessor.ACCESSOR.getWarningId(wd), null);
        }
    }
    
    public static class FutureWarnings {
        private Map<AnalyzerFactory, Map<String, WarningDescription>> result;
        public synchronized boolean isDone() {
            return result != null;
        }
        public synchronized Map<AnalyzerFactory, Map<String, WarningDescription>> get() {
            while (result == null) {
                try {
                    wait();
                } catch (InterruptedException ex) {
                    //Should not be happening, hopefully:
                    Logger.getLogger(RunAnalysisPanel.class.getName()).log(Level.FINE, null, ex);
                }
            }
            return result;
        }
        synchronized void set(Map<AnalyzerFactory, Map<String, WarningDescription>> value) {
            result = value;
            notifyAll();
        }
    }
}
