package de.tu_darmstadt.tk.SmartHomeNetworkSim.view;

import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Line2D;
import java.util.LinkedList;

import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputListener;

import de.tu_darmstadt.tk.SmartHomeNetworkSim.control.SettingsController;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.control.Controller;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.control.NetworkController;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.control.PacketCaptureController;
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.Packet;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.PacketCollector;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Port;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.SmartDevice;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.configuration.SelectionModel;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.simpleImplementation.SimpleLink;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.simpleImplementation.SimpleProtocol;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.util.Pair;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.view.popups.ConnectionCreationDialog;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.view.popups.EditPacketSniffer;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.view.popups.LinkCreationDialog;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.view.popups.SmartDeviceCreationPopUp;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.view.util.LinkToolTip;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.view.util.Utility;

/**
 * Listener which detects User Interaction with the {@link VisualizationPanel},
 * stores interaction information, triggers the controller and helps
 * visualization of the interaction
 * 
 * @author Andreas T. Meyer-Berg
 */
public class VisualizationInteractor implements MouseInputListener,
		MouseMotionListener, ActionListener, KeyListener {
	/**
	 * Controller to notify when the model changes
	 */
	private Controller controller;
	
	/**
	 * Configurations
	 */
	private SettingsController config;
	
	/**
	 * Network Controller
	 */
	private NetworkController network;
	
	/**
	 * Panel which is observed
	 */
	private VisualizationPanel panel;

	/**
	 * Menu which is shown on right clicks
	 */
	private JPopupMenu rightClickMenu;

	/**
	 * RightClick MenuItem for SmartDevice creation
	 */
	private JMenuItem itemCreate;

	/**
	 * RightClick MenuItem for Link creation
	 */
	private JMenuItem itemCreateLink;
	
	/**
	 * RightClick MenuItem for Connection creation
	 */
	private JMenuItem itemCreateConnection;
	/**
	 * RightClickMenu for editing connections
	 */
	private JMenu itemEditConnection;
	/**
	 * RightClickMenu for options for capturing of packets
	 */
	private JMenu itemCapture;
	/**
	 * Position of editCreate connection in the Rightclick menu 
	 */
	private int editCreateConnectionIndex = 2;

	/**
	 * RightClick MenuItem for SmartDevice deletion
	 */
	private JMenuItem itemDelete;
	
	/**
	 * RightClick MenuItem for deletion of multiple selected SmartDevices
	 */
	private JMenuItem itemDeleteSelected;

	/**
	 * RightClick MenuItem for debug purposes
	 */
	private JMenuItem itemDebug;

	/**
	 * SmartDevice that is dragged on Screen
	 */
	public SmartDevice dragged = null;

	/**
	 * Current x Position of the dragged device
	 */
	public int dragged_x;

	/**
	 * Current y Position of the dragged device
	 */
	public int dragged_y;
	
	/**
	 * x Position where the drage begun
	 */
	public int dragged_x_start;
	
	/**
	 *  y Position where the drag begun
	 */
	public int dragged_y_start;
	
	/**
	 * true if an selected device was dragged
	 */
	private boolean draggedDeviceWasSelected = false;
	
	/**
	 * No interaction
	 */
	public static final byte NOTHING = 0;
	
	/**
	 * Right-click menu is shown
	 */
	public static final byte RIGHTCLICK_MENU = 1; 
	
	/**
	 * Drag of multiple selected devices
	 */
	public static final byte SELECTED_DRAG = 2;
	
	/**
	 * drag to create connection
	 */
	public static final byte DRAG_CONNECTION = 3;
	
	/**
	 * Drag to select multiple devices
	 */
	public static final byte DRAG_SELECT = 4; 
	
	/**
	 * Devices are selected on the screen
	 */
	public static final byte SELECTED = 5;
	
	/**
	 * Mode, which action is currently executed
	 */
	public byte mode = NOTHING;
	
	/**
	 * SmartDevice that was clicked
	 */
	private SmartDevice clicked = null;
	
	
	/**
	 * Device where the current connection is dragged from
	 */
	public SmartDevice connectionFrom = null;

	/**
	 * True if the ControlKey is currently pressed
	 */
	public boolean controlDown = false;

	/**
	 * ToolTip which describes the Links
	 */
	private LinkToolTip toolTip;
	
	/**
	 * Link which was clicked
	 */
	private Link clickedLink = null;

	/**
	 * PacketCapture Controller, for capturing packets
	 */
	private PacketCaptureController captureController;
	SelectionModel selectionModel;
	/**
	 * Creates a new VisualisationInteractor
	 *
	 * @param controller
	 *            controller that is accessed
	 * @param panel
	 *            which should visualize the interactions
	 */
	public VisualizationInteractor(Controller controller,
			VisualizationPanel panel) {
		// Initialize the values
		this.controller = controller;
		this.panel = panel;
		this.config = controller.getSettingsController();
		this.network = controller.getNetworkController();
		this.toolTip = new LinkToolTip();
		this.captureController = controller.getSimulationController().getPacketCaptureController();
		this.selectionModel = controller.getSettingsController().getConfigurationManager().getSelectionModel();
		initializeRightClickMenu();
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		toolTip.setEnabled(false);
		/*
		 * FindSmartDevice that was clicked
		 */
		clicked = getSmartDeviceAtPosition(e.getX(), e.getY());
		
		selectionModel.clickedConnection.clear();
		if(clicked == null)
			selectionModel.clickedConnection = getConnectionsAtPosition(e.getX(), e.getY());
		
		
		switch (mode) {
		case DRAG_CONNECTION:
			finishConnectionCreation();			
			break;
		case SELECTED_DRAG:
			// Finish Drag Device operations
			finishDrag();
		case SELECTED:			
		case NOTHING:
			// If Rightclick
			if (SwingUtilities.isRightMouseButton(e)) {
				// Show the RightClickMenu
				Link linkAtPosition = getLinkVisualizationAtPosition(e.getX(), e.getY());
				showRightClickMenu(clicked, linkAtPosition);
				break;
			}
			if(!selectionModel.selectedDevices.isEmpty()&&!controlDown){
				selectionModel.selectedDevices.clear();
				if (clicked == null) {
					panel.repaint();					
				}
			}
			if(clicked!=null){
				if(!controlDown)
					selectionModel.selectedDevices.add(clicked);
				else if(selectionModel.selectedDevices.remove(clicked)&&!draggedDeviceWasSelected){
					selectionModel.selectedDevices.add(clicked);
				}
				mode = SELECTED;
				panel.repaint();
			}else{
				if(!controlDown)
					mode = NOTHING; 
				else if(!selectionModel.selectedDevices.isEmpty())
					mode = SELECTED;

				panel.repaint();	
			}
			break;
		default:
			break;
		}
	}

	@Override
	public void mousePressed(MouseEvent e) {
		toolTip.setEnabled(false);
		// Save mouse position for right Click options
		dragged_x = e.getX();
		dragged_y = e.getY();
		// Check if SmartDevice was clicked
		SmartDevice pressedOn = getSmartDeviceAtPosition(dragged_x, dragged_y);
		switch (mode) {
		case SELECTED:
			if(!controlDown)
				mode = NOTHING;
		case RIGHTCLICK_MENU:
		case NOTHING:
			if (pressedOn == null){
				//Click drag - multi select
				dragged_x_start = e.getX();
				dragged_y_start = e.getY();
				mode = DRAG_SELECT;
				if(!controlDown && !SwingUtilities.isRightMouseButton(e))
					selectionModel.selectedDevices.clear();
				break;
			} else if (e.getButton() == MouseEvent.BUTTON1){// && connectionFrom == null) {
				// Recognize possible drag operation
				draggedDeviceWasSelected = selectionModel.selectedDevices.contains(pressedOn);
				if(!draggedDeviceWasSelected){
					if(!controlDown)
						selectionModel.selectedDevices.clear();
					selectionModel.selectedDevices.add(pressedOn);
				}
				dragged = pressedOn;
				dragged_x = pressedOn.getX();
				dragged_y = pressedOn.getY();
				mode = SELECTED_DRAG;
			} else if (SwingUtilities.isRightMouseButton(e)){// && dragged == null) {
				connectionFrom = pressedOn;
				mode = DRAG_CONNECTION;
			}
			break;
		case DRAG_SELECT:
			finishDragSelect();
			break;
		default:
			mode = NOTHING;
			break;
		}
		panel.repaint();
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		// Finish operation
		switch (mode) {
		case DRAG_SELECT:
			finishDragSelect();
			panel.repaint();
			break;
		case SELECTED_DRAG:
			finishDrag();
			
			break;
		case DRAG_CONNECTION:
			finishConnectionCreation();
			break;
		default:
			break;
		}
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		toolTip.setEnabled(false);
		// move dragged object/object selection on screen
		switch (mode) {
		case DRAG_SELECT:
		case DRAG_CONNECTION:
			//Update position
			if (e.getX() < 0)
				dragged_x = 0;
			else if (e.getX() >= config.getWidth())
				dragged_x = config.getWidth()-1;
			else
				dragged_x = e.getX();
			if (e.getY() < 0)
				dragged_y = 0;
			else if (e.getY() >= config.getHeight())
				dragged_y = config.getHeight()-1;
			else
				dragged_y = e.getY();
			if(mode == DRAG_CONNECTION)
				break;
			//Update Selected objects, if DragSelect mode is active
			int min_x = Math.min(dragged_x, dragged_x_start);
			int min_y = Math.min(dragged_y, dragged_y_start);
			int max_x = Math.max(dragged_x, dragged_x_start);
			int max_y = Math.max(dragged_y, dragged_y_start);
			//Remove unselected
			selectionModel.selectedDevicesDrag.removeIf(s->(s.getX()<min_x||s.getX()>max_x||s.getY()<min_y||s.getY()>max_y));
			//Add selected devices
			for(SmartDevice sel:network.getVisibleSmartDevices()){
				if(sel.getX()>=min_x&&sel.getX()<=max_x&&sel.getY()>=min_y&&sel.getY()<=max_y&&!selectionModel.selectedDevicesDrag.contains(sel)){
					selectionModel.selectedDevicesDrag.add(sel);
				}
			}
			break;
		case SELECTED_DRAG:
			//new Position of the dragged Device
			dragged_x = e.getX();
			dragged_y = e.getY();
			//offset of the new Position - old position
			int x_offset = dragged_x - dragged.getX();
			int y_offset = dragged_y - dragged.getY();
			//Validate for all moved devices, that they are within the model, if not change offset
			for(SmartDevice d:selectionModel.selectedDevices){
				if (d.getX() + x_offset <= config.getDeviceVisualizationRadius())
					x_offset = config.getDeviceVisualizationRadius()-d.getX();
				else if (d.getX() + x_offset >= config.getWidth() - config.getDeviceVisualizationRadius())
					x_offset = config.getWidth() - config.getDeviceVisualizationRadius()-d.getX();
				if (d.getY() + y_offset <= config.getDeviceVisualizationRadius())
					y_offset = config.getDeviceVisualizationRadius()-d.getY();
				else if (d.getY() + y_offset >= config.getHeight() - config.getDeviceVisualizationRadius())
					y_offset = config.getHeight() - config.getDeviceVisualizationRadius()-d.getY();
			}
			//update the dragged position, if the offset changed
			dragged_x =dragged.getX() + x_offset;
			dragged_y =dragged.getY() + y_offset;
			break;

		default:
			break;
		}
		
		// repaint the dragged smartDevice or new connection
		if (mode == SELECTED_DRAG || mode == DRAG_CONNECTION || mode == DRAG_SELECT)
			panel.repaint();
	}
	
	/**
	 * Finishes the drag operation, if a SmartDevice was dragged, repaint the
	 * panel and set dragged to null
	 */
	public void finishDrag() {
		if (mode == SELECTED_DRAG){
			if(dragged != null
				&& (dragged.getX() != dragged_x || dragged.getY() != dragged_y)) {
				int x_offset = dragged_x-dragged.getX();
				int y_offset = dragged_y-dragged.getY();
				for(SmartDevice d: selectionModel.selectedDevices)
					network.moveSmartDevice(d, d.getX()+x_offset, d.getY()+y_offset, d.getZ());
			}
			dragged = null;
			if(selectionModel.selectedDevices.isEmpty())
				mode = NOTHING;
			else
				mode = SELECTED;
			panel.repaint();
			controller.notifyObservers();
		}
	}

	private void finishDragSelect() {
		LinkedList<SmartDevice> merged = new LinkedList<SmartDevice>();
		//XOR of controller.getControllerConfiguration().getConfigurationManager().getSelectionModel().selectedDevices and controller.getControllerConfiguration().getConfigurationManager().getSelectionModel().selectedDevices Drag
		merged.addAll(selectionModel.selectedDevices);
		merged.removeAll(selectionModel.selectedDevicesDrag);
		selectionModel.selectedDevicesDrag.removeAll(selectionModel.selectedDevices);
		merged.addAll(selectionModel.selectedDevicesDrag);
		//clear sets
		selectionModel.selectedDevices.clear();
		selectionModel.selectedDevicesDrag.clear();
		selectionModel.selectedDevices = merged;
		if(selectionModel.selectedDevices.isEmpty())
			mode = NOTHING;
		else
			mode = SELECTED;
	}
	
	/**
	 * Finishes the create connection operation and open a PopUp for
	 * Link/Connection creation
	 */
	private void finishConnectionCreation() {
		/**
		 * SmartDevice the connection was
		 */
		SmartDevice connectionTo = getSmartDeviceAtPosition(dragged_x,
				dragged_y);
		if (mode == DRAG_CONNECTION && connectionFrom != null && connectionTo != null
				&& connectionFrom != connectionTo) {
			// Create new Connection
			/**
			 * Link of the new Connection
			 */
			Link l = null;
			/**
			 * Devices of the connection -> to find common Link
			 */
			LinkedList<SmartDevice> devices = new LinkedList<SmartDevice>();
			devices.add(connectionFrom);
			devices.add(connectionTo);
			for(Link link:network.getLinks()){
				if(link.getDevices().containsAll(devices)){
					l = link;
					break;
				}
			}
			/**
			 * Create new Connection if no link was found
			 */
			if(l==null){
				l= new SimpleLink("Ethernet: " + connectionFrom.getName()
					+ " to " + connectionTo.getName());
				l.addDevice(connectionFrom);
				l.addDevice(connectionTo);
			}
			//Create ports and add to controller
			Port p1 = new Port(connectionFrom, (short) 1,(long) (Math.random()*300+300));
			Port p2 = new Port(connectionTo, (short) 2,(long) (Math.random()*300+300));
			Connection c = new ConnectionPerformance(l, new SimpleProtocol(p1, p2));
			c.addSmartDevice(p1);
			c.addSmartDevice(p2);
			p1.setConnection(c);
			p2.setConnection(c);
			connectionTo.addLink(l);
			connectionTo.addPort(p2);
			connectionFrom.addLink(l);
			connectionFrom.addPort(p1);
			network.addConnectionToLink(c, l);
			network.addConnection(c);
			if(!network.getLinks().contains(l))
				network.addLink(l);
			
		}
		connectionFrom = null;
		if(selectionModel.selectedDevices.isEmpty())
			mode = NOTHING;
		else
			mode = SELECTED;
		panel.repaint();
		controller.notifyObservers();
	}

	/**
	 * Returns SmartDevice which is at position (x,y) or within its
	 * visualization radius. Returns null, if no SmartDevice is within the
	 * range.
	 * 
	 * @param x
	 *            x-position which should be checked
	 * @param y
	 *            y-position which should be checked
	 * @return {@link SmartDevice} at position (x,y) or null if there is no
	 */
	private SmartDevice getSmartDeviceAtPosition(int x, int y) {
		// Check is device is inside visualization radius
		for (SmartDevice d : network.getVisibleSmartDevices()) {
			if (Math.abs(d.getX() - x) < config.getDeviceVisualizationRadius()
					&& Math.abs(d.getY() - y) < config.getDeviceVisualizationRadius()) {
				/**
				 * correct distance / radius from the smartDevice
				 */
				int radius = (int) Math.floor(Math.sqrt((d.getX() - x)*(d.getX() - x)+(d.getY() - y)*(d.getY() - y)));
				if(radius <= config.getDeviceVisualizationRadius())
					return d;
			}
		}
		return null;
	}
	
	/**
	 * Returns all Connections, which cross point (x,y)
	 * @param x xPosition of the point (x,y)
	 * @param y yPosition of the point (x,y)
	 * @return List of all possible clicked Connections
	 */
	private LinkedList<Pair<Connection, Pair<Port, Port>>> getConnectionsAtPosition(int x, int y) {
		LinkedList<Pair<Connection,Pair<Port,Port>>> edges = new LinkedList<Pair<Connection,Pair<Port,Port>>>();
		// Check is device is inside visualization radius
		for(Connection c: network.getVisibleConnections()){
			if(c.getProtocol()==null)
				continue;
			for(Pair<Port, Port> p: c.getProtocol().getTopology()){
				if(pointOnConnection(x,y,p)){
					edges.add(new Pair<Connection, Pair<Port,Port>>(c, p));
					//break;//Connection was clicked - check for further connections
				}
			}
		}
		return edges;
	}

	/**
	 * Returns true if the point (x,y) is on the connection part p.
	 * 
	 * @param x xPosition of the Point
	 * @param y yPosition of the Point
	 * @param p connection part, which connects to Ports with a line
	 * @return true if point is on connection, else false
	 */
	private boolean pointOnConnection(int x, int y, Pair<Port, Port> p) {
		/** Filter invalid lines to prevent nullpointers */
		if(p==null||p.getLeft()==null||p.getRight()==null||p.getLeft().getOwner()==null
				||p.getRight().getOwner()==null)
			return false;		
		/** Number of pixels between click and connection, which should be recognized as click on the connection */
		double hitBoxThreshold = 4;
		/** Connection which might be clicked */
		Line2D con = new Line2D.Float(p.getLeft().getOwner().getX(), p.getLeft().getOwner().getY(), p.getRight().getOwner().getX(), p.getRight().getOwner().getY());
		/** Distance between click and connection */
		double dist = con.ptSegDist(x, y);
		/** Return true if within threshold */
		return dist<hitBoxThreshold;
	}

	@Override
	public void keyTyped(KeyEvent e) {
		if(e.getKeyCode()==KeyEvent.VK_CONTROL){
			if(controlDown!=true){
				controlDown = true;
				panel.repaint();
			}
		}
		if(e.getKeyCode()==KeyEvent.VK_A && controlDown){
			selectAllDevics();	
		}
		if(e.getKeyCode()==KeyEvent.VK_ESCAPE){
			deselectAllDevics();
		}
	}
	
	/**
	 * Select all Devices
	 */
	private void selectAllDevics(){
		deselectAllDevics();
		config.getConfigurationManager().getSelectionModel().selectedDevices.addAll(controller.getNetworkController().getVisibleSmartDevices());
		panel.repaint();
	}
	
	/**
	 * Deselect all Devices
	 */
	private void deselectAllDevics(){
		config.getConfigurationManager().getSelectionModel().selectedDevicesDrag.clear();
		config.getConfigurationManager().getSelectionModel().selectedDevices.clear();
		panel.repaint();
	}
	
	@Override
	public void keyPressed(KeyEvent e) {
		if(e.getKeyCode()==KeyEvent.VK_CONTROL){
			if(controlDown!=true){
				controlDown = true;
				panel.repaint();
			}
		}
		if(e.getKeyCode()==KeyEvent.VK_A && controlDown){
			selectAllDevics();
		}
		if(e.getKeyCode()==KeyEvent.VK_ESCAPE){
			deselectAllDevics();
		}
		if(e.getKeyCode()==KeyEvent.VK_F2){
			/** mouse position */
			Point p = panel.getMousePosition();
			if(p!=null){
				/** Device at mouse */
				SmartDevice d = getSmartDeviceAtPosition((int)p.getX(), (int)p.getY());
				if(d!=null){
					/**
					 * PopUp for editing the given device
					 */
					SmartDeviceCreationPopUp popUp = new SmartDeviceCreationPopUp(d, true,controller);
					popUp.setLocationRelativeTo(panel);
					popUp.setEnabled(true);
					popUp.setVisible(true);
					popUp.setNameFocus();
				}
			}
		}
	}

	@Override
	public void keyReleased(KeyEvent e) {
		if(e.getKeyCode()==KeyEvent.VK_CONTROL){
			if(controlDown==true){
				controlDown = false;
				panel.repaint();
			}
		}
	}
	
	@Override
	public void mouseMoved(MouseEvent e) {
		/**
		 * Link which might get a toolTip
		 */
		Link l = null; 
		/**
		 * Check Hover on Link
		 */
		if(config.isShowLinkToolTips())
			l = getLinkVisualizationAtPosition(e.getX(),e.getY());
		if(l != null){
			if(l != toolTip.getLink() || !toolTip.isEnabled()){
				toolTip.setText("Link "+l.getName());
				toolTip.setLink(l);
				toolTip.setX(e.getX());
				toolTip.setY(e.getY());
				toolTip.setEnabled(true);
				panel.update(null, null);
			}
		}else{
			if(toolTip.getLink()!=null || toolTip.isEnabled()){
				toolTip.setLink(null);
				toolTip.setEnabled(false);
				panel.update(null, null);
			}
		}
	}

	/**
	 * Return Link which is at position at the given position
	 * @param x xPosition
	 * @param y yPosition
	 * @return Link at the position, else null
	 */
	private Link getLinkVisualizationAtPosition(int x, int y) {
		if(!config.isShowLinks())
			return null;
		// Check is device is inside visualization radius
		/**
		 * Radius of the device
		 */
		int smallRadius = config.getDeviceVisualizationRadius();
		/**
		 * Radius of the device + link
		 */
		int radius =  smallRadius + config.getLinkRadius();
		for (SmartDevice d : network.getVisibleSmartDevices()) {
			//In DeviceRadius + Link Radius ?
			if (Math.abs(d.getX() - x) <= radius && Math.abs(d.getY() - y) <= radius) {
				//More detailed check
				// On Ring ?
				// On which part of Ring ?
				//Outside of device ?
				int realDistance = (int)Math.round(Math.sqrt((d.getX() - x)*(d.getX() - x)+(d.getY() - y)*(d.getY() - y)));
				if (realDistance >= smallRadius && realDistance <= radius && !d.getLinks().isEmpty()) {
					/**
					 * Angle of this point. Counterclockwise, starting at 3 o' clock position
					 */
					float angle = Utility.getAngle(d.getX(), d.getY(), x, y);
					/**
					 * Number of Links in the Model
					 */
					int numberOfLinks = network.getVisibleLinks().size();
					/**
					 * Angle per Link in "linkSlice degrees"
					 */
					double linkSlice = Math.min(360.0 / numberOfLinks,90.0);
					/**
					 * calculate the number of the link
					 */
					int linkNumber = (int) Math.floor(angle / linkSlice);
					/**
					 * Return if, there are not so many link
					 */
					if(linkNumber>=numberOfLinks)
						return null;
					/**
					 * Link, which would be at this connection
					 */
					Link linkAtPosition = (Link) network.getVisibleLinks().toArray()[linkNumber];
					/**
					 * Return link, if smartDevice contains it
					 */
					if(d.getLinks().contains(linkAtPosition))
						return linkAtPosition;
				}
			}
		}
		return null;
	}

	@Override
	public void mouseEntered(MouseEvent e) {}

	@Override
	public void mouseExited(MouseEvent e) {
		toolTip.setEnabled(false);
	}

	@Override
	public void actionPerformed(ActionEvent e) {}
	
	/**
	 * Shows the RightClick Menu on the Visualization Panel, with Options for
	 * the given SmartDevice clickedOn, if clickedOn is null, the RightClick
	 * Menu contains Options for creation of new SmartDevices. The Menu will be
	 * shown at the Mouse position on the VisualisationPanel.
	 * 
	 * @param clickedOn
	 *            Device which was clicked, otherwise null
	 * @param clickedLink Link which was clicked, otherwise null
	 */
	protected void showRightClickMenu(SmartDevice clickedOn, Link clickedLink) {
		this.clickedLink = clickedLink;
		/**
		 * Mouse Position on the VisualisationPanel
		 */
		Point mousePos = panel.getMousePosition();
		// Just execute if Mouse Position is on the Panel
		if (mousePos != null) {			
			if (clickedOn == null) {
				/**
				 * Links which could be captured
				 */
				LinkedList<Link> captureLinks = new LinkedList<Link>();
				if(clickedLink == null){
					itemCreateLink.setText("Create Link");
					if(selectionModel.selectedDevices.isEmpty())
						itemCreateLink.setEnabled(false);
					else
						itemCreateLink.setEnabled(true);
				}else{
					itemCreateLink.setText("Edit Link");
					itemCreateLink.setEnabled(true);
					captureLinks.add(clickedLink);
				}
				
				itemCreate.setText("Create Device");
				itemCreate.setEnabled(true);
				itemDelete.setEnabled(false);
				itemDeleteSelected.setEnabled(false);
				if(selectionModel.clickedConnection.size()>0){
					/**
					 * Update Connections which can be edited
					 */
					itemEditConnection.removeAll();	
					
					//List to prevent duplicate connection entries
					LinkedList<Connection> editableConnections = new LinkedList<Connection>();
					for(Pair<Connection, Pair<Port,Port>> p:selectionModel.clickedConnection){
						//Don't add the same connection multiple times
						if(editableConnections.contains(p.getLeft())|| p == null || p.getLeft() == null)continue;
						editableConnections.add(p.getLeft());
						JMenuItem a = new JMenuItem(p.getLeft().getName());
						a.addActionListener(e->new ConnectionCreationDialog(p.getLeft(), controller, panel));
						itemEditConnection.add(a);
					}
					//Add Links of editable connections to the editLinks
					for(Connection c:editableConnections){
						if(c.getLink() != null && !captureLinks.contains(c.getLink()))
							captureLinks.add(c.getLink());
					}
					
					if(rightClickMenu.getComponentIndex(itemCreateConnection)!=-1){
						rightClickMenu.remove(itemCreateConnection);
						rightClickMenu.add(itemEditConnection,editCreateConnectionIndex);
						itemCreateConnection.setEnabled(false);				
					}
					itemEditConnection.setEnabled(true);
				}else{
					if(rightClickMenu.getComponentIndex(itemEditConnection)!=-1){
						rightClickMenu.remove(itemEditConnection);
						rightClickMenu.add(itemCreateConnection,editCreateConnectionIndex);
					}
					if(selectionModel.selectedDevices.isEmpty()){
						itemCreateConnection.setEnabled(false);
					}else{
						itemCreateConnection.setEnabled(true);
					}
				}
				
				//Item capture
				itemCapture.removeAll();
				if(captureLinks.isEmpty()){
					itemCapture.setEnabled(false);
				}else{
					for(Link l: captureLinks){
						/**
						 * Menu item to display a link which could be captured
						 */
						JMenu currentLink = new JMenu(l.getName());
						for(PacketCollector collector: captureController.getPacketCollectors()){
							JMenuItem currentAlgorithm = new JMenuItem();
							String algoName;
							if(collector.getPacketAlgorithm()==null){
								algoName = "Null";
							}else{
								algoName = collector.getPacketAlgorithm().getClass().getSimpleName();
							}
							if(collector.getLinks().contains(l)){
								algoName += ": stop capture";
								currentAlgorithm.addActionListener(a->captureController.removeLinkFromCollector(collector, l));
							}else{
								algoName += ": start capture";
								currentAlgorithm.addActionListener(a->captureController.addLinkToCollector(collector, l));
							}
							currentAlgorithm.setText(algoName);
							currentLink.add(currentAlgorithm);
						}
						JMenuItem newCapture = new JMenuItem("Create new PacketCapturer");
						newCapture.addActionListener(a->{
							PacketCollector c = new PacketCollector();
							c.addLink(l);
							EditPacketSniffer popUp = new EditPacketSniffer(controller, panel, c, false);
							popUp.setVisible(true);
						});
						currentLink.add(newCapture);
						itemCapture.add(currentLink);
					}
					
					itemCapture.setEnabled(true);
				}
			} else {
				itemCreate.setText("Edit Device");
				itemCreate.setEnabled(true);
				itemDelete.setEnabled(true);
				//Remove edit connection if it exist
				if(rightClickMenu.getComponentIndex(itemEditConnection)!=-1){
					rightClickMenu.remove(itemEditConnection);
					rightClickMenu.add(itemCreateConnection,editCreateConnectionIndex);
				}
				itemCreateLink.setText("Create Link");
				itemCapture.removeAll();
				if(selectionModel.selectedDevices.contains(clickedOn)){
					itemDeleteSelected.setEnabled(true);
					itemCreateLink.setEnabled(true);
					itemCreateConnection.setEnabled(true);
					
					/**
					 * Capture multiple
					 */
					for(PacketCollector collector:captureController.getPacketCollectors()){
						JMenuItem itemCaptureDevice = new JMenuItem();
						String captureDeviceText = "";
						if(collector.getPacketAlgorithm()==null){
							captureDeviceText += "Null: ";
						}else{
							captureDeviceText += collector.getPacketAlgorithm().getClass().getSimpleName()+": ";
						}
						if(collector.getDevices().contains(clickedOn)){
							captureDeviceText += "stop capturing all";
							itemCaptureDevice.addActionListener(a->{
								for(SmartDevice d: selectionModel.selectedDevices)
									captureController.removeDeviceFromCollector(collector, d);
							});
						}else{
							captureDeviceText += "start capturing all";
							itemCaptureDevice.addActionListener(a->{
								for(SmartDevice d: selectionModel.selectedDevices)
									captureController.addDeviceToCollector(collector, d);
							});
						}
						itemCaptureDevice.setText(captureDeviceText);
						itemCapture.add(itemCaptureDevice);					
					}
					itemCapture.setEnabled(true);
					JMenuItem newCapture = new JMenuItem("Create new PacketCapturer");
					newCapture.addActionListener(a->{
						PacketCollector c = new PacketCollector();
						for(SmartDevice d: selectionModel.selectedDevices)
							c.addDevice(d);
						EditPacketSniffer popUp = new EditPacketSniffer(controller, panel, c, false);
						popUp.setVisible(true);
					});
					itemCapture.add(newCapture);
				}
				else{
					itemDeleteSelected.setEnabled(false);
					itemCreateLink.setEnabled(false);
					itemCreateConnection.setEnabled(false);
					/**
					 * Capture Single Device
					 */
					for(PacketCollector collector:captureController.getPacketCollectors()){
						JMenuItem itemCaptureDevice = new JMenuItem();
						String captureDeviceText = "";
						if(collector.getPacketAlgorithm()==null){
							captureDeviceText += "Null: ";
						}else{
							captureDeviceText += collector.getPacketAlgorithm().getClass().getSimpleName()+": ";
						}
						if(collector.getDevices().contains(clickedOn)){
							captureDeviceText += "stop capture";
							itemCaptureDevice.addActionListener(a->captureController.removeDeviceFromCollector(collector, clickedOn));
						}else{
							captureDeviceText += "start capture";
							itemCaptureDevice.addActionListener(a->captureController.addDeviceToCollector(collector, clickedOn));
						}
						itemCaptureDevice.setText(captureDeviceText);
						itemCapture.add(itemCaptureDevice);					
					}
					itemCapture.setEnabled(true);
					JMenuItem newCapture = new JMenuItem("Create new PacketCapturer");
					newCapture.addActionListener(a->{
						PacketCollector c = new PacketCollector();
						c.addDevice(clickedOn);
						EditPacketSniffer popUp = new EditPacketSniffer(controller, panel, c, false);
						popUp.setVisible(true);
					});
					itemCapture.add(newCapture);
				}
				
				
			}
			// Enable/Disable debug print out
			itemDebug.setVisible(config.isDebugModus());
			// Show the RightClickMenu
			rightClickMenu.show(panel, mousePos.x, mousePos.y);
			rightClickMenu.setEnabled(true);
			rightClickMenu.setVisible(true);
			mode = RIGHTCLICK_MENU;
		}
	}

	/**
	 * Creates the RightClickMenu, adds the different Labels and functionalities to the menu and adds it to the panel.
	 */
	private void initializeRightClickMenu() {
		this.rightClickMenu = new JPopupMenu();

		// Create device option
		itemCreate = new JMenuItem("Create SmartDevice");

		itemCreate.addActionListener(e -> {
			SmartDevice toEdit;
			if(clicked == null){
				toEdit = new SmartDevice("DeviceName");
				toEdit.setX(dragged_x);
				toEdit.setY(dragged_y);
			}else{
				toEdit = clicked;
			}
			SmartDeviceCreationPopUp popUp = new SmartDeviceCreationPopUp(toEdit, toEdit==clicked,controller);
			popUp.setLocationRelativeTo(panel);
			popUp.setEnabled(true);
			popUp.setVisible(true);
			mode = NOTHING;

		});

		rightClickMenu.add(itemCreate);
		
		// Create Link
		itemCreateLink = new JMenuItem("Create Link");
		
		itemCreateLink.addActionListener(e -> {
			if(clickedLink!=null){
				new LinkCreationDialog(clickedLink, controller, panel);
			}else{
				new LinkCreationDialog(selectionModel.selectedDevices, controller, panel);
			}
			if(selectionModel.selectedDevices.isEmpty())
				mode = NOTHING;
			else
				mode = SELECTED;
			
		});
		rightClickMenu.add(itemCreateLink);
		
		// Create Connection
		itemCreateConnection = new JMenuItem("Create Connection");
		itemCreateConnection.addActionListener(e->{
			LinkedList<SmartDevice> selectedDevices = new LinkedList<SmartDevice>(selectionModel.selectedDevices);
			
			new ConnectionCreationDialog(selectedDevices, controller, panel);
			if(selectionModel.selectedDevices.isEmpty())
				mode = NOTHING;
			else
				mode = SELECTED;
		});
		rightClickMenu.add(itemCreateConnection);
		
		// Edit connection
		itemEditConnection = new JMenu("Edit Connection");
		
		rightClickMenu.add(itemEditConnection);
		
		//Capture packets
		itemCapture = new JMenu("Capture Packets");
		
		rightClickMenu.add(itemCapture);
		
		// Delete device option
		itemDelete = new JMenuItem("Delete");

		itemDelete.addActionListener(e -> {
			// Delete the clicked object
				network.deleteSmartDevice(clicked);
				clicked = null;
				controller.notifyObservers();
				mode = NOTHING;
			});

		rightClickMenu.add(itemDelete);
		
		// Delete device option
		itemDeleteSelected = new JMenuItem("Delete Selected");
		
		itemDeleteSelected.addActionListener(e -> {
			// Delete the clicked object
			for(SmartDevice c: selectionModel.selectedDevices)
				network.deleteSmartDevice(c);
			selectionModel.selectedDevices.clear();
			clicked = null;
			controller.notifyObservers();
			mode = NOTHING;
		});
		itemDeleteSelected.setEnabled(false);
		rightClickMenu.add(itemDeleteSelected);
				
		/*
		 * Add PrintDebug option
		 */
		itemDebug = new JMenuItem("Print NetworkState");
		itemDebug.addActionListener(e -> {
			// Print Links, Devices and Connections
			System.out.println("Links:");
				for (Link l : network.getLinks()){
					System.out.println("Link: " + l.getName());
					System.out.print("Devices of "+l.getName()+": ");
					for(SmartDevice d:l.getDevices())
						System.out.print(d.getName()+", ");
					System.out.println();
					System.out.print("Connections of "+l.getName()+": ");
					for(Connection c: l.getConnections())
						System.out.print(c.getName()+", ");
					System.out.println();
				}
				System.out.println();
				System.out.println("Devices");
				for (SmartDevice d : network.getSmartDevices()) {
					System.out.println("Device: " + d.getName());
					System.out.println("Ports: ");
					for (Port p : d.getPorts()) {
						if(p==null){
							System.out.println("Port: null");
							continue;
						}
						System.out.println("Port: "+p.getPortNumber());
						if(p.getOwner()!=null)
							System.out.println("Owner: "+p.getOwner().getName());
						else
							System.out.println("Owner: null");
						if(p.getConnection()==null){
							System.out.println("Connection: null");
						}else{
							System.out.println("Connection: "+p.getConnection().getName());							
						}
						System.out.println("LastTrigger: " +p.getLastTrigger());
						System.out.println("Jitter: " +p.getJitter());
						System.out.println("ResponseTime: " +p.getResponseTime());
						System.out.println("TriggerInterval: " +p.getTriggerInterval());
						System.out.println("Status: " +Port.statusToString(p.getStatus()));
						System.out.println();
					}
					System.out.println("");
				}
				// Print Terminating Packages
				LinkedList<Connection> terminated = new LinkedList<Connection>();

				network.getConnections()
						.stream()
						.forEach(c -> {
							if(c.getStatus() == Connection.FINISHED || c
								.getStatus() == Connection.TERMINATED)
								terminated.add(c);
							for (Packet p : c.getTerminationPackages(1000))
								System.out.println(p.getTextualRepresentation());
						});
				network.getConnections().removeAll(terminated);
				if(selectionModel.selectedDevices.isEmpty())
					mode = NOTHING;
				else
					mode = SELECTED;
				controller.notifyObservers();
			});
		rightClickMenu.add(itemDebug);
		this.panel.add(rightClickMenu);
	}

	/**
	 * @return the toolTip
	 */
	public LinkToolTip getToolTip() {
		return toolTip;
	}
}
