package com.limegroup.gnutella.gui.connection;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;

import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTextField;

import com.limegroup.gnutella.ManagedConnection;
import com.limegroup.gnutella.RouterService;
import com.limegroup.gnutella.gui.AutoCompleteTextField;
import com.limegroup.gnutella.gui.ClearableAutoCompleteTextField;
import com.limegroup.gnutella.gui.GUIMediator;
import com.limegroup.gnutella.gui.GUIUtils;
import com.limegroup.gnutella.gui.PaddedPanel;
import com.limegroup.gnutella.gui.WholeNumberField;
import com.limegroup.gnutella.gui.tables.AbstractTableMediator;
import com.limegroup.gnutella.gui.tables.LimeJTable;
import com.limegroup.gnutella.gui.tables.TableSettings;
import com.limegroup.gnutella.gui.themes.ThemeMediator;
import com.limegroup.gnutella.util.NetworkUtils;

/**
 * This class acts as a mediator between all of the components of the
 * connection window.
 */
public final class ConnectionMediator extends AbstractTableMediator {

    public static ConnectionMediator instance() { return INSTANCE; }

    /**
     * Listeners so buttons and possibly future right-click menu share.
     */
    ActionListener ADD_LISTENER;

    /**
     * Listeners so buttons and possibly future right-click menu share.
     */
    ActionListener BROWSE_HOST_LISTENER;

    private static final String IS_ULTRAPEER =
        GUIMediator.getStringResource("CV_STRING_ULTRAPEER");

    private static final String IS_LEAF =
        GUIMediator.getStringResource("CV_STRING_CLIENT");
        
    private static final String CONNECTING = 
        GUIMediator.getStringResource("CV_TABLE_STRING_CONNECTINGS");
        
	private static final String LEAVES =
        GUIMediator.getStringResource("CV_TABLE_STRING_LEAVES");
        
    private static final String ULTRAPEERS =
        GUIMediator.getStringResource("CV_TABLE_STRING_ULTRAPEERS");
        
    private static final String PEERS =
        GUIMediator.getStringResource("CV_TABLE_STRING_PEERS");

    private static final String STANDARD =
        GUIMediator.getStringResource("CV_TABLE_STRING_STANDARDS");

    /**
     * Instance of singleton access
     */
    private static final ConnectionMediator INSTANCE =
        new ConnectionMediator();

    /**
     * Extra component constants
     */
    private JLabel SERVENT_STATUS;
    
    /**
     * The label displaying the number of ultrapeers, peers & leaves.
     */
    private JLabel NEIGHBORS;

