package org.limewire.ui.swing.properties;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EventObject;
import java.util.List;

import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;

import net.miginfocom.swing.MigLayout;

import org.jdesktop.application.Resource;
import org.jdesktop.swingx.decorator.SortKey;
import org.limewire.bittorrent.Torrent;
import org.limewire.bittorrent.TorrentEvent;
import org.limewire.bittorrent.TorrentEventType;
import org.limewire.bittorrent.TorrentFileEntry;
import org.limewire.collection.glazedlists.GlazedListsFactory;
import org.limewire.core.api.library.PropertiableFile;
import org.limewire.listener.EventListener;
import org.limewire.ui.swing.action.AbstractAction;
import org.limewire.ui.swing.components.HyperlinkButton;
import org.limewire.ui.swing.table.AbstractTableFormat;
import org.limewire.ui.swing.table.DefaultLimeTableCellRenderer;
import org.limewire.ui.swing.table.FileSizeRenderer;
import org.limewire.ui.swing.table.MouseableTable;
import org.limewire.ui.swing.table.TableCellHeaderRenderer;
import org.limewire.ui.swing.util.EventListJXTableSorting;
import org.limewire.ui.swing.util.EventListTableSortFormat;
import org.limewire.ui.swing.util.FontUtils;
import org.limewire.ui.swing.util.GuiUtils;
import org.limewire.ui.swing.util.I18n;
import org.limewire.ui.swing.util.SwingUtils;
import org.limewire.util.Objects;

import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.ObservableElementList;
import ca.odell.glazedlists.SortedList;
import ca.odell.glazedlists.gui.AdvancedTableFormat;
import ca.odell.glazedlists.swing.DefaultEventTableModel;

public class FileInfoBittorrentPanel implements FileInfoPanel, EventListener<TorrentEvent> {

    @Resource private Font selectFont;
    
    public static final String TORRENT_FILE_ENTRY_SELECTED = "torrentFileEntrySelected";
    
    private static final int DONT_DOWNLOAD = 0;

    private static final int LOWEST_PRIORITY = 1;

    private static final int NORMAL_PRIORITY = 2;

    private static final int HIGHEST_PRIORITY = 3;

    private final Torrent torrent;

    private final JPanel component;

    private BitTorrentTable table;
    
    private EventListJXTableSorting tableSorting;

    /**
     * Items in the eventList are expected to be in the order that they are
     * returned from the Torrent instance. This is so we can pull items out by
     * the matching index in the TorrentFileEntry.
     */
    private EventList<TorrentFileEntryWrapper> eventList;

    private PropertyChangeSupport support = new PropertyChangeSupport(this);
    
    public FileInfoBittorrentPanel(Torrent torrent) {
        GuiUtils.assignResources(this);
        this.torrent = torrent;

        component = new JPanel(new MigLayout("fill", "[grow]", "[][grow]"));

        init();
    }

    @Override
    public JComponent getComponent() {
        return component;
    }

    @Override
    public void updatePropertiableFile(PropertiableFile file) {
        // do nothing
    }

    private void init() {
        component.setOpaque(false);

        ObservableElementList.Connector<TorrentFileEntryWrapper> torrentFileEntryConnector = GlazedLists
                .beanConnector(TorrentFileEntryWrapper.class);
        eventList = GlazedListsFactory.observableElementList(
                new BasicEventList<TorrentFileEntryWrapper>(), torrentFileEntryConnector);
        
        List<TorrentFileEntry> fileEntries = torrent.getTorrentFileEntries();
        for (TorrentFileEntry entry : fileEntries) {
            eventList.add(new TorrentFileEntryWrapper(entry));
        }

        
        // NOTE: this sortedList should never be used for iterating over torrent values, its strictly used to
        // sort the table
        SortedList<TorrentFileEntryWrapper> sortedList = GlazedListsFactory.sortedList(eventList, null);
        
        BitTorrentTableFormat tableFormat = new BitTorrentTableFormat();
        table = new BitTorrentTable(new DefaultEventTableModel<TorrentFileEntryWrapper>(sortedList, tableFormat));
        
        tableSorting = EventListJXTableSorting.install(table, sortedList, tableFormat);

        JLabel selectLabel = new JLabel(I18n.tr("Select"));
        selectLabel.setFont(selectFont);
        
        HyperlinkButton allButton = new HyperlinkButton(new SelectAll(I18n.tr("all")));
        allButton.setFont(selectFont);
        
        HyperlinkButton noneButton = new HyperlinkButton(new SelectNone(I18n.tr("none")));
        noneButton.setFont(selectFont);
        
        // only show add/remove all buttons if is downloadng
        if(!torrent.isFinished()) {
            component.add(selectLabel, "gapleft 5, gaptop 2, split 3");
            component.add(allButton, "gapleft 6, gaptop 2");
            component.add(noneButton, "gapleft 6, gaptop 2, wrap");
        }
        
        JScrollPane scrollPane = new JScrollPane(table);
        configureEnclosingScrollPane(scrollPane);
        
        component.add(scrollPane, "grow, gaptop 7");
        
        torrent.addListener(this);
    }

