package org.limewire.ui.swing.library.sharing;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.table.TableCellEditor;

import net.miginfocom.swing.MigLayout;

import org.jdesktop.application.Resource;
import org.jdesktop.swingx.JXButton;
import org.jdesktop.swingx.JXTable;
import org.limewire.collection.glazedlists.GlazedListsFactory;
import org.limewire.friend.api.Friend;
import org.limewire.inject.LazySingleton;
import org.limewire.ui.swing.components.HyperlinkButton;
import org.limewire.ui.swing.components.PromptTextField;
import org.limewire.ui.swing.components.decorators.ButtonDecorator;
import org.limewire.ui.swing.components.decorators.TextFieldDecorator;
import org.limewire.ui.swing.painter.BorderPainter.AccentType;
import org.limewire.ui.swing.table.GlazedJXTable;
import org.limewire.ui.swing.util.GuiUtils;
import org.limewire.ui.swing.util.I18n;

import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.SortedList;
import ca.odell.glazedlists.TextFilterator;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.event.ListEventListener;
import ca.odell.glazedlists.gui.WritableTableFormat;
import ca.odell.glazedlists.impl.swing.SwingThreadProxyEventList;
import ca.odell.glazedlists.matchers.MatcherEditor;
import ca.odell.glazedlists.swing.DefaultEventTableModel;
import ca.odell.glazedlists.swing.TextComponentMatcherEditor;

import com.google.inject.Inject;
import com.google.inject.name.Named;

@LazySingleton
class LibrarySharingEditablePanel {
    
    @Resource private Color borderColor;
    @Resource private Font sharingLabelFont;
    @Resource private Color sharingLabelColor;
    @Resource private Font selectFont;
    @Resource private Color selectColor;
    
    private final JPanel component;
    private final PromptTextField filterTextField;
    private final HyperlinkButton allButton;
    private final HyperlinkButton noneButton;
    private final JXButton applyButton;
    private final HyperlinkButton cancelButton;
    
    private final Map<String, Friend> knownFriends;
    
    private final EventList<EditableSharingData> baseEventList;
    private final EventList<EditableSharingData> filteredList;
    private final JXTable friendTable;
    
    @Inject
    public LibrarySharingEditablePanel(ApplySharingAction applyAction,
            CancelSharingAction cancelAction, LibrarySharingAction libraryAction, TextFieldDecorator textFieldDecorator,
            ButtonDecorator buttonDecorator, @Named("known") Map<String, Friend> knownFriends) {
        GuiUtils.assignResources(this);
        this.knownFriends = knownFriends;
        
        component = new JPanel(new MigLayout("insets 0, gap 0, fillx", "[134!]", ""));        
        component.setOpaque(false);
        
        JLabel shareLabel = new JLabel(I18n.tr("Sharing list with..."));
        shareLabel.setFont(sharingLabelFont);
        shareLabel.setForeground(sharingLabelColor);
        component.add(shareLabel, "gapleft 5, gaptop 5, wrap");        
        
        filterTextField = new PromptTextField(I18n.tr("Filter..."));
        textFieldDecorator.decorateClearablePromptField(filterTextField, AccentType.NONE);
        component.add(filterTextField, "gapleft 5, gaptop 4, gapright 5, wmax 124, wrap");

        JLabel selectLabel = new JLabel(I18n.tr("Select"));
        selectLabel.setFont(selectFont);
        selectLabel.setForeground(selectColor);
        component.add(selectLabel, "gapleft 5, gaptop 2, split 3");
        
        allButton = new HyperlinkButton(new AbstractAction(I18n.tr("all")) {
            @Override
            public void actionPerformed(ActionEvent e) {
                setSelectedStateForAll(true);
            }
        });
        allButton.setFont(selectFont);
        noneButton = new HyperlinkButton(new AbstractAction(I18n.tr("none")) {
            @Override
            public void actionPerformed(ActionEvent e) {
                setSelectedStateForAll(false);
            }
        });
        noneButton.setFont(selectFont);
        component.add(allButton, "gapleft 6, gaptop 2");
        component.add(noneButton, "gapleft 6, gaptop 2, wrap");
                
        baseEventList = new BasicEventList<EditableSharingData>();
        SortedList<EditableSharingData> sortedList = GlazedListsFactory.sortedList(baseEventList, new FriendComparator());        
        MatcherEditor<EditableSharingData> matcher = new TextComponentMatcherEditor<EditableSharingData>(filterTextField, new FriendFilterator());
        filteredList = GlazedListsFactory.filterList(sortedList, matcher);
        SwingThreadProxyEventList<EditableSharingData> stpl = GlazedListsFactory.swingThreadProxyEventList(filteredList);
        friendTable = new GlazedJXTable(new DefaultEventTableModel<EditableSharingData>(stpl, new EditTableFormat())) {
            @Override
            public void editingStopped(ChangeEvent e) {
                TableCellEditor editor = getCellEditor();
                if(editor != null) {
                    removeEditor();
                }
            }
            
            @Override
            public Dimension getPreferredScrollableViewportSize() {
                return new Dimension(super.getPreferredScrollableViewportSize().width, getModel().getRowCount() * getRowHeight());
            }
        };
        // the table is strictly the size of the number of rows or full screen with a scrollbar
        // if it surpasses available space. When adding/removing friends or filtering need to
        // revalidate the size to correctly update the panel and table sizing.
        filteredList.addListEventListener(new ListEventListener<EditableSharingData>(){
            @Override
            public void listChanged(ListEvent<EditableSharingData> listChanges) {
                if(LibrarySharingEditablePanel.this.getComponent().isShowing()) {
                    SwingUtilities.invokeLater(new Runnable(){
                        @Override
						public void run() {
                            component.revalidate();
                        }
                    });
                }
            }
        });
        
        JScrollPane scrollPane = new JScrollPane(friendTable);
        scrollPane.setMinimumSize(new Dimension(0,0));
        scrollPane.setBorder(BorderFactory.createMatteBorder(1,0,1,0, borderColor)); 
        friendTable.getColumnExt(0).setCellRenderer(new LibrarySharingEditableRendererEditor());
        friendTable.getColumnExt(0).setCellEditor(new LibrarySharingEditableRendererEditor());
        friendTable.setTableHeader(null);
        friendTable.setShowGrid(false, false);
        friendTable.setRowHeight(20);
                
        component.add(scrollPane, "growx, gaptop 3, wrap");
        
        applyButton = new JXButton(applyAction);
        applyButton.setFont(selectFont);
        applyButton.addActionListener(libraryAction);
        buttonDecorator.decorateDarkFullButton(applyButton, AccentType.NONE);
        cancelButton = new HyperlinkButton(cancelAction);
        cancelButton.setFont(selectFont);
        cancelButton.addActionListener(libraryAction);
        
        component.add(applyButton, "split 2, gaptop 5, gapbottom 5, gapright unrelated, alignx center");
        component.add(cancelButton, "gaptop 5, gapbottom 5, wrap");
    }
    
