package org.limewire.ui.swing.options;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.EventObject;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;

import net.miginfocom.swing.MigLayout;

import org.jdesktop.swingx.JXTable;
import org.limewire.bittorrent.TorrentManager;
import org.limewire.bittorrent.TorrentManagerSettings;
import org.limewire.bittorrent.TorrentSettingsAnnotation;
import org.limewire.core.settings.ConnectionSettings;
import org.limewire.ui.swing.table.AbstractTableFormat;
import org.limewire.ui.swing.util.BackgroundExecutorService;
import org.limewire.ui.swing.util.I18n;

import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.swing.DefaultEventTableModel;

import com.google.inject.Inject;
import com.google.inject.Provider;

/**
 * Network Interface Option View.
 */
public class NetworkInterfaceOptionPanel extends OptionPanel {

    private ButtonGroup buttonGroup;
    
    private JRadioButton limewireChooseRadioButton;
    private JRadioButton meChooseRadioButton;
    private NetworkTable table;
    private JScrollPane scrollPane;
    
    private NetworkItem selectedItem;
    
    private EventList<NetworkItem> eventList;
    
    private final Provider<TorrentManager> torrentManager;
    private final TorrentManagerSettings torrentSettings;
    
    @Inject
    public NetworkInterfaceOptionPanel(Provider<TorrentManager> torrentManager, @TorrentSettingsAnnotation TorrentManagerSettings torrentSettings) {
        this.torrentManager = torrentManager;
        this.torrentSettings = torrentSettings;
        
        setLayout(new MigLayout("insets 15, fillx, wrap"));
        setOpaque(false);
        
        add(getNetworkPanel(), "pushx, growx");
    }
    
    private JPanel getNetworkPanel() {
        JPanel p = new JPanel();
        p.setBorder(BorderFactory.createTitledBorder(""));
        p.setLayout(new MigLayout("fillx, gapy 10"));
        p.setOpaque(false);
        
        limewireChooseRadioButton = new JRadioButton(I18n.tr("Let WireShare choose my network interface (Recommended)"));
        meChooseRadioButton = new JRadioButton(I18n.tr("Use a specific network interface"));
        
        limewireChooseRadioButton.setOpaque(false);
        meChooseRadioButton.setOpaque(false);
        
        buttonGroup = new ButtonGroup();
        buttonGroup.add(limewireChooseRadioButton);
        buttonGroup.add(meChooseRadioButton);
        
        eventList = GlazedLists.threadSafeList(new BasicEventList<NetworkItem>());
        table = new NetworkTable(new DefaultEventTableModel<NetworkItem>(eventList, new NetworkTableFormat()));
        scrollPane = new JScrollPane(table);
        scrollPane.setVisible(false);
        
        meChooseRadioButton.addItemListener(new RadioListener(scrollPane, meChooseRadioButton));

        p.add(limewireChooseRadioButton, "wrap");
        
        p.add(meChooseRadioButton, "wrap");
        
        p.add(scrollPane, "grow, span");
        
        return p;
    }
    
    @Override
    ApplyOptionResult applyOptions() {
        boolean customBefore = ConnectionSettings.CUSTOM_NETWORK_INTERFACE.getValue();
        boolean customNow = meChooseRadioButton.isSelected();
        ConnectionSettings.CUSTOM_NETWORK_INTERFACE.setValue(customNow);
        String addressBefore = ConnectionSettings.CUSTOM_INETADRESS.get();
        String addressNow = null;
        for(NetworkItem item : eventList) {
            if(item.isSelected()) {
                addressNow = item.getAddress();
                ConnectionSettings.CUSTOM_INETADRESS.set(addressNow);
                break;
            }
        }
        boolean restart = customBefore != customNow || !addressBefore.equals(addressNow);
        
        if(torrentManager.get().isInitialized() && torrentManager.get().isValid()) {
            BackgroundExecutorService.execute(new Runnable() {
               @Override
                public void run() {
                   torrentManager.get().setTorrentManagerSettings(torrentSettings);
                } 
            });
        }
        return new ApplyOptionResult(restart, true);
    }

    @Override
    boolean hasChanged() {
        if(!ConnectionSettings.CUSTOM_NETWORK_INTERFACE.getValue())
            return !limewireChooseRadioButton.isSelected();
        
        String expect = ConnectionSettings.CUSTOM_INETADRESS.get();
        for(NetworkItem item : eventList) {
            if(expect.equals(item.getAddress()));
                return false;
        }
        return true;
    }

