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

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import org.netbeans.api.autoupdate.InstallSupport;
import org.netbeans.api.autoupdate.OperationContainer;
import org.netbeans.api.autoupdate.UpdateElement;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.modules.autoupdate.ui.api.PluginManager;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.awt.Mnemonics;
import org.openide.awt.Notification;
import org.openide.awt.NotificationDisplayer;
import org.openide.util.Exceptions;
import org.openide.util.ImageUtilities;
import org.openide.util.NbBundle;
import org.openide.util.NbPreferences;
import org.openide.util.RequestProcessor;

/**
 *
 * @author Jirka Rechtacek
 */
public class ImportManager extends java.awt.Panel {
    private final File srcCluster;
    private final File dest;
    private final PluginImporter importer;
    private static ImportManager INSTANCE = null;
    private List<Boolean> checkedToInstall = Collections.emptyList ();
    private List<Boolean> checkedToImport = Collections.emptyList ();
    private List<UpdateElement> toInstall = Collections.emptyList ();
    private List<UpdateElement> toImport = Collections.emptyList ();
    private Notification currentNotification = null;
    private JButton bImport;
    private JButton bNo;    

    /** Creates new form ImportManager */
    @SuppressWarnings("LeakingThisInConstructor")
    public ImportManager (File src, File dest, PluginImporter importer) {
        this.srcCluster = src;
        this.dest = dest;
        this.importer = importer;
        initialize();
        INSTANCE = this;
    }

    public PluginImporter getPluginImporter() {
        return importer;
    }

    private void initialize() {
        toInstall = new ArrayList<UpdateElement> (importer.getPluginsAvailableToInstall ());
        Collections.sort (toInstall, new Comparator<UpdateElement> () {
            @Override
            public int compare (UpdateElement o1, UpdateElement o2) {
                return o1.getDisplayName ().compareTo (o2.getDisplayName ());
            }
        });
        checkedToInstall = new ArrayList<Boolean> (Collections.nCopies (importer.getPluginsAvailableToInstall ().size (), Boolean.TRUE));

        toImport = new ArrayList<UpdateElement> (importer.getPluginsToImport ());
        Collections.sort (toImport, new Comparator<UpdateElement> () {
            @Override
            public int compare (UpdateElement o1, UpdateElement o2) {
                return o1.getDisplayName ().compareTo (o2.getDisplayName ());
            }
        });
        checkedToImport = new ArrayList<Boolean> (Collections.nCopies (toImport.size (), Boolean.FALSE));

        initComponents();

        tpBroken.setEnabled (!importer.getBrokenPlugins ().isEmpty ());
        lBroken.setEnabled (!importer.getBrokenPlugins ().isEmpty ());
        if (! importer.getBrokenPlugins ().isEmpty ()) {
            tpBroken.setText (importer.getBrokenPlugins ().toString ());
        }        
        refreshUI ();        
    }

    public static ImportManager getInstance () {
       return INSTANCE;
    }

    public void notifyAvailable () {
        remindLater ();
        String msg = NbBundle.getMessage (ImportManager.class,
                "ImportNotifier_PluginAvailableForImport", // NOI18N
                toImport.size () + toInstall.size ());
        String details = NbBundle.getMessage (ImportManager.class,
                "ImportNotifier_PluginAvailableForImport_Details", // NOI18N
                srcCluster);
        MyAction a = new MyAction();
        synchronized( this ) {
            if( null != currentNotification ) {
                currentNotification.clear();
            }
            Notification notification = NotificationDisplayer.getDefault().notify(msg,
                    ImageUtilities.loadImageIcon("org/netbeans/modules/autoupdate/pluginimporter/resources/import.png", false), //NOI18N
                    details,
                    a);
            a.notification = notification;
            currentNotification = notification;
        }
    }

    private class MyAction extends AbstractAction {
        final JButton bRemindLaterButton = new JButton ();
        final JButton bImportButton = new JButton ();
        final JButton bNoButton = new JButton ();
        private Notification notification;