    public JComponent getComponent() {
        return component;
    }
    
    void clear() {
        baseEventList.clear();
    }
    
    void refreshSelectedList() {
        Set<String> selectedIds = new HashSet<String>();
        for(EditableSharingData data : baseEventList) {
            if(data.isSelected()) {
                selectedIds.addAll(data.getIds());
            }
        }
        addItemsToList(selectedIds);
    }
    
    void editWithSelectedIds(List<String> selectedIds) {
        filterTextField.setText("");
        addItemsToList(new HashSet<String>(selectedIds));
    }
    
    private void addItemsToList(Set<String> setOfIds) {
        baseEventList.clear();
        for(Friend friend : knownFriends.values()) {
            baseEventList.add(new EditableSharingData(friend, setOfIds.remove(friend.getId())));
        }
        if(!setOfIds.isEmpty()) {
            baseEventList.add(new EditableSharingData(new ArrayList<String>(setOfIds), true));
        }
        component.revalidate();
    }
    
    /**
     * Returns a list of Friends who this list is shared with.
     */
    List<String> getSelectedFriendIds() {
        List<String> friends = new ArrayList<String>();
        for(EditableSharingData data : baseEventList) {
            if(data.isSelected()) {
                friends.addAll(data.getIds());
            }
        }
        return friends;
    }
    
    private void setSelectedStateForAll(boolean isSelected) {
        for(EditableSharingData data : filteredList) {
            data.setSelected(isSelected);
        }
        friendTable.repaint();
    }
    
    /**
     * Filters on the displayed name of a Friend.
     */
    private static class FriendFilterator implements TextFilterator<EditableSharingData> {
        @Override
        public void getFilterStrings(List<String> baseList, EditableSharingData data) {
            Friend friend = data.getFriend();
            if(friend != null) {
                if(friend.getName() != null) {
                    baseList.add(friend.getName());
                }
                baseList.add(friend.getId());
            }
        }
    }
    
    private static class FriendComparator implements Comparator<EditableSharingData> {
        @Override
        public int compare(EditableSharingData data1, EditableSharingData data2) {
            Friend friend1 = data1.getFriend();
            Friend friend2 = data2.getFriend();
            if(friend1 == friend2)
                return 0;
            if(friend1 == null || friend2 == null) {
                if(friend1 == null && friend2 == null)
                    return 0;
                else if(friend1 == null)
                    return 1;
                else
                    return -1;
            } else
                return friend1.getRenderName().compareToIgnoreCase(friend2.getRenderName());
        }
    }
    
    private static class EditTableFormat implements WritableTableFormat<EditableSharingData> {
        @Override
        public int getColumnCount() {
            return 1;
        }

        @Override
        public String getColumnName(int column) {
            return "";
        }

        @Override
        public Object getColumnValue(EditableSharingData baseObject, int column) {
            return baseObject;
        }
        
        @Override
        public boolean isEditable(EditableSharingData baseObject, int column) {
            return true;
        }
        
        @Override
        public EditableSharingData setColumnValue(EditableSharingData baseObject,
                Object editedValue, int column) {
            return baseObject;
        }
        
    }
}