    @Override
    public boolean hasChanged() {
        boolean hasChanged = false;
        for (TorrentFileEntryWrapper wrapper : eventList) {
            if (wrapper.hasChanged()) {
                hasChanged = true;
                break;
            }
        }
        return hasChanged;
    }

    @Override
    public void save() {
        if (!torrent.isEditable()) {
            return;
        }
        torrent.getLock().lock();
        try {
            if (hasChanged() && !torrent.isFinished() && torrent.isValid()) {
                for (TorrentFileEntryWrapper wrapper : eventList) {
                    torrent.setTorrenFileEntryPriority(wrapper.getTorrentFileEntry(), wrapper
                            .getPriority());
                }
            }
        } finally {
            torrent.getLock().unlock();
        }
    }

    @Override
    public void dispose() {
        tableSorting.uninstall();
        torrent.removeListener(this);
    }
    
    /**
     * Fills in the top right corner if a scrollbar appears with an empty table
     * header.
     */
    protected void configureEnclosingScrollPane(JScrollPane scrollPane) {
        JTableHeader th = new JTableHeader();
        th.setDefaultRenderer(new TableCellHeaderRenderer());
        // Put a dummy header in the upper-right corner.
        final Component renderer = th.getDefaultRenderer().getTableCellRendererComponent(null, "", false, false, -1, -1);
        JPanel cornerComponent = new JPanel(new BorderLayout());
        cornerComponent.add(renderer, BorderLayout.CENTER);
        scrollPane.setCorner(ScrollPaneConstants.UPPER_RIGHT_CORNER, cornerComponent);
    }