        public MyAction() {
            Mnemonics.setLocalizedText (bRemindLaterButton, NbBundle.getMessage (ImportManager.class, "ImportNotifier_bRemindLater"));
            Mnemonics.setLocalizedText (bImportButton, NbBundle.getMessage (ImportManager.class, "ImportNotifier_bImport"));
            Mnemonics.setLocalizedText (bNoButton, NbBundle.getMessage (ImportManager.class, "ImportNotifier_bNo"));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            ImportManager ui = ImportManager.getInstance ();
            ui.getPluginImporter().reinspect();
            ui.initialize();
            
            ui.attachButtons (bImportButton, bNoButton);
            DialogDescriptor dd = new DialogDescriptor (
                    ui,
                    NbBundle.getMessage (Installer.class, "Installer_DialogTitle"),
                    true,
                    new Object[] {bImportButton, bNoButton, bRemindLaterButton},
                    NotifyDescriptor.OK_OPTION,
                    DialogDescriptor.BOTTOM_ALIGN,
                    null,
                    null);
            dd.setClosingOptions (new Object[] {bImportButton, bNoButton, bRemindLaterButton});
            DialogDisplayer.getDefault ().createDialog (dd).setVisible (true);
            if (bImportButton.equals (dd.getValue ()) || bNoButton.equals (dd.getValue ())) {
                ui.dontRemind ();
                SwingUtilities.invokeLater (new Runnable () {
                    @Override
                    public void run () {
                        if( null != notification ) {
                            notification.clear();
                        }
                    }
                });
            } else if (bRemindLaterButton.equals (dd.getValue ())) {
                ui.remindLater ();
            }
        }
    }

