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

import java.awt.Component;
import java.awt.Cursor;
import java.awt.Window;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.api.autoupdate.UpdateManager;
import org.netbeans.api.autoupdate.UpdateUnit;
import org.netbeans.modules.autoupdate.ui.actions.AutoupdateCheckScheduler;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.util.Exceptions;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import org.openide.util.Task;
import org.openide.util.TaskListener;

/**
 *
 * @author  Jiri Rechtacek, Radek Matous
 */
public class PluginManagerUI extends javax.swing.JPanel  {
    private List<UpdateUnit> units = Collections.emptyList ();
    private UnitTable installedTable;
    private UnitTable availableTable;
    private UnitTable updateTable;
    private UnitTable localTable;
    private JButton closeButton;
    public final RequestProcessor.Task initTask;
    private final Object initLock = new Object();
    private Object helpInstance = null;
    
    private static volatile RequestProcessor.Task runningTask;
    private static Collection<Runnable> runOnCancel = null;
    private boolean wasSettings = false;
    public static final int INDEX_OF_UPDATES_TAB = 0;
    public static final int INDEX_OF_AVAILABLE_TAB = 1;
    public static final int INDEX_OF_DOWNLOAD_TAB = 2;
    public static final int INDEX_OF_INSTALLED_TAB = 3;
    public static final int INDEX_OF_SETTINGS_TAB = 4;

    public static final String[] TAB_NAMES = { "update", "available", "local", "installed" }; //NOI18N
    private int initialTabToSelect;
    private boolean detailView;
    public static final String DETAIL_VIEW_SELECTED_PROP = "plugin.manager.detail.view.selected";//NOI18N
    
    public PluginManagerUI (JButton closeButton ) {
        this(closeButton, null, true);
    }

    public PluginManagerUI (JButton closeButton, Object initialTab) {
        this(closeButton, initialTab, Boolean.getBoolean(DETAIL_VIEW_SELECTED_PROP));
    }
    
    /** Creates new form PluginManagerUI */
    public PluginManagerUI (JButton closeButton, Object initialTab, boolean detailView) {
        this.detailView = detailView;
        this.closeButton = closeButton;
        this.willClose = false;
        int selIndex = -1;
        for( int i=0; i<TAB_NAMES.length; i++ ) {
            if( TAB_NAMES[i].equals(initialTab) ) {
                selIndex = i;
                break;
            }
        }
        if( selIndex < 0 && null != initialTab ) {
            throw new IllegalArgumentException("Invalid tab name: " + initialTab); //NOI18N
        }
        initialTabToSelect = selIndex;
        initComponents ();
        //start initialize method as soon as possible
        initTask = Utilities.startAsWorkerThread (new Runnable () {
            @Override
            public void run () {
                postInitComponents ();
                initialize ();
            }
        });
    }

    boolean isDetailView() {
        return detailView;
    }

    void setDetailView(boolean detailView) {
        this.detailView = detailView;
    }

    private Window findWindowParent () {
        Component c = this;
        while(c != null) {
            c = c.getParent ();
            if (c instanceof Window) {
                return (Window)c;
            }
        }
        return null;
    }
    
    void setWaitingState (boolean waitingState) {
        boolean enabled = !waitingState;

        for (Component c : tpTabs.getComponents()) {
            if (c instanceof UnitTab) {
                ((UnitTab) c).setWaitingState (waitingState);
            }            
        }               
        
        // the Close & Help buttons are always enabled
        bClose.setEnabled (true);
        
        Component parent = getParent ();
        Component rootPane = getRootPane ();
        if (parent != null) {
            parent.setEnabled (enabled);
        }
        if (rootPane != null) {
            if (enabled) {
                rootPane.setCursor (null);
            } else {
                rootPane.setCursor (Cursor.getPredefinedCursor (Cursor.WAIT_CURSOR));
            }
        }
    }