    private class BitTorrentTable extends MouseableTable {
        boolean torrentPartSelected = true;
        public BitTorrentTable(final DefaultEventTableModel<TorrentFileEntryWrapper> model) {
            super(model);
            setShowHorizontalLines(false);
            setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
            setColumnSelectionAllowed(false);
            final CheckBoxRendererEditor checkBoxEditor = new CheckBoxRendererEditor();
            checkBoxEditor.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (checkBoxEditor.getCellEditorValue() != null) {
                        checkBoxEditor.getCellEditorValue().setPriority(checkBoxEditor.isSelected() ? 1 : 0);
                        checkBoxEditor.cancelCellEditing();
                    }
                    
                    validateSelection();
                    
                    BitTorrentTable.this.repaint();
                }
            });
            getColumn(BitTorrentTableFormat.DOWNLOAD_INDEX).setCellRenderer(new CheckBoxRendererEditor());
            getColumn(BitTorrentTableFormat.DOWNLOAD_INDEX).setCellEditor(checkBoxEditor);
            
            getColumn(BitTorrentTableFormat.SIZE_INDEX).setCellRenderer(new FileSizeRenderer());
            getColumn(BitTorrentTableFormat.SIZE_INDEX).setMaxWidth(72);

            getColumn(BitTorrentTableFormat.PERCENT_INDEX).setCellRenderer(new PercentRenderer());
            getColumn(BitTorrentTableFormat.PERCENT_INDEX).setMaxWidth(20);
            getColumn(BitTorrentTableFormat.NAME_INDEX).setCellRenderer(new DefaultLimeTableCellRenderer());
            getColumn(BitTorrentTableFormat.NAME_INDEX).setMinWidth(140);
            
            final PriorityRendererEditor editor = new PriorityRendererEditor();
            editor.getButton().addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    int oldPriority = editor.getCellEditorValue().getPriority();
                    if (oldPriority != DONT_DOWNLOAD) {
                        editor.getCellEditorValue().setPriority(
                                oldPriority + 1 > HIGHEST_PRIORITY ? LOWEST_PRIORITY
                                        : (oldPriority + 1));
                        editor.cancelCellEditing();
                        BitTorrentTable.this.repaint();
                    }
                }

            });
            getColumn(BitTorrentTableFormat.PRIORITY_INDEX).setCellRenderer(
                    new PriorityRendererEditor());
            getColumn(BitTorrentTableFormat.PRIORITY_INDEX).setCellEditor(editor);

            getColumnExt(BitTorrentTableFormat.DOWNLOAD_INDEX).setMaxWidth(30);
            getColumnExt(BitTorrentTableFormat.DOWNLOAD_INDEX).setMinWidth(30);

            getColumnExt(BitTorrentTableFormat.PERCENT_INDEX).setMaxWidth(50);
            getColumnExt(BitTorrentTableFormat.PERCENT_INDEX).setMinWidth(50);

            getColumnExt(BitTorrentTableFormat.PRIORITY_INDEX).setMaxWidth(60);
            getColumnExt(BitTorrentTableFormat.PRIORITY_INDEX).setMinWidth(60);
            
            boolean editable = torrent.isEditable();
            getColumnExt(BitTorrentTableFormat.PRIORITY_INDEX).setVisible(editable);
            getColumnExt(BitTorrentTableFormat.PERCENT_INDEX).setVisible(editable);
            getColumnExt(BitTorrentTableFormat.DOWNLOAD_INDEX).setVisible(editable);
            
        }
        
        private void validateSelection() {
            if ( isAnyTorrentPartSelected() != torrentPartSelected ) {
                torrentPartSelected = !torrentPartSelected;
                support.firePropertyChange(TORRENT_FILE_ENTRY_SELECTED, !torrentPartSelected, torrentPartSelected);
            }
        }
        
        @SuppressWarnings("unchecked")
        private boolean isAnyTorrentPartSelected() {
            DefaultEventTableModel<TorrentFileEntryWrapper> model = (DefaultEventTableModel<TorrentFileEntryWrapper>)getModel();
            for (int counter = 0; counter < model.getRowCount(); counter++) {
                TorrentFileEntryWrapper torrentFile = model.getElementAt(counter);
                if (torrentFile.getPriority() != 0)
                    return true;
            }
            return false;
        }
    }

    private class BitTorrentTableFormat extends AbstractTableFormat<TorrentFileEntryWrapper> implements EventListTableSortFormat, AdvancedTableFormat<TorrentFileEntryWrapper> {

        private static final int DOWNLOAD_INDEX = 0;
        private static final int NAME_INDEX = 1;
        private static final int SIZE_INDEX = 2;
        private static final int PERCENT_INDEX = 3;
        private static final int PRIORITY_INDEX = 4;

        public BitTorrentTableFormat() {
            super(I18n.tr("DL"), I18n.tr("Name"), I18n.tr("Size"), I18n.tr("%"), I18n.tr("Priority"));
        }

        @Override
        public Object getColumnValue(TorrentFileEntryWrapper baseObject, int column) {
            switch (column) {
            case DOWNLOAD_INDEX:
                return baseObject;
            case NAME_INDEX:
                return baseObject.getPath();
            case SIZE_INDEX:
                return baseObject.getSize();
            case PERCENT_INDEX:
                return baseObject;
            case PRIORITY_INDEX:
                return baseObject;
            }
            throw new IllegalStateException("Unknown column:" + column);
        }
        
        @Override
        public Comparator getColumnComparator(int column) {
            switch(column) {
            case DOWNLOAD_INDEX:
                return new SelectedComparator();
            case NAME_INDEX:
                return new NameComparator();
            case SIZE_INDEX:
                return Objects.getComparator(true);
            case PERCENT_INDEX:
                return new PercentComparator();
            case PRIORITY_INDEX:
                return new PriorityComparator();
            }
            throw new IllegalStateException("Unknown column:" + column);
        }

        @Override
        public List<SortKey> getDefaultSortKeys() {
            return Collections.emptyList();
        }

        @Override
        public List<SortKey> getPreSortColumns() {
            return Collections.emptyList();
        }

        @Override
        public List<Integer> getSecondarySortColumns(int column) {
            return Collections.emptyList();
        }

        @Override
        public Class getColumnClass(int column) {
            switch(column) {
            case DOWNLOAD_INDEX:
                return TorrentFileEntryWrapper.class;
            case NAME_INDEX:
                return String.class;
            case SIZE_INDEX:
                return Long.class;
            case PERCENT_INDEX:
                return TorrentFileEntryWrapper.class;
            case PRIORITY_INDEX:
                return TorrentFileEntryWrapper.class;
            }
            throw new IllegalStateException("Unknown column:" + column);
        }
    }
    
    /**
     * Compares whether two TorrentFileEntries are selected or not. 
     */
    private class SelectedComparator implements Comparator<TorrentFileEntryWrapper> {
        @Override
        public int compare(TorrentFileEntryWrapper o1, TorrentFileEntryWrapper o2) {
            boolean o1isSelected = getIsSelected(o1);
            boolean o2isSelected = getIsSelected(o2);
            
            if(o1isSelected && o2isSelected)
                return 0;
            else if(o1isSelected)
                return 1;
            else 
                return -1;
        }
        
        private boolean getIsSelected(TorrentFileEntryWrapper wrapper) {
            if (torrent.isFinished()) {
                return wrapper.getProgress() == 1.0f && wrapper.getPriority() > DONT_DOWNLOAD;
            } else if (wrapper.getProgress() == 1.0f) {
                return true;
            } else {
                return wrapper.getPriority() != DONT_DOWNLOAD;
            }
        }
    }
    
    /**
     * Compares the Percent complete of two TorrentFileEntries. If the file is
     * not being downloaded, any percent completed is ignored.
     */
    private class PercentComparator implements Comparator<TorrentFileEntryWrapper> {
        @Override
        public int compare(TorrentFileEntryWrapper o1, TorrentFileEntryWrapper o2) {
            int o1Percent = (int) o1.getProgress() * 100;
            int o2Percent = (int) o2.getProgress() * 100;
            
            if(o1.getPriority() == DONT_DOWNLOAD)
                o1Percent = -1;
            if(o2.getPriority() == DONT_DOWNLOAD)
                o2Percent = -1;

            return o1Percent - o2Percent;
        }       
    }
    
    /**
     * Compares the Priority of two TorrentFileEntries. Completed torrents
     * are considered higher priority than the max priority setting.
     */
    private class PriorityComparator implements Comparator<TorrentFileEntryWrapper> {
        @Override
        public int compare(TorrentFileEntryWrapper o1, TorrentFileEntryWrapper o2) {
            int o1Priority = getPriority(o1);
            int o2Priority = getPriority(o2);
            
            return o1Priority - o2Priority;
        }
        
        private int getPriority(TorrentFileEntryWrapper wrapper) {
            if (wrapper.getProgress() == 1.0f) {
                return 4;
            } else {
                return wrapper.getPriority();
            }
        }
    }
    
    /**
     * Comapares the path/filename of two TorrentFileEntries.
     */
    private class NameComparator implements Comparator<String> {
        @Override
        public int compare(String o1, String o2) {
            return o1.compareToIgnoreCase(o2);
        }
    }

    private class CheckBoxRendererEditor extends JCheckBox implements TableCellRenderer,
            TableCellEditor {

        /** List of cell editor listeners. */
        private final List<CellEditorListener> listenerList = new ArrayList<CellEditorListener>();

        private TorrentFileEntryWrapper currentWrapper;

        public CheckBoxRendererEditor() {
            setHorizontalAlignment(SwingConstants.CENTER);
        }

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value,
                boolean isSelected, boolean hasFocus, int row, int column) {
            return getTableCellComponent(table, value, isSelected, column, column);
        }

        @Override
        public Component getTableCellEditorComponent(JTable table, Object value,
                boolean isSelected, int row, int column) {
            return getTableCellComponent(table, value, isSelected, column, column);
        }

        private Component getTableCellComponent(JTable table, Object value, boolean isSelected,
                int row, int column) {
            if (value instanceof TorrentFileEntryWrapper) {
                currentWrapper = (TorrentFileEntryWrapper) value;

                if (torrent.isFinished()) {
                    setEnabled(false);
                    setSelected(currentWrapper.getProgress() == 1.0f
                            && currentWrapper.getPriority() > DONT_DOWNLOAD);
                } else if (currentWrapper.getProgress() == 1.0f) {
                    setSelected(true);
                    setEnabled(false);
                } else {
                    setSelected(((TorrentFileEntryWrapper) value).getPriority() != DONT_DOWNLOAD);
                    setEnabled(!torrent.isFinished());
                }
            } else {
                currentWrapper = null;
                setSelected(false);
            }

            return this;
        }

        @Override
        public void addCellEditorListener(CellEditorListener l) {
            if (!listenerList.contains(l)) {
                listenerList.add(l);
            }
        }

        @Override
        public void cancelCellEditing() {
            ChangeEvent event = new ChangeEvent(this);
            for (int i = 0, size = listenerList.size(); i < size; i++) {
                listenerList.get(i).editingCanceled(event);
            }
        }

        @Override
        public TorrentFileEntryWrapper getCellEditorValue() {
            return currentWrapper;
        }

        @Override
        public boolean isCellEditable(EventObject anEvent) {
            return true;
        }

        @Override
        public void removeCellEditorListener(CellEditorListener l) {
            listenerList.remove(l);
        }

        @Override
        public boolean shouldSelectCell(EventObject anEvent) {
            return true;
        }

        @Override
        public boolean stopCellEditing() {
            cancelCellEditing();
            return true;
        }
    }

    private class PercentRenderer extends DefaultLimeTableCellRenderer {
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value,
                boolean isSelected, boolean hasFocus, int row, int column) {
            super.getTableCellRendererComponent(table, value, isSelected, false, row, column);

            if (value instanceof TorrentFileEntryWrapper) {
                float percent = ((TorrentFileEntryWrapper) value).getProgress();
                if (torrent.isFinished()) {
                    if (percent == 1.0f
                            && ((TorrentFileEntryWrapper) value).getPriority() > DONT_DOWNLOAD) {
                        setText(I18n.tr("Done"));
                    } else {
                        setText("");
                    }
                } else if (((TorrentFileEntryWrapper) value).getPriority() == DONT_DOWNLOAD) {
                    setText("");
                } else {
                    setText((int) (percent * 100) + "%");
                }
            } else {
                setText("");
            }

            return this;
        }
    }

    private class PriorityRendererEditor extends JPanel implements TableCellRenderer,
            TableCellEditor {

        /** List of cell editor listeners. */
        private final List<CellEditorListener> listenerList = new ArrayList<CellEditorListener>();

        @Resource
        private Icon lowestPriorityIcon;

        @Resource
        private Icon normalPriorityIcon;

        @Resource
        private Icon highestPriorityIcon;

        @Resource
        private Font textFont;

        @Resource
        private Color fontColor;

        private final JButton button;

        private TorrentFileEntryWrapper currentWrapper;

        public PriorityRendererEditor() {
            super(new MigLayout("align 50%"));
            GuiUtils.assignResources(this);

            button = new JButton();
            button.setBorderPainted(false);
            button.setContentAreaFilled(false);
            button.setFont(textFont);
            button.setFocusPainted(false);
            button.setForeground(fontColor);
            FontUtils.underline(button);

            add(button);
        }

        public JButton getButton() {
            return button;
        }

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value,
                boolean isSelected, boolean hasFocus, int row, int column) {
            return getTableCellComponent(table, value, isSelected, row, column);
        }

        @Override
        public Component getTableCellEditorComponent(JTable table, Object value,
                boolean isSelected, int row, int column) {
            return getTableCellComponent(table, value, isSelected, row, column);
        }

        private Component getTableCellComponent(JTable table, Object value, boolean isSelected,
                int row, int column) {
            if (value instanceof TorrentFileEntryWrapper) {
                currentWrapper = (TorrentFileEntryWrapper) value;
                int priority = ((TorrentFileEntryWrapper) value).getPriority();
                if (((TorrentFileEntryWrapper) value).getProgress() == 1.0f) {
                    button.setIcon(null);
                    button.setText("");
                    // button.setText(I18n.tr("delete"));
                } else if (priority == DONT_DOWNLOAD) {
                    button.setIcon(null);
                    button.setText("");
                } else if (priority == LOWEST_PRIORITY) {
                    button.setIcon(lowestPriorityIcon);
                    button.setText("");
                } else if (priority == NORMAL_PRIORITY) {
                    button.setIcon(normalPriorityIcon);
                    button.setText("");
                } else if (priority == HIGHEST_PRIORITY) {
                    button.setIcon(highestPriorityIcon);
                    button.setText("");
                }
            } else {
                currentWrapper = null;
                button.setIcon(null);
                button.setText("");
            }
            return this;
        }

        @Override
        public void addCellEditorListener(CellEditorListener l) {
            if (!listenerList.contains(l)) {
                listenerList.add(l);
            }
        }

        @Override
        public void cancelCellEditing() {
            ChangeEvent event = new ChangeEvent(this);
            for (int i = 0, size = listenerList.size(); i < size; i++) {
                listenerList.get(i).editingCanceled(event);
            }
        }

        @Override
        public TorrentFileEntryWrapper getCellEditorValue() {
            return currentWrapper;
        }

        @Override
        public boolean isCellEditable(EventObject anEvent) {
            return true;
        }

        @Override
        public void removeCellEditorListener(CellEditorListener l) {
            listenerList.remove(l);
        }

        @Override
        public boolean shouldSelectCell(EventObject anEvent) {
            return true;
        }

        @Override
        public boolean stopCellEditing() {
            cancelCellEditing();
            return true;
        }
    }

    public void addPropertyChangeListener( PropertyChangeListener listener )
    {
        support.addPropertyChangeListener(listener);
    }
    
    public void removePropertyChangeListener( PropertyChangeListener listener )
    {
        support.removePropertyChangeListener(listener);
    }
    
    @Override
    public void handleEvent(TorrentEvent event) {
        if (event.getType() == TorrentEventType.STATUS_CHANGED || event.getType() == TorrentEventType.COMPLETED) {
            SwingUtils.invokeNowOrLater(new Runnable() {
                @Override
                public void run() {
                    List<TorrentFileEntry> fileEntries = torrent.getTorrentFileEntries();
                    for (TorrentFileEntry newEntry : fileEntries) {
                        TorrentFileEntryWrapper wrapper = eventList.get(newEntry.getIndex());
                        wrapper.setTorrentFileEntry(newEntry);
                    }
                }
            });
        }
    }
       
    private final class SelectNone extends AbstractAction {
        private SelectNone(String name) {
            super(name);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            torrent.getLock().lock();
            try {
                if(!torrent.isFinished()) {
                    for (TorrentFileEntryWrapper wrapper : eventList) {
                        if(!(wrapper.getProgress() == 1.0f)) {
                            wrapper.setPriority(DONT_DOWNLOAD);
                        }
                    }
                }
            } finally {
                torrent.getLock().unlock();
            }
            
            table.repaint();
            table.validateSelection();
        }
    }

    private final class SelectAll extends AbstractAction {
        private SelectAll(String name) {
            super(name);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            torrent.getLock().lock();
            try {
                if(!torrent.isFinished()) {
                    for (TorrentFileEntryWrapper wrapper : eventList) {
                        if(wrapper.getPriority() == DONT_DOWNLOAD) {
                            wrapper.setPriority(LOWEST_PRIORITY);
                        }
                    }
                }
            } finally {
                torrent.getLock().unlock();
            }
            table.repaint();
            table.validateSelection();
        }
    }
}