    /**
     * Build the listeners
     */
    protected void buildListeners() {
        super.buildListeners();
        ADD_LISTENER = new AddListener();
        BROWSE_HOST_LISTENER = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                doBrowseHost();
            }
	    };
    }
    
    /**
     * Overriden to have different default values for tooltips.
     */
    protected void buildSettings() {
        SETTINGS = new TableSettings(ID) {
            public boolean getDefaultTooltips() {
                return false;
            }
        };
    }

    /**
     * Add the listeners
     */
    protected void addListeners() {
        super.addListeners();
    }

	/**
	 * Set up the necessary constants.
	 */
	protected void setupConstants() {
	    //  Create padded panel without bottom padding so that button
	    //  rows for all the tabs line up.
		MAIN_PANEL = new PaddedPanel();
		DATA_MODEL = new ConnectionModel();
		TABLE = new LimeJTable(DATA_MODEL);
		BUTTON_ROW = (new ConnectionButtons(this)).getComponent();

		SERVENT_STATUS = new JLabel("");
		NEIGHBORS = new JLabel("");
    }

    /**
     * Overridden to set the size.
     */
    protected JComponent getScrolledTablePane() {
		JComponent pane = super.getScrolledTablePane();

        SCROLL_PANE.setPreferredSize(new Dimension(3000, 5000));

        return pane;
    }

    /**
     * Update the splash screen
     */
	protected void updateSplashScreen() {
		GUIMediator.setSplashScreenString(
            GUIMediator.getStringResource("SPLASH_STATUS_CONNECTION_WINDOW"));
    }

    /**
     * Override the default main panel setup
     */
    protected void setupMainPanel() {
        if (MAIN_PANEL == null)
            return;

        super.setupMainPanel();

	    JPanel status = new JPanel();
	    status.setLayout(new BorderLayout());
	    status.add(SERVENT_STATUS, BorderLayout.WEST);
	    status.add(NEIGHBORS, BorderLayout.EAST);

        MAIN_PANEL.add(status, 0);
    }

	/**
	 * Constructor -- private for Singleton access
	 */
	private ConnectionMediator() {
	    super("CONNECTION_TABLE");
	    GUIMediator.addRefreshListener(this);
	    ThemeMediator.addThemeObserver(this);
	    doRefresh();
	}

    /**
     * Removes all selected rows from Router,
     * which will in turn remove it from the list.
     * Overrides default removeSelection
     */
    public void removeSelection() {
		int[] sel = TABLE.getSelectedRows();
		Arrays.sort(sel);
		ManagedConnection c;
		for( int counter = sel.length - 1; counter >= 0; counter--) {
			int i = sel[counter];
			c = (ManagedConnection)DATA_MODEL.get(i).getInitializeObject();
			RouterService.removeConnection(c);
		}
		clearSelection();
    }

    /** 
     * Returns the JPopupMenu for the connection table
     */
    protected JPopupMenu createPopupMenu() {
        JPopupMenu jpm = new JPopupMenu();

        //  add
        JMenuItem jmi = new JMenuItem(GUIMediator
                .getStringResource("CV_BUTTON_ADD"));
        jmi.addActionListener(ADD_LISTENER);
        jpm.add(jmi);
        jpm.addSeparator();

        //  remove
        jmi = new JMenuItem(GUIMediator.getStringResource("CV_BUTTON_REMOVE"));
        jmi.addActionListener(REMOVE_LISTENER);
        jpm.add(jmi);
        jpm.addSeparator();

        //  browse host
        jmi = new JMenuItem(GUIMediator
                .getStringResource("GENERAL_BROWSE_HOST_LABEL"));
        jmi.addActionListener(BROWSE_HOST_LISTENER);
        jpm.add(jmi);

        return jpm;
    }

	/**
	 * Handles the selection of the specified row in the connection window,
	 * enabling or disabling buttons
	 *
	 * @param row the selected row
	 */
	public void handleSelection(int row) {
	    setButtonEnabled( ConnectionButtons.REMOVE_BUTTON, true );
	    setButtonEnabled( ConnectionButtons.BROWSE_HOST_BUTTON, true );
	}

	/**
	 * Handles the deselection of all rows in the download table,
	 * disabling all necessary buttons and menu items.
	 */
	public void handleNoSelection() {
	    setButtonEnabled( ConnectionButtons.REMOVE_BUTTON, false );
	    setButtonEnabled( ConnectionButtons.BROWSE_HOST_BUTTON, false );
	}

    public void handleActionKey() {
        doBrowseHost(); 
    }

    /**
     * get the first selected row and trigger a browse host
     */
    private void doBrowseHost() {
        int[] rows = TABLE.getSelectedRows();
        if(rows.length > 0) {
            ManagedConnection c = 
                (ManagedConnection)DATA_MODEL.get(rows[0]).getInitializeObject();
            GUIMediator.instance().doBrowseHost(c.getAddress(), c.getPort());
        }
    }

	/**
	 * Override the default doRefresh so we can update the servent status label
	 * (Uses doRefresh instead of refresh so this will only get called
	 *  when the table is showing.  Small optimization.)
	 */
	public void doRefresh() {
	    super.doRefresh();
	    SERVENT_STATUS.setText(GUIMediator.getStringResource("CV_STRING_STATUS") + "  " +
	        ( RouterService.isSupernode() ?
                IS_ULTRAPEER : IS_LEAF ) + "      "
        );
        int[] counts = ((ConnectionModel)DATA_MODEL).getConnectionInfo();
        NEIGHBORS.setText("(" +
            counts[1] + " " + ULTRAPEERS + ", " +
            counts[2] + " " + PEERS + ", " + 
            counts[3] + " " + LEAVES + ", " +
            counts[0] + " " + CONNECTING + ", " +
            counts[4] + " " + STANDARD + ")");
    }
    
    /**
     * Determines the number of connections that are in connecting state.
     */
    public int getConnectingCount() {
        return ((ConnectionModel)DATA_MODEL).getConnectingCount();
    }

    private void tryConnection(final String hostname, final int portnum) {
        GUIMediator.instance().schedule(new Runnable() {
            public void run() {
                RouterService.connectToHostAsynchronously(hostname, portnum);
            }
        });
    }

    /**
     *  Clear the connections visually
     */
    public void clearConnections() {
		DATA_MODEL.clear();
    }

    /**
     * Adds the host & port to the dictionary of the HOST_INPUT
     */
    void addKnownHost( String host, int port ) {
	    //HOST_INPUT.addToDictionary( host + ":" + port );
	}

    /**
     * First attempts to parse out the ':' from the host.
     * If one exists, it replaces the text in PORT_INPUT.
     * Otherwise, it uses the text in port input.
     */
    private final class AddListener implements ActionListener {
    
    	private JDialog dialog = null;
    
    	private AutoCompleteTextField HOST_INPUT = new ClearableAutoCompleteTextField(20);
    
    	private JTextField PORT_INPUT = new WholeNumberField(6346, 4);
    
    	private JButton OK_BUTTON = new JButton(GUIMediator.getStringResource("GENERAL_OK_BUTTON_LABEL"));
    
    	private JButton CANCEL_BUTTON = new JButton(GUIMediator.getStringResource("GENERAL_CANCEL_BUTTON_LABEL"));
    
    	private void createDialog() {
    	    if(dialog != null) return;
    	    //  1.  create modal dialog
    	    //      Host: [            ]
    	    //      Port: [            ]
    	    //         [ OK ] [ Cancel ]
    	    dialog = new JDialog(GUIMediator.getAppFrame(),
    				 GUIMediator.getStringResource("CV_ADD_DIALOG_TITLE"),
    				 true);
    	    JPanel jp = (JPanel)dialog.getContentPane();
    	    GUIUtils.addHideAction(jp);
    	    jp.setLayout(new GridBagLayout());
    	    GridBagConstraints gbc = new GridBagConstraints();
    
    	    //  space between title bar and Host line
    	    gbc.gridwidth = 5;
    	    gbc.gridheight = 1;
    	    gbc.weightx = 1;
    	    gbc.weighty = 0;
    	    gbc.fill = GridBagConstraints.HORIZONTAL;
    	    gbc.gridx = 0;
    	    gbc.gridy = 0;
    	    jp.add(getHorizontalSpacer(), gbc);
    
    	    //  lefthand side space
    	    gbc.gridwidth = 1;
    	    gbc.gridheight = 5;
    	    gbc.weightx = 0;
    	    gbc.weighty = 1;
    	    gbc.fill = GridBagConstraints.VERTICAL;
    	    gbc.gridx = 0;
    	    gbc.gridy = 1;
    	    jp.add(getVerticalSpacer(), gbc);
    
    	    //  righthand side space
    	    gbc.gridwidth = 1;
    	    gbc.gridheight = 5;
    	    gbc.weightx = 0;
    	    gbc.weighty = 1;
    	    gbc.fill = GridBagConstraints.VERTICAL;
    	    gbc.gridx = 4;
    	    gbc.gridy = 1;
    	    jp.add(getVerticalSpacer(), gbc);
    	    
    	    //  bottom spacer between buttons and window border
    	    gbc.gridwidth = 5;
    	    gbc.gridheight = 1;
    	    gbc.weightx = 1;
    	    gbc.weighty = 0;
    	    gbc.fill = GridBagConstraints.HORIZONTAL;
    	    gbc.gridx = 0;
    	    gbc.gridy = 6;
    	    jp.add(getHorizontalSpacer(), gbc);
    
    	    //  host label
    	    gbc.gridwidth = 1;
    	    gbc.gridheight = 1;
    	    gbc.weightx = 0;
    	    gbc.weighty = 0;
    	    gbc.fill = GridBagConstraints.NONE;
    	    gbc.gridx = 1;
    	    gbc.gridy = 1;
    	    jp.add(new JLabel(GUIMediator.getStringResource("CV_ADD_HOST_LABEL")), gbc);
    
    	    //  host label <-> host input field spacer
    	    gbc.gridwidth = 1;
    	    gbc.gridheight = 1;
    	    gbc.weightx = 0;
    	    gbc.weighty = 0;
    	    gbc.fill = GridBagConstraints.NONE;
    	    gbc.gridx = 2;
    	    gbc.gridy = 1;
    	    jp.add(getVerticalSpacer(), gbc);
    	    
    	    //  host input field
    	    gbc.gridwidth = 1;
    	    gbc.gridheight = 1;
    	    gbc.weightx = 1;
    	    gbc.weighty = 0;
    	    gbc.fill = GridBagConstraints.HORIZONTAL;
    	    gbc.gridx = 3;
    	    gbc.gridy = 1;
    	    jp.add(HOST_INPUT, gbc);
    
    	    //  host <-> port spacer
    	    gbc.gridwidth = 3;
    	    gbc.gridheight = 1;
    	    gbc.weightx = 1;
    	    gbc.weighty = 0;
    	    gbc.fill = GridBagConstraints.HORIZONTAL;
    	    gbc.gridx = 1;
    	    gbc.gridy = 2;
    	    jp.add(getHorizontalSpacer(), gbc);
    
    	    //  port label
    	    gbc.gridwidth = 1;
    	    gbc.gridheight = 1;
    	    gbc.weightx = 0;
    	    gbc.weighty = 0;
    	    gbc.fill = GridBagConstraints.NONE;
    	    gbc.gridx = 1;
    	    gbc.gridy = 3;
    	    jp.add(new JLabel(GUIMediator.getStringResource("CV_ADD_PORT_LABEL")), gbc);
    
    	    //  port label <-> port input field spacer
    	    gbc.gridwidth = 1;
    	    gbc.gridheight = 1;
    	    gbc.weightx = 0;
    	    gbc.weighty = 0;
    	    gbc.fill = GridBagConstraints.NONE;
    	    gbc.gridx = 2;
    	    gbc.gridy = 3;
    	    jp.add(getVerticalSpacer(), gbc);
    	    
    	    //  port input field
    	    gbc.gridwidth = 1;
    	    gbc.gridheight = 1;
    	    gbc.weightx = 1;
    	    gbc.weighty = 0;
    	    gbc.fill = GridBagConstraints.HORIZONTAL;
    	    gbc.gridx = 3;
    	    gbc.gridy = 3;
    	    jp.add(PORT_INPUT, gbc);
    
    	    //  port <-> buttons spacer
    	    gbc.gridwidth = 3;
    	    gbc.gridheight = 1;
    	    gbc.weightx = 1;
    	    gbc.weighty = 0;
    	    gbc.fill = GridBagConstraints.HORIZONTAL;
    	    gbc.gridx = 1;
    	    gbc.gridy = 4;
    	    jp.add(getHorizontalSpacer(), gbc);
    
    	    //  buttons
    	    JPanel buttons = new JPanel();
    	    OK_BUTTON.addActionListener(new ActionListener() {
    		    public void actionPerformed(ActionEvent ae) {
    			String hostnamestr = HOST_INPUT.getText();
    			String portstr = PORT_INPUT.getText();
    
    			// look for the port in the host
    			int idx = hostnamestr.lastIndexOf(':');
    			// if it exists, rewrite the host & port
    			if ( idx != -1 ) {
    			    PORT_INPUT.setText( hostnamestr.substring(idx+1) );
    			    portstr = PORT_INPUT.getText();
    			    HOST_INPUT.setText( hostnamestr.substring(0, idx) );
    			    hostnamestr = HOST_INPUT.getText();
    			}
    
    			int portnum = -1;
    			try {
    			    portnum = Integer.parseInt(portstr);
    			} catch (NumberFormatException ee) {
    			    portnum = 6346;
    			}
    			if(!NetworkUtils.isValidPort(portnum))
    			    portnum = 6346;
                
    			PORT_INPUT.setText("" + portnum);
    
    			if ( !hostnamestr.equals("") ) {
    			    tryConnection(hostnamestr, portnum);
    			    dialog.setVisible(false);
    			    dialog.dispose();
    			} else {
    			    HOST_INPUT.requestFocus();
    			}
    		    }
    		});
    	    CANCEL_BUTTON.addActionListener(GUIUtils.getDisposeAction());
    	    buttons.add(OK_BUTTON);
    	    buttons.add(CANCEL_BUTTON);
    	    gbc.gridwidth = 3;
    	    gbc.gridheight = 1;
    	    gbc.weightx = 1;
    	    gbc.weighty = 0;
    	    gbc.fill = GridBagConstraints.NONE;
    	    gbc.anchor = GridBagConstraints.EAST;
    	    gbc.gridx = 1;
    	    gbc.gridy = 5;
    	    jp.add(buttons, gbc);
       	}

        public void actionPerformed(ActionEvent e) {
    	    if(dialog == null) createDialog();
    
    	    //  2.  display dialog centered (and modal)
    	    dialog.getRootPane().setDefaultButton(OK_BUTTON);
    	    dialog.pack();
    	    dialog.setLocation(GUIMediator.getScreenCenterPoint(dialog));
    	    dialog.setVisible(true);
    	}

    	/** Returns a vertical separator */
    	private Component getVerticalSpacer() {
    	    return Box.createRigidArea(new Dimension(6,0));
    	}
    
    	/** Returns a horizontal separator */
    	private Component getHorizontalSpacer() {
    	    return Box.createRigidArea(new Dimension(0,6));
    	}
    }
}
