package de.tu_darmstadt.tk.SmartHomeNetworkSim.view.popups;
 
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.*;
import javax.swing.event.*;

import de.tu_darmstadt.tk.SmartHomeNetworkSim.control.Controller;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Connection;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.ConnectionPerformance;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Link;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Model;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Port;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.SmartDevice;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.protocols.MQTT_protocol;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.simpleImplementation.SimpleLink;
 
/**
 * Panel for editing of Ports of a SmartDevice
 *
 * @author Andreas T. Meyer-Berg
 */
@SuppressWarnings("serial")
public class PortEditorPanel extends JPanel
                          implements ListSelectionListener, FocusListener, ActionListener, MouseListener {

	/**
	 * Panel, which contains the different Textfields and Selectors for editing a port
	 */
	private JPanel editPanel;
	/**
	 * List for the selection of a port
	 */
    private JList<Port> list;
    /**
     * Split pane for the List
     */
    private JSplitPane splitPane;
    
    /**
     * SmartDevice which is being edited
     */
    private SmartDevice toEdit;
    
    /**
     * Label for the protocol name
     */
    private JLabel lblProtocolName;
    /**
     * Textfield for the PortNumber
     */
    private JTextField tfPortNumber;
    /**
     * ComboBox for the status
     */
    private JComboBox<String> cmbStatus;
    /**
     * Textfield for the trigger Interval
     */
    private JTextField tfTriggerInterval;
    /**
     * Textfield for the responseTime
     */
    private JTextField tfResponseTime;
    /**
     * Textfield for the last trigger
     */
    private JTextField tfLastTrigger;
    /**
     * Textfield for the jitter
     */
    private JTextField tfJitter;
    
    /**
     * Controller for notifying other parts of the Framework
     */
    private Controller controller;
    
    /**
     * Creates a new PortEditorPanel, which allows editing of the Ports of toEdit
     * @param toEdit SmartDevice, which ports will be edited
     * @param controller Controller to modify the Model
     */
    public PortEditorPanel(SmartDevice toEdit, Controller controller) {
    	this.controller = controller;
    	this.toEdit = toEdit;
        //Create the list of ports
        Port[] ports = new Port[toEdit.getPorts().size()];
        for(int i = 0; i<toEdit.getPorts().size();i++)
        	ports[i]=toEdit.getPorts().get(i);
        
        list = new JList<Port>(ports);//Safe conversion
        //list = new JList(ports);//To enable Eclipse.WindowBuilder
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        list.setSelectedIndex(0);
        list.addListSelectionListener(this);
         
        
        JScrollPane listScrollPane = new JScrollPane(list);
        editPanel = new JPanel();
        editPanel.setFont(editPanel.getFont().deriveFont(Font.ITALIC));
        editPanel.setLayout(null);
        
        String portNumberToolTip = "<html>Number of this port, should be a 16bit short [0 - 65335].<br> A Port describes the Endpoint of an Connection.</html>";
        JLabel lblPortnumber = new JLabel("PortNumber:");
        lblPortnumber.setHorizontalAlignment(SwingConstants.RIGHT);
        lblPortnumber.setBounds(10, 10, 140, 20);
        lblPortnumber.setToolTipText(portNumberToolTip);
        editPanel.add(lblPortnumber);
         
        JScrollPane portsScrollPane = new JScrollPane(editPanel);
        
        tfPortNumber = new JTextField();
        tfPortNumber.setBounds(160, 10, 130, 20);
        editPanel.add(tfPortNumber);
        tfPortNumber.setColumns(10);
        tfPortNumber.addFocusListener(this);
        tfPortNumber.addActionListener(this);
        tfPortNumber.addMouseListener(this);
        tfPortNumber.setToolTipText(portNumberToolTip);
        
        JLabel lblProtocol = new JLabel("Protocol:");
        lblProtocol.setHorizontalAlignment(SwingConstants.RIGHT);
        lblProtocol.setBounds(10, 40, 140, 20);
        lblProtocol.setToolTipText("Protocol which this Port participates in.");
        editPanel.add(lblProtocol);
        
        lblProtocolName = new JLabel("Protocol");
        lblProtocolName.setBounds(160, 40, 60, 20);
        lblProtocolName.setToolTipText("Name of the protocol, which is performed via this Port.");
        editPanel.add(lblProtocolName);

        JButton btnEditConnection = new JButton("Edit");
        btnEditConnection.setBounds(230, 40, 60, 20);
        btnEditConnection.setToolTipText("Edit the Connection & Protocol performed via this port.");
        editPanel.add(btnEditConnection);
        btnEditConnection.addActionListener(a->{
        	if(list.getSelectedValue()==null||list.getSelectedValue().getConnection()==null)
        		return;
        	new ConnectionCreationDialog(list.getSelectedValue().getConnection(), controller, this);
        });
        
        String toolTipStatus = "<html>Status of this port, how it is acting:<br>"
        		+" * sending: Port sends packets & responds to incoming packets<br>"
        		+" * open: Port responds to incoming packets, but will not initialize new packets, without trigger<br>"
        		+" * closed: Port won't send or respond to packets<br>"
        		+"</html>";
        JLabel lblStatus = new JLabel("Status:");
        lblStatus.setHorizontalAlignment(SwingConstants.RIGHT);
        //lblStatus.setBounds(66, 66, 56, 16);
        lblStatus.setBounds(10, 70, 140, 20);
        lblStatus.setToolTipText(toolTipStatus);
        editPanel.add(lblStatus);
        
        cmbStatus = new JComboBox<String>();
        cmbStatus.addItem(Port.statusToString(Port.CLOSED));
        cmbStatus.addItem(Port.statusToString(Port.OPEN));
        cmbStatus.addItem(Port.statusToString(Port.SENDING));
        cmbStatus.addItem(Port.statusToString((short) 4));
        cmbStatus.setBounds(160, 70, 130, 20);
        editPanel.add(cmbStatus);
        cmbStatus.addFocusListener(this);
        cmbStatus.addActionListener(this);
        cmbStatus.addMouseListener(this);
        cmbStatus.setToolTipText(toolTipStatus);
        
        String toolTipTrigger = "<html>Interval between to triggers of this port.<br> This port will sent a new outgoing packet every *triggerIntervall* milliseconds.<html>";
        JLabel lblTriggerInterval = new JLabel("Trigger Interval:");
        lblTriggerInterval.setHorizontalAlignment(SwingConstants.RIGHT);
        lblTriggerInterval.setBounds(10, 100, 140, 20);
        lblTriggerInterval.setToolTipText(toolTipTrigger);
        editPanel.add(lblTriggerInterval);
        
        tfTriggerInterval = new JTextField();
        tfTriggerInterval.setBounds(160, 100, 130, 20);
        editPanel.add(tfTriggerInterval);
        //tfTriggerInterval.setColumns(10);
        //tfTriggerInterval.addFocusListener(this);
        //tfTriggerInterval.addActionListener(this);
        //tfTriggerInterval.addMouseListener(this);
        tfTriggerInterval.setToolTipText(toolTipTrigger);
        tfTriggerInterval.setEnabled(false);
        
        JButton btnEditTriggerInterval = new JButton("Edit Distribution");
        btnEditTriggerInterval.setBounds(300, 100, 150, 20);
        btnEditTriggerInterval.addActionListener(l->openEditDistribution());
        editPanel.add(btnEditTriggerInterval);
        
        //JLabel lblTriggerIntervalUnit = new JLabel("ms");
        //lblTriggerIntervalUnit.setBounds(300, 100, 50, 20);
        //editPanel.add(lblTriggerIntervalUnit);
        
        
        String toolTipResponse = "<html>Time in milliseconds which this port needs to calculate<br>"
        		+" and respond to an incoming packet.</html>";
        
        JLabel lblResponseTime = new JLabel("Response Time:");
        lblResponseTime.setHorizontalAlignment(SwingConstants.RIGHT);
        lblResponseTime.setBounds(10, 130, 140, 20);
        lblResponseTime.setToolTipText(toolTipResponse);
        editPanel.add(lblResponseTime);
        
        tfResponseTime = new JTextField();
        tfResponseTime.setBounds(160, 130, 130, 20);
        editPanel.add(tfResponseTime);
        tfResponseTime.setColumns(10);
        tfResponseTime.addFocusListener(this);
        tfResponseTime.addActionListener(this);
        tfResponseTime.addMouseListener(this);
        tfResponseTime.setToolTipText(toolTipResponse);
        
        JLabel lblResponseTimeUnit = new JLabel("ms");
        lblResponseTimeUnit.setBounds(300, 130, 50, 20);
        editPanel.add(lblResponseTimeUnit);
        
        String toolTipLastTrigger = "<html>Timestep in milliseconds, where this port sent its last packet.<br>"
        		+ "Just accounts for packets that originate from this port, no forwards or responses.</html>";
        
        JLabel lblLasttrigger = new JLabel("LastTrigger:");
        lblLasttrigger.setHorizontalAlignment(SwingConstants.RIGHT);
        lblLasttrigger.setBounds(10, 160, 140, 20);
        lblLasttrigger.setToolTipText(toolTipLastTrigger);
        editPanel.add(lblLasttrigger);
        
        tfLastTrigger = new JTextField();
        tfLastTrigger.setBounds(160, 160, 130, 20);
        editPanel.add(tfLastTrigger);
        tfLastTrigger.setColumns(10);
        tfLastTrigger.addFocusListener(this);
        tfLastTrigger.addActionListener(this);
        tfLastTrigger.addMouseListener(this);
        tfLastTrigger.setToolTipText(toolTipLastTrigger);
        
        JLabel lblLastTriggerUnit = new JLabel("ms");
        lblLastTriggerUnit.setBounds(300, 160, 50, 20);
        editPanel.add(lblLastTriggerUnit);
        
        String toolTipJitter = "<html>Jitter describes the differences between the minimum and maximum delay of a connection.<br>"
                + "In this takes the Port may send packets jitter/2 ms earlier or later</html>";
        JLabel lblJitter = new JLabel("Jitter:");
        lblJitter.setHorizontalAlignment(SwingConstants.RIGHT);
        lblJitter.setBounds(10, 190, 140, 20);
        lblJitter.setToolTipText(toolTipJitter);
        editPanel.add(lblJitter);
        
        tfJitter = new JTextField();
        tfJitter.setBounds(160, 190, 130, 20);
        editPanel.add(tfJitter);
        tfJitter.setColumns(10);
        tfJitter.setToolTipText(toolTipJitter);
        
        JLabel lblJitterUnit = new JLabel("ms");
        lblJitterUnit.setBounds(300, 190, 50, 20);
        editPanel.add(lblJitterUnit);
        tfJitter.addFocusListener(this);
        tfJitter.addActionListener(this);
        tfJitter.addMouseListener(this);
        
        splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
                                   listScrollPane, portsScrollPane);
        splitPane.setDividerLocation(90);
 
        /**
         * minimum sizes of the panels
         */
        Dimension minimumSize = new Dimension(150, 60);
        listScrollPane.setMinimumSize(minimumSize);
        listScrollPane.setMaximumSize(minimumSize);
        portsScrollPane.setMinimumSize(minimumSize);
 
        // Set the preferred size
        splitPane.setPreferredSize(new Dimension(500, 240));
        if(!toEdit.getPorts().isEmpty())
        	updateLabel(toEdit.getPorts().get(list.getSelectedIndex()));
    }
     
    private void openEditDistribution() {
    	if(list.getSelectedIndex() < 0 || list.getSelectedIndex() >= toEdit.getPorts().size())return;
		/**
		 * Port, which is being edited at the moment 
		 */
		Port toChange = toEdit.getPorts().get(list.getSelectedIndex());
		
		PortDistributionConfigurationPopUp popUp = new PortDistributionConfigurationPopUp(toChange, controller);
		popUp.setEnabled(true);
		popUp.setVisible(true);
		popUp.setLocationRelativeTo(this);
		popUp.setFocusable(true);
	}

	//Listens to the list
    @SuppressWarnings("unchecked")
	public void valueChanged(ListSelectionEvent e) {
    	if(!(e.getSource() instanceof JList<?>)||!(((JList<?>)e.getSource()).getSelectedValue() instanceof Port))return;
        JList<Port> list = (JList<Port>)e.getSource();
        updateLabel(toEdit.getPorts().get(list.getSelectedIndex()));
    }
     
    /**
     * Updates the labels to the given Port
     * @param port Port which should be showed in this panel
     */
    protected void updateLabel (Port port) {
    	if(port == null)return;
    	tfPortNumber.setText(""+port.getPortNumber());
    	tfPortNumber.setBackground(Color.WHITE);
    	tfLastTrigger.setText(""+port.getLastTrigger());
    	tfLastTrigger.setBackground(Color.WHITE);
    	tfResponseTime.setText(""+port.getResponseTime());
    	tfResponseTime.setBackground(Color.WHITE);
    	tfTriggerInterval.setText(""+port.getTriggerInterval()+" ms");
    	tfTriggerInterval.setBackground(Color.WHITE);
    	tfJitter.setText(""+port.getJitter());
    	tfJitter.setBackground(Color.WHITE);
    	cmbStatus.setSelectedIndex(port.getStatus());
    	if(port.getConnection() == null || port.getConnection().getProtocol()==null)
    		lblProtocolName.setText("null");
    	else
    		lblProtocolName.setText(port.getConnection().getProtocol().getName());
    }
    
    /**
     * Returns the SplitPane, which is the Main component of this Panel
     * @return panel, which represents this object
     */
    public JSplitPane getSplitPane() {
        return splitPane;
    }
 
    /**
     * Test the Panel
     */
    private static void testGUI() {
        JFrame frame = new JFrame("Port Editor");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        SmartDevice test = new SmartDevice("TestDevice");
        Port p1 = new Port(test, (short) 1);
        Port p2 = new Port(test, (short) 2);
        test.addPort(p1);
        test.addPort(p2);
        MQTT_protocol testProtocol = new MQTT_protocol();
        testProtocol.addDeviceOfRole(p1, 0);
        testProtocol.addDeviceOfRole(p2, 1);
        Link test_link = new SimpleLink("Test");
        test_link.addDevice(test);
        Connection con = new ConnectionPerformance(test_link, testProtocol);
        p1.setConnection(con);
        p2.setConnection(con);
        con.addSmartDevice(p1);
        con.addSmartDevice(p2);
        PortEditorPanel portEditorPanel = new PortEditorPanel(test, new Controller(new Model()));
        frame.getContentPane().add(portEditorPanel.getSplitPane());
 
        frame.pack();
        frame.revalidate();
        frame.setVisible(true);
    }
 
    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                testGUI();
            }
        });
    }

    /**
     * Checks values of the textfields have changed, and updates the Protocol accordingly.
     * Invalid values are highlighted RED
     */
	private void checkValueChange() {
		if(list.getSelectedIndex() < 0 || list.getSelectedIndex() >= toEdit.getPorts().size())return;
		/**
		 * Port, which is being edited at the moment 
		 */
		Port toChange = toEdit.getPorts().get(list.getSelectedIndex());
		
		//Edit Port Number
		if(tfPortNumber.getText()!=""+toChange.getPortNumber()){
			try {
				toChange.setPortNumber(Short.parseShort(tfPortNumber.getText()));
				tfPortNumber.setBackground(Color.WHITE);
			} catch (Exception e) {
				tfPortNumber.setBackground(Color.RED);
			}
		}else{
			tfPortNumber.setBackground(Color.WHITE);
		}
		
		//Edit Status
		if(cmbStatus.getSelectedIndex()!=toChange.getStatus())
			toChange.setStatus((short) cmbStatus.getSelectedIndex());
		
		//Edit trigger Interval
		/**
		if(tfTriggerInterval.getText()!=""+toChange.getTriggerInterval()){
			try {
				//TODO: Advanced Triggers
				ProbabilityDistributionHandler dist = toChange.getTriggerHandler();
				if(dist instanceof ConstantValueDistribution){
					((ConstantValueDistribution)dist).setValue(Long.parseLong(tfTriggerInterval.getText()));
					toChange.resampleTriggerInterval();
				}
					//toChange.setTriggerInterval(Long.parseLong(tfTriggerInterval.getText()));
				tfTriggerInterval.setBackground(Color.WHITE);
			} catch (Exception e) {
				tfTriggerInterval.setBackground(Color.RED);
			}
		}else{
			tfTriggerInterval.setBackground(Color.WHITE);
		}
		*/
		
		//Edit Response time
		if(tfResponseTime.getText()!=""+toChange.getResponseTime()){
			try {
				toChange.setResponseTime(Short.parseShort(tfResponseTime.getText()));
				tfResponseTime.setBackground(Color.WHITE);
			} catch (Exception e) {
				tfResponseTime.setBackground(Color.RED);
			}
		}else{
			tfResponseTime.setBackground(Color.WHITE);
		}
		
		//Edit last Trigger
		if(tfLastTrigger.getText()!=""+toChange.getLastTrigger()){
			try {
				toChange.setLastTrigger(Long.parseLong(tfLastTrigger.getText()));
				tfLastTrigger.setBackground(Color.WHITE);
			} catch (Exception e) {
				tfLastTrigger.setBackground(Color.RED);
			}
		}else{
			tfLastTrigger.setBackground(Color.WHITE);
		}
		
		//Edit Jitter
		if(tfJitter.getText()!=""+toChange.getJitter()){
			try {
				toChange.setJitter(Short.parseShort(tfJitter.getText()));
				tfJitter.setBackground(Color.WHITE);
			} catch (Exception e) {
				tfJitter.setBackground(Color.RED);
			}
		}else{
			tfJitter.setBackground(Color.WHITE);
		}
		
		
	}

	@Override
	public void focusGained(FocusEvent e) {
		checkValueChange();
	}

	@Override
	public void focusLost(FocusEvent e) {
		checkValueChange();
	}

	@Override
	public void mouseClicked(MouseEvent e) {	
	}

	@Override
	public void mousePressed(MouseEvent e) {
	}

	@Override
	public void mouseReleased(MouseEvent e) {
	}

	@Override
	public void mouseEntered(MouseEvent e) {
	}

	@Override
	public void mouseExited(MouseEvent e) {
		checkValueChange();
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		checkValueChange();
	}
}