    @Override
    public void addNotify () {
        super.addNotify ();
        //show progress for initialize method
        final Window w = findWindowParent ();
        if (w != null) {
            w.addWindowListener (new WindowAdapter (){
                @Override
                public void windowOpened (WindowEvent e) {
                    final WindowAdapter waa = this;
                    setWaitingState (true);
                    Utilities.startAsWorkerThread (PluginManagerUI.this,
                            new Runnable () {
                                @Override
                                public void run () {
                                    try {
                                        initTask.waitFinished ();
                                        w.removeWindowListener (waa);
                                    } finally {
                                        setWaitingState (false);
                                    }
                                }
                            },
                            NbBundle.getMessage (PluginManagerUI.class, "UnitTab_InitAndCheckingForUpdates"),
                            Utilities.getTimeOfInitialization ());
                }
            });
        }
        HelpCtx.setHelpIDString (this, PluginManagerUI.class.getName ());
        tpTabs.addChangeListener (new ChangeListener () {
            @Override
            public void stateChanged (ChangeEvent evt) {
                HelpCtx.setHelpIDString (PluginManagerUI.this, getHelpCtx ().getHelpID ());
            }
        });
    }
    
    
    @Override
    public void removeNotify () {
        super.removeNotify ();
        unitilialize ();
    }
    
    public void close () {
        bClose.doClick ();
    }

    private boolean willClose;

    public void willClose() {
        willClose = true;
    }

    public boolean isClosing() {
        return willClose;
    }

    private void initialize () {
        try {
            final List<UpdateUnit> uu = UpdateManager.getDefault().getUpdateUnits(Utilities.getUnitTypes());
//            List<UnitCategory> precompute1 = Utilities.makeUpdateCategories (uu, false);
            if (localTable != null) {
                final List<UpdateUnit> nbms = new ArrayList<UpdateUnit>(((LocallyDownloadedTableModel) localTable.getModel()).getLocalDownloadSupport().getUpdateUnits());
                List<UnitCategory> precompute2 = Utilities.makeUpdateCategories (nbms, true);
            }
            // postpone later
            refreshUnitsInBackground(uu);
            SwingUtilities.invokeAndWait (new Runnable () {
                @Override
                public void run () {
                    refreshUnitsInAWT();
                    setSelectedTab ();
                }
            });
        } catch (InterruptedException ex) {
            Exceptions.printStackTrace (ex);
        } catch (InvocationTargetException ex) {
            Exceptions.printStackTrace (ex);
        }
    }
    