    @Override
    public void initOptions() {
        eventList.clear();
        limewireChooseRadioButton.setSelected(!ConnectionSettings.CUSTOM_NETWORK_INTERFACE.getValue());
        meChooseRadioButton.setSelected(ConnectionSettings.CUSTOM_NETWORK_INTERFACE.getValue());
        
        try {
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            
            // Add the available interfaces / addresses
            while(interfaces.hasMoreElements()) {
                NetworkInterface ni = interfaces.nextElement();
                Enumeration<InetAddress> addresses = ni.getInetAddresses();
                while(addresses.hasMoreElements()) {
                    InetAddress address = addresses.nextElement();
                    if(address.isAnyLocalAddress() || address.isLinkLocalAddress() || address.isLoopbackAddress())
                        continue;
                    NetworkItem networkItem = new NetworkItem(address, ni.getDisplayName());
                    if(ConnectionSettings.CUSTOM_INETADRESS.get().equals(address.getHostAddress())) {
                        networkItem.setSelected(true);
                        selectedItem = networkItem;
                    }
                    eventList.add(networkItem);
                }
            }
            if(selectedItem == null && eventList.size() > 0) { 
                eventList.get(0).setSelected(true); 
                selectedItem = eventList.get(0);
            }
        } catch (SocketException e) {
            meChooseRadioButton.setEnabled(false);
        }
        if(eventList.size() == 0 )
            meChooseRadioButton.setEnabled(false);
    }
    
    private static class RadioListener implements ItemListener {

        private JScrollPane scrollPane;
        private JRadioButton radioButton;
        
        public RadioListener(JScrollPane scrollPane, JRadioButton button) {
            this.scrollPane = scrollPane;
            this.radioButton = button;
        }
        
        @Override
        public void itemStateChanged(ItemEvent e) {
            scrollPane.setVisible(radioButton.isSelected());
        }
    }
    
    private class NetworkTable extends JXTable {
        
        public NetworkTable(DefaultEventTableModel<NetworkItem> model) {
            super(model);
            setShowGrid(false, false);
            setColumnSelectionAllowed(false);
            getColumn(0).setCellRenderer(new CheckBoxRenderer());
            getColumn(0).setCellEditor(new CheckBoxRenderer());
            getColumn(0).setMaxWidth(30);
        }
        
        @Override
        public boolean isCellEditable(int row, int col) {
            if (row >= getRowCount() || col >= getColumnCount() || row < 0 || col < 0) {
                return false;
            }
            return getColumnModel().getColumn(col).getCellEditor() != null;
        }
    }
    
    private static class NetworkTableFormat extends AbstractTableFormat<NetworkItem> {

        private static final int CHECK_INDEX = 0;
        private static final int ADDRESS_INDEX = 1;
        private static final int NAME_INDEX = 2;
        
        public NetworkTableFormat() {
            super("", I18n.tr("Address"), I18n.tr("Name"));
        }

        @Override
        public Object getColumnValue(NetworkItem baseObject, int column) {
            switch(column) {
                case CHECK_INDEX: return baseObject;
                case ADDRESS_INDEX: return baseObject.getAddress();
                case NAME_INDEX: return baseObject.getName();
            }
                
            throw new IllegalStateException("Unknown column:" + column);
        }   
    }
    
    private static class NetworkItem {       
        private boolean isSelected = false;
        private InetAddress address;
        private String displayName;
        
        public NetworkItem(InetAddress address, String displayName) {
            this.address = address;
            this.displayName = displayName;
        }
        
        public String getAddress() {
            return address.getHostAddress();
        }
        
        public String getName() {
            return displayName;
        }
        
        public boolean isSelected() {
            return isSelected;
        }
        
        public void setSelected(boolean value) {
            this.isSelected = value;
        }
    }
    
    private class CheckBoxRenderer extends JRadioButton implements TableCellRenderer, TableCellEditor {

        private final List<CellEditorListener> listeners = new ArrayList<CellEditorListener>();
        
        public CheckBoxRenderer() {
            addActionListener(new ActionListener(){
                @Override
                public void actionPerformed(ActionEvent e) {
                    NetworkItem currentItem = eventList.get(table.getSelectedRow());
                    if(!currentItem.getAddress().equals(selectedItem.getAddress())) {
                        selectedItem.setSelected(false);
                        selectedItem = currentItem;
                    }
                    currentItem.setSelected(true);
                    cancelCellEditing();
                    table.repaint();
                }
            });
        }
        
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value,
                boolean isSelected, boolean hasFocus, int row, int column) {

            if(value instanceof NetworkItem) {
                NetworkItem item = (NetworkItem) value;
                this.setSelected(item.isSelected);
            } else {
                this.setSelected(false);
            }
            
            if(isSelected) 
                setBackground(table.getSelectionBackground());
            else    
                setBackground(table.getBackground());
            
            return this;
        }

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

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

        @Override
        public Object getCellEditorValue() {
            return null;
        }

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

        @Override
        public void removeCellEditorListener(CellEditorListener lis) {
            synchronized (listeners) {
                if (listeners.contains(lis)) listeners.remove(lis);
            }
        }
        
        @Override
        public void addCellEditorListener(CellEditorListener lis) {
            synchronized (listeners) {
                if (!listeners.contains(lis)) listeners.add(lis);
            }
        }

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

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