    public void attachButtons(JButton bImport, JButton bNo) {
        this.bImport = bImport;
        this.bNo = bNo;
        bImport.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                Object source = e.getSource();
                if (source instanceof JButton) {
                    RequestProcessor.getDefault().post(new Runnable() {

                        @Override
                        public void run() {
                            doImport();
                        }
                    });
                }
            }
        });
        refreshUI();
    }

    public void remindLater () {
        Preferences p = NbPreferences.forModule (Installer.class);
        p.put (Installer.KEY_IMPORT_FROM, srcCluster.toString ());
    }

    public void dontRemind () {
        Preferences p = NbPreferences.forModule (Installer.class);
        try {
            p.clear ();
        } catch (BackingStoreException ex) {
            Exceptions.printStackTrace (ex);
        }
    }

    private boolean doImport () {
        boolean res = true;
        if (checkedToImport.indexOf (Boolean.TRUE) != -1) {
            final Collection<UpdateElement> reallyToImport = new HashSet<UpdateElement>();
            for (UpdateElement el : toImport) {
                if (checkedToImport.get(toImport.indexOf(el))) {
                    reallyToImport.add(el);
                }
            }
            if (reallyToImport.size() > 0) {
                final ProgressHandle handle = ProgressHandleFactory.createHandle(NbBundle.getMessage(ImportManager.class, ("ImportManager.Progress.Name")));
                final JComponent progressComp = ProgressHandleFactory.createProgressComponent(handle);
                final JLabel detailLabel = new JLabel(NbBundle.getMessage(ImportManager.class, "ImportManager.Progress.Label"));
                detailLabel.setHorizontalAlignment(SwingConstants.LEFT);
                setProgressComponent(detailLabel, progressComp);

                try {
                    importer.importPlugins(reallyToImport, srcCluster, dest, handle);
                } catch (IOException ex) {
                    Exceptions.printStackTrace(ex);
                } finally {
                    toImport.clear();
                    checkedToImport.clear();
                }

                try {
                    SwingUtilities.invokeAndWait(new Runnable() {

                        @Override
                        public void run() {
                            detailLabel.setVisible(false);
                            progressComp.setVisible(false);
                            tToImport.setModel(getModel(toImport, checkedToImport));
                            refreshUI();
                        }
                    });
                } catch (InterruptedException ex) {
                    Exceptions.printStackTrace(ex);
                } catch (InvocationTargetException ex) {
                    Exceptions.printStackTrace(ex);
                }
            }
        }
        
        try {
            dontRemind ();
            if (checkedToInstall.indexOf (Boolean.TRUE) != -1) {
                final OperationContainer<InstallSupport> oc = OperationContainer.createForInstall ();
                for (UpdateElement el : toInstall) {
                    if (checkedToInstall.get (toInstall.indexOf (el))) {
                        OperationContainer.OperationInfo<InstallSupport> info = oc.add (el);
                        if (info != null) {
                            for(UpdateElement required : info.getRequiredElements ()) {
                                if(!required.getUpdateUnit().isPending()) {
                                    oc.add (required);
                                }
                            }
                        }
                    }
                }
                try {
                    SwingUtilities.invokeAndWait(new Runnable() {

                        @Override
                        public void run() {
                            PluginManager.openInstallWizard(oc, true);
                            SwingUtilities.invokeLater(new Runnable() {

                                @Override
                                public void run() {
                                    toInstall.clear();
                                    checkedToInstall.clear();
                                }
                            });
                        }
                    });
                } catch (InterruptedException ex) {
                    Exceptions.printStackTrace(ex);
                } catch (InvocationTargetException ex) {
                    Exceptions.printStackTrace(ex);
                }
            }
        } finally {
            try {
                SwingUtilities.invokeAndWait(new Runnable() {

                    @Override
                    public void run() {
                        tToInstall.setModel(getModel(toInstall, checkedToInstall));
                        refreshUI();
                    }
                });
            } catch (InterruptedException ex) {
                Exceptions.printStackTrace(ex);
            } catch (InvocationTargetException ex) {
                Exceptions.printStackTrace(ex);
            }
            
        }
        return res;

    }

    private void setProgressComponent (final JLabel detail, final JComponent progressComponent) {
        if (SwingUtilities.isEventDispatchThread ()) {
            setProgressComponentInAwt (detail, progressComponent);
        } else {
            SwingUtilities.invokeLater (new Runnable () {
                @Override
                public void run () {
                    setProgressComponentInAwt (detail, progressComponent);
                }
            });
        }
    }
    private void setProgressComponentInAwt (JLabel detail, JComponent progressComponent) {
        assert pProgress != null;
        assert SwingUtilities.isEventDispatchThread () : "Must be called in EQ.";

        progressComponent.setMinimumSize (progressComponent.getPreferredSize ());

        pProgress.setVisible (true);

        java.awt.GridBagConstraints gridBagConstraints;

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 12);
        pProgress.add(progressComponent, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.weightx = 1.0;
        pProgress.add(detail, gridBagConstraints);

        validate ();
    }

    private TableModel getModel (final List<UpdateElement> plugins, final List<Boolean> checked) {
        return new AbstractTableModel () {
            @Override
            public int getRowCount () {
                return plugins.size ();
            }

            @Override
            public int getColumnCount () {
                return 2;
            }

            @Override
            public Object getValueAt (int rowIndex, int columnIndex) {
                switch (columnIndex) {
                    case 0 :
                        return checked.get (rowIndex);
                    case 1 :
                        return plugins.get (rowIndex).getDisplayName ();
                }
                return null;
            }

            @Override
            public Class<?> getColumnClass (int columnIndex) {
                switch (columnIndex) {
                    case 0 :
                        return Boolean.class;
                    case 1 :
                        return String.class;
                }
                return null;
            }

            @Override
            public String getColumnName (int column) {
                switch (column) {
                    case 0 :
                        return NbBundle.getMessage (ImportManager.class, "ImportNotifier_Install");
                    case 1 :
                        return NbBundle.getMessage (ImportManager.class, "ImportNotifier_Plugin");
                }
                return null;
            }

            @Override
            public boolean isCellEditable (int rowIndex, int columnIndex) {
                switch (columnIndex) {
                    case 0 :
                        return true;
                    case 1 :
                        return false;
                }
                return false;
            }

            @Override
            public void setValueAt (Object aValue, int rowIndex, int columnIndex) {
                switch (columnIndex) {
                    case 0 :
                        checked.set (rowIndex, ((Boolean) aValue));
                        refreshUI ();
                        break;
                    case 1 :
                        assert false : "Name is not editable.";
                        break;
                }
            }

        };
    }

    /** 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() {

        jScrollPane3 = new javax.swing.JScrollPane();
        jTextPane1 = new javax.swing.JTextPane();
        lToInstall = new javax.swing.JLabel();
        jScrollPane1 = new javax.swing.JScrollPane();
        tToInstall = new javax.swing.JTable();
        lToImport = new javax.swing.JLabel();
        jScrollPane2 = new javax.swing.JScrollPane();
        tToImport = new javax.swing.JTable();
        lBroken = new javax.swing.JLabel();
        jScrollPane4 = new javax.swing.JScrollPane();
        tpBroken = new javax.swing.JTextPane();
        lDesc = new javax.swing.JLabel();
        jSeparator1 = new javax.swing.JSeparator();
        jSeparator2 = new javax.swing.JSeparator();
        pProgress = new javax.swing.JPanel();

        jScrollPane3.setViewportView(jTextPane1);

        lToInstall.setLabelFor(lToInstall);
        org.openide.awt.Mnemonics.setLocalizedText(lToInstall, org.openide.util.NbBundle.getMessage(ImportManager.class, "ImportManager.lToInstall.text")); // NOI18N

        tToInstall.setModel(getModel (toInstall, checkedToInstall));
        tToInstall.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_ALL_COLUMNS);
        tToInstall.setOpaque(false);
        jScrollPane1.setViewportView(tToInstall);

        lToImport.setLabelFor(tToImport);
        org.openide.awt.Mnemonics.setLocalizedText(lToImport, org.openide.util.NbBundle.getMessage(ImportManager.class, "ImportManager.lToImport.text")); // NOI18N

        tToImport.setModel(getModel (toImport, checkedToImport));
        tToImport.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_ALL_COLUMNS);
        tToImport.setOpaque(false);
        jScrollPane2.setViewportView(tToImport);

        lBroken.setLabelFor(tpBroken);
        org.openide.awt.Mnemonics.setLocalizedText(lBroken, org.openide.util.NbBundle.getMessage(ImportManager.class, "ImportManager.lBroken.text")); // NOI18N

        tpBroken.setEditable(false);
        tpBroken.setEnabled(false);
        tpBroken.setOpaque(false);
        jScrollPane4.setViewportView(tpBroken);

        lDesc.setFont(lDesc.getFont().deriveFont(lDesc.getFont().getStyle() | java.awt.Font.BOLD));
        org.openide.awt.Mnemonics.setLocalizedText(lDesc, org.openide.util.NbBundle.getMessage(ImportManager.class, "ImportManager.lDesc.text", new Object[] {srcCluster})); // NOI18N

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

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addComponent(pProgress, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 688, Short.MAX_VALUE)
                    .addComponent(jScrollPane4, javax.swing.GroupLayout.DEFAULT_SIZE, 688, Short.MAX_VALUE)
                    .addComponent(lToImport, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 688, Short.MAX_VALUE)
                    .addComponent(jSeparator2, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 688, Short.MAX_VALUE)
                    .addComponent(lToInstall, javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(lDesc, javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 688, Short.MAX_VALUE)
                    .addComponent(jSeparator1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 688, Short.MAX_VALUE)
                    .addComponent(jScrollPane2, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 688, Short.MAX_VALUE)
                    .addComponent(lBroken, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 688, Short.MAX_VALUE))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addComponent(lDesc)
                .addGap(7, 7, 7)
                .addComponent(lToInstall)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 123, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 10, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(lToImport)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 158, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jSeparator2, javax.swing.GroupLayout.PREFERRED_SIZE, 10, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(lBroken)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jScrollPane4, javax.swing.GroupLayout.DEFAULT_SIZE, 75, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(pProgress, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap())
        );
    }// </editor-fold>//GEN-END:initComponents


    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JScrollPane jScrollPane2;
    private javax.swing.JScrollPane jScrollPane3;
    private javax.swing.JScrollPane jScrollPane4;
    private javax.swing.JSeparator jSeparator1;
    private javax.swing.JSeparator jSeparator2;
    private javax.swing.JTextPane jTextPane1;
    private javax.swing.JLabel lBroken;
    private javax.swing.JLabel lDesc;
    private javax.swing.JLabel lToImport;
    private javax.swing.JLabel lToInstall;
    private javax.swing.JPanel pProgress;
    private javax.swing.JTable tToImport;
    private javax.swing.JTable tToInstall;
    private javax.swing.JTextPane tpBroken;
    // End of variables declaration//GEN-END:variables

    private void refreshUI () {
        lToImport.setEnabled (toImport.size () > 0);
        tToImport.setEnabled (toImport.size () > 0);

        lToInstall.setEnabled (toInstall.size () > 0);
        tToInstall.setEnabled (toInstall.size () > 0);

        TableColumn activeColumn = tToImport.getColumnModel ().getColumn (0);
        activeColumn.setMaxWidth (tToImport.getTableHeader ().getHeaderRect (0).width);
        activeColumn = tToInstall.getColumnModel ().getColumn (0);
        activeColumn.setMaxWidth (tToInstall.getTableHeader ().getHeaderRect (0).width);

        if (bImport != null) {
            bImport.setEnabled (checkedToInstall.indexOf (Boolean.TRUE) != -1 || checkedToImport.indexOf (Boolean.TRUE) != -1);
        }
    }

}