    //workaround of #96282 - Memory leak in org.netbeans.core.windows.services.NbPresenter
    private void unitilialize () {
        Utilities.startAsWorkerThread (new Runnable () {
            @Override
            public void run () {
                //ensures that uninitialization runs after initialization
                initTask.waitFinished ();
                AutoupdateCheckScheduler.runCheckAvailableUpdates (0);
                //ensure exclusivity between this uninitialization code and refreshUnits (which can run even after this dialog is disposed)
                synchronized(initLock) {
                    units = null;
                    installedTable = null;
                    availableTable = null;
                    updateTable = null;
                    localTable = null;
                }
            }
        }, 10000);
    }
    
    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 UnitTable createTabForModel(final UnitCategoryTableModel model) {
        final UnitTable table = new UnitTable(model);
        selectFirstRow(table);
        
        final UnitTab tab = new UnitTab(table, new UnitDetails(), this);
        if (initTask != null) {
            tab.setWaitingState(! initTask.isFinished());
            initTask.addTaskListener(new TaskListener() {

                @Override
                public void taskFinished(Task task) {
                    SwingUtilities.invokeLater(new Runnable() {

                        @Override
                        public void run() {
                            tab.setWaitingState(false);
                        }
                    });
                }
            });
        }
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                tpTabs.add(tab, model.getTabIndex());
                decorateTabTitle(table);
            }
        });
        return table;
    }
    
    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);

        revalidate ();
    }
    
    void unsetProgressComponent (final JLabel detail, final JComponent progressComponent) {
        if (SwingUtilities.isEventDispatchThread ()) {
            unsetProgressComponentInAwt (detail, progressComponent);
        } else {
            SwingUtilities.invokeLater (new Runnable () {
                @Override
                public void run () {
                    unsetProgressComponentInAwt (detail, progressComponent);
                }
            });
        }
    }

    private void setSelectedTab() {
        if( initialTabToSelect >= 0 && initialTabToSelect != tpTabs.getSelectedIndex()
                && initialTabToSelect < tpTabs.getComponentCount() ) {
            Component c = tpTabs.getComponentAt(initialTabToSelect);
            if (c instanceof UnitTab) {
                UnitTab unitTab = (UnitTab) c;
                if (unitTab.getModel().isTabEnabled() && unitTab.getModel().canBePrimaryTab() ) {
                    tpTabs.setSelectedIndex(initialTabToSelect);
                    initialTabToSelect = -1;
                    return;
                }
            }
        }
        initialTabToSelect = -1;
        Component component = tpTabs.getSelectedComponent();
        if (component instanceof UnitTab) {
            UnitTab unitTab = (UnitTab) component;
            if (!unitTab.getModel().isTabEnabled()) {
                for (int i = 0; i < tpTabs.getComponentCount(); i++) {
                    component = tpTabs.getComponentAt(i);
                    if (component instanceof UnitTab) {
                        unitTab = (UnitTab) component;
                        if (unitTab.getModel().isTabEnabled() && unitTab.getModel().canBePrimaryTab()) {
                            tpTabs.setSelectedIndex(i);
                            break;
                        }
                    } else {
                        tpTabs.setSelectedIndex(i);
                        break;
                    }
                }
            }
        }
    }
    
    private void unsetProgressComponentInAwt (JLabel detail, JComponent progressComponent) {
        assert pProgress != null;
        assert SwingUtilities.isEventDispatchThread () : "Must be called in EQ.";
        pProgress.remove (detail);
        pProgress.remove (progressComponent);
        pProgress.setVisible (false);
        revalidate ();
    }
    
    /** 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() {

        tpTabs = new javax.swing.JTabbedPane();
        pProgress = new javax.swing.JPanel();
        bClose = closeButton;
        bHelp = new javax.swing.JButton();

        tpTabs.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                tpTabsStateChanged(evt);
            }
        });

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

        org.openide.awt.Mnemonics.setLocalizedText(bClose, org.openide.util.NbBundle.getMessage(PluginManagerUI.class, "UnitTab_bClose_Text")); // NOI18N

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

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                        .addComponent(pProgress, javax.swing.GroupLayout.PREFERRED_SIZE, 562, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 157, Short.MAX_VALUE)
                        .addComponent(bClose)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(bHelp))
                    .addComponent(tpTabs, javax.swing.GroupLayout.DEFAULT_SIZE, 864, Short.MAX_VALUE))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(tpTabs, javax.swing.GroupLayout.DEFAULT_SIZE, 471, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addComponent(pProgress, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                        .addComponent(bHelp)
                        .addComponent(bClose, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
                .addContainerGap())
        );

        tpTabs.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(PluginManagerUI.class, "ACN_Tabs")); // NOI18N
        tpTabs.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(PluginManagerUI.class, "ACD_Tabs")); // NOI18N

        getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(PluginManagerUI.class, "ACN_PluginManagerUI")); // NOI18N
        getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(PluginManagerUI.class, "ACD_PluginManagerUI")); // NOI18N
    }// </editor-fold>//GEN-END:initComponents
    
private void tpTabsStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_tpTabsStateChanged
    Component component = ((JTabbedPane) evt.getSource ()).getSelectedComponent ();
    if (component instanceof SettingsTab) {
        int i = ((SettingsTab)component).getSelectedRow();
        ((SettingsTab)component).getSettingsTableModel ().refreshModel ();
        if (i > -1) {
            ((SettingsTab)component).setSelectedRow(i);
        }
        wasSettings = true;
    } else {
        if (wasSettings) {
            final UnitCategoryTableModel availableModel = (UnitCategoryTableModel) (availableTable).getModel ();
            final Map<String, Boolean> availableState = UnitCategoryTableModel.captureState (availableModel.getUnits ());
            ((SettingsTab) tpTabs.getComponentAt (INDEX_OF_SETTINGS_TAB)).doLazyRefresh (new Runnable () { // get SettingsTab
                @Override
                public void run () {
                    UnitCategoryTableModel.restoreState (availableModel.getUnits (), availableState, false);
                }
            });
        }
        wasSettings = false;
    }
}//GEN-LAST:event_tpTabsStateChanged

private void bHelpActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bHelpActionPerformed
    getHelpCtx().display();
}//GEN-LAST:event_bHelpActionPerformed

    private HelpCtx getHelpCtx() {
        String id = PluginManagerUI.class.getName ();
        Component c = tpTabs.getSelectedComponent ();
        if (c instanceof UnitTab) {
            id = ((UnitTab) c).getHelpId ();
        } else if (c instanceof SettingsTab) {
            id = SettingsTab.class.getName ();
        }
        return new HelpCtx (id);
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton bClose;
    private javax.swing.JButton bHelp;
    private javax.swing.JPanel pProgress;
    private javax.swing.JTabbedPane tpTabs;
    // End of variables declaration//GEN-END:variables
    
    private void postInitComponents () {
        Containers.initNotify ();
        updateTable = createTabForModel(new UpdateTableModel(units));
        availableTable = createTabForModel(new AvailableTableModel (units));
        final LocalDownloadSupport localDownloadSupport = new LocalDownloadSupport();
        final LocallyDownloadedTableModel localTM = new LocallyDownloadedTableModel(localDownloadSupport);
        localTable = createTabForModel(localTM);
        installedTable = createTabForModel(new InstalledTableModel(units));

        DropTargetListener l = new LocallDownloadDnD(localDownloadSupport, localTM, this);
        final DropTarget dt = new DropTarget(null, l);
        dt.setActive(true);
        this.setDropTarget(dt);


        final SettingsTab tab = new SettingsTab(this);
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                tpTabs.add(tab, INDEX_OF_SETTINGS_TAB);
                tpTabs.setTitleAt(INDEX_OF_SETTINGS_TAB, tab.getDisplayName());
            }
        });
        setWaitingState(true);
    }
    
    void decorateTabTitle (UnitTable table) {
        if (table == null) {
            return ;
        }
        UnitCategoryTableModel model = (UnitCategoryTableModel)table.getModel();
        int index = model.getTabIndex();
        tpTabs.setTitleAt (index, model.getDecoratedTabTitle());
        tpTabs.setEnabledAt(index, model.isTabEnabled());
        tpTabs.setToolTipTextAt(index, model.getTabTooltipText()); // NOI18N
    }
    
    void undecorateTabTitles () {
        tpTabs.setTitleAt (INDEX_OF_UPDATES_TAB, NbBundle.getMessage (PluginManagerUI.class, "PluginManagerUI_UnitTab_Update_Title")); // NOI18N // Updates tab
        tpTabs.setTitleAt (INDEX_OF_AVAILABLE_TAB, NbBundle.getMessage (PluginManagerUI.class, "PluginManagerUI_UnitTab_Available_Title")); // NOI18N // Available tab
    }
    
    private int findRowWithFirstUnit (UnitCategoryTableModel model) {
        for (int row = 0; row <= model.getRowCount (); row++) {
            if (model.getUnitAtRow (row) != null) {
                return row;
            }
        }
        return -1;
    }
    
    private void selectFirstRow (UnitTable table) {
        if (table != null && table.getSelectedRow () == -1) {
            UnitCategoryTableModel model = (UnitCategoryTableModel)table.getModel ();
            int row = findRowWithFirstUnit (model);
            if (row != -1) {
                table.getSelectionModel ().setSelectionInterval (row, row);
            }
        }
    }
    
    private void refreshUnitsInBackground(List<UpdateUnit> newUnits) {
        //ensure exclusivity between this refreshUnits code(which can run even after this dialog is disposed) and uninitialization code
        synchronized(initLock) {
            //return immediatelly if uninialization(after removeNotify) was alredy called
            if (units == null) {
                return;
            }
            //TODO: REVIEW THIS CODE - problem is that is called from called from AWT thread
            //UpdateManager.getDefault().getUpdateUnits() should never be called fromn AWT because it may cause
            //long terming starvation because in fact impl. of this method calls AutoUpdateCatalogCache.getCatalogURL
            //which is synchronized and may wait until cache is created
            //even more AutoUpdateCatalog.getUpdateItems () can at first start call refresh and thus writeToCache again
            units = newUnits;
            InstalledTableModel installTableModel = (InstalledTableModel)installedTable.getModel();
            UnitCategoryTableModel updateTableModel = ((UnitCategoryTableModel)updateTable.getModel());
            UnitCategoryTableModel availableTableModel = ((UnitCategoryTableModel)availableTable.getModel());
            LocallyDownloadedTableModel localTableModel = ((LocallyDownloadedTableModel)localTable.getModel());
            
            updateTableModel.setUnits(UpdateManager.getDefault().getUpdateUnits(UpdateManager.TYPE.MODULE));
            
            List<UpdateUnit> features = UpdateManager.getDefault().getUpdateUnits(UpdateManager.TYPE.FEATURE);
            if (isDetailView() && !features.isEmpty()) {
                installTableModel.setUnits(units);
            } else {
                installTableModel.setUnits(units, features);
            }
            
            availableTableModel.setUnits(units);
            localTableModel.setUnits(units);
        }
    }
    
    private void refreshUnitsInAWT() {
        assert SwingUtilities.isEventDispatchThread() : "Call from AWT only";
        //ensure exclusivity between this refreshUnits code(which can run even after this dialog is disposed) and uninitialization code
        synchronized(initLock) {
            selectFirstRow(installedTable);
            selectFirstRow(updateTable);
            selectFirstRow(availableTable);
            selectFirstRow(localTable);
            decorateTabTitle(updateTable);
            decorateTabTitle(availableTable);
            decorateTabTitle(localTable);
            decorateTabTitle(installedTable);
            Component[] components = tpTabs.getComponents();
            for (Component component : components) {
                if (component instanceof UnitTab) {
                    UnitTab tab = (UnitTab)component;
                    tab.refreshState();
                }
            }
            setSelectedTab();
        }        
    }
        
    static boolean canContinue (String message) {
        return NotifyDescriptor.YES_OPTION.equals (DialogDisplayer.getDefault ().notify (new NotifyDescriptor.Confirmation (message)));
    }

    public static void registerRunningTask (RequestProcessor.Task it) {
        assert runningTask == null || runningTask.isFinished () : "Only once task can be running. Already running : " + runningTask;
        runningTask = it;
    }
    
    public static void unregisterRunningTask () {
        runningTask = null;
        runOnCancel = null;
    }
    
    public static RequestProcessor.Task getRunningTask (Runnable onCancel) {
        if (runningTask != null) {
            if (runOnCancel == null) {
                runOnCancel = new HashSet<Runnable>();
            }
            runOnCancel.add(onCancel);
        }
        return runningTask;
    }
    
    public static RequestProcessor.Task getRunningTask () {
        return runningTask;
    }
    
    public static void cancelRunningTask() {
        if (runningTask != null && runOnCancel != null) {
            runningTask = null;
            for (Runnable run : runOnCancel) {
                run.run();
            }
            runOnCancel = null;
        } else {
            runningTask = null;
        }
    }
    
    //TODO: all the request for refresh should be cancelled if there is already one such running refresh task
    public void updateUnitsChanged () {
        refreshUnitsInBackground(UpdateManager.getDefault().getUpdateUnits(Utilities.getUnitTypes()));
        if (! SwingUtilities.isEventDispatchThread()) {
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    refreshUnitsInAWT();
                }
            });
        } else {
            refreshUnitsInAWT();
        }
    }
    
    public void tableStructureChanged () {
        installedTable.resortByDefault ();
        ((UnitCategoryTableModel) installedTable.getModel ()).fireTableStructureChanged ();
        installedTable.setColumnsSize ();
        installedTable.resetEnableRenderer ();
        updateTable.resortByDefault ();
        ((UnitCategoryTableModel) updateTable.getModel ()).fireTableStructureChanged ();
        updateTable.setColumnsSize ();
        availableTable.resortByDefault ();
        ((UnitCategoryTableModel) availableTable.getModel ()).fireTableStructureChanged ();
        availableTable.setColumnsSize ();
    }
    
    public void buttonsChanged () {
        Component c = tpTabs.getSelectedComponent ();
        if (c instanceof UnitTab) {
            ((UnitTab) c).refreshState ();
        }
    }
    
    final void setSelectedTab(UnitTab tab) {
        tpTabs.setSelectedComponent(tab);
    }

    final UnitTab findTabForModel(UnitCategoryTableModel model) {
        for (Component c : tpTabs.getComponents()) {
            if (c instanceof UnitTab) {
                UnitTab ut = (UnitTab) c;
                if (ut.getModel() == model) {
                    return ut;
                }
            }
        }
        return null;
    }
    
    final String getSelectedTabName() {
        int index = initialTabToSelect;
        if (index == -1) {
            index = tpTabs.getSelectedIndex();
        }
        return TAB_NAMES[index];
    }
}
