package de.tu_darmstadt.tk.SmartHomeNetworkSim.view.popups;

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Observable;
import java.util.Observer;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.TreePath;

import de.tu_darmstadt.tk.SmartHomeNetworkSim.control.Controller;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.control.NetworkController;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.control.NetworkTreeSettingsController;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Connection;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Link;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Port;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.SmartDevice;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.configuration.NetworkTreeNodeStatus;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.view.util.Utility;

/**
 * Network Tree Panel which visualizes the Network as a Tree
 *
 * @author Andreas T. Meyer-Berg
 */
public class NetworkTreePanel extends JScrollPane implements Observer {
	/**
	 * 
	 */
	private static final long serialVersionUID = 6946547116132845220L;

	/**
	 * Parent Frame
	 */
	private JFrame parent;

	/**
	 * Root of the tree
	 */
	private DefaultMutableTreeNode root;

	/**
	 * JTree, which visualizes the network
	 */
	private JTree tree;

	/**
	 * Reference to this for anonymous listener
	 */
	private NetworkTreePanel that;
	/**
	 * NetworkTreeSettings Controller
	 */
	private NetworkTreeSettingsController networkTreeSettings;
	
	/**
	 * Network Controller
	 */
	private NetworkController network;
	/**
	 * Controller
	 */
	private Controller controller;

	/**
	 * Right click menu
	 */
	private JPopupMenu rightClick;
	
	/**
	 * MenuItem for editing of Links, Connections and Devices
	 */
	private JMenuItem mntmEdit = new JMenuItem("Edit");
	/**
	 * MenuItem for deletion of devices
	 */
	private JMenuItem mntmDelete = new JMenuItem("Delete");
	/**
	 * MenuItem for expanding/collapsing nodes
	 */
	private JMenuItem mntmExpand = new JMenuItem("Expand");
	/**
	 * MenuItem for changing visibility of objects
	 */
	private JMenuItem mntmShow = new JMenuItem("Show");
	/**
	 * MenuItem for changing visibility of an object and all its  descendants
	 */
	private JMenuItem mntmShowRecursive = new JMenuItem("Show recursive");
	
	/**
	 * Clicked object (Device, Link or Connection)
	 */
	private Object clickedObject;
	
	/**
	 * Mutex to prevent listeners from activating which building the JTree. If true, all listeners should not activate.
	 */
	private boolean mutexTreeUpdate = false;
	
	/**
	 * Creates a new Network Tree Panel
	 * 
	 * @param controller
	 *            Controller
	 * @param parent
	 *            parent frame
	 */
	public NetworkTreePanel(Controller controller, JFrame parent) {
		that = this;
		this.parent = parent;
		this.controller = controller;
		network = this.controller.getNetworkController();
		networkTreeSettings = controller.getSettingsController().getNetworkTreeSettingsController();

		//Adds + - as icons for expanding and collapsing the tree
		UIManager.put("Tree.expandedIcon",  new ImageIcon(Utility.loadFile("images/treeMinus.gif")));
		UIManager.put("Tree.collapsedIcon", new ImageIcon(Utility.loadFile("images/treePlus.gif")));
		
		tree = new JTree();
		initializeRightClickMenu();

		/**
		 * Custom Renderer
		 */
		tree.setCellRenderer(new NetworkTreeCellRenderer(controller));

		/**
		 * Tree selection Listener
		 */
		tree.addTreeSelectionListener(new TreeSelectionListener() {
			@Override
			public void valueChanged(TreeSelectionEvent e) {
				if(mutexTreeUpdate)return;
				//System.out.println("Path selected" + e.getPath());
			}
		});

		/**
		 * Tree click listener
		 */
		tree.addMouseListener(new MouseAdapter() {

			@Override
			public void mouseClicked(MouseEvent e) {
				if(mutexTreeUpdate)return;
				
				TreePath clicked = tree.getPathForLocation(e.getX(), e.getY());
				if(clicked==null||clicked.getPath() == null || clicked.getLastPathComponent() == null)
					return;
				clickedObject = ((DefaultMutableTreeNode)clicked.getLastPathComponent()).getUserObject();
				//Expand/Collapse
				/*
				if(SwingUtilities.isLeftMouseButton(e)&&e.getClickCount()==2&&clicked!=null&&clicked.getLastPathComponent()!=null){
					//Collapse/expand
					
					NetworkTreeNodeStatus status = networkTreeSettings.getStatusOfObject(((DefaultMutableTreeNode)clicked.getLastPathComponent()).getUserObject());
					System.out.println("Object: "+status.getNodeObject());
					System.out.println("expanded: "+status.isExpanded());
					status.setExpanded(!status.isExpanded());
					controller.notifyObservers();
				}*/
				if(SwingUtilities.isRightMouseButton(e)&&e.getClickCount()==1){
					showRightClickMenu(e, clicked);
				}else if(SwingUtilities.isLeftMouseButton(e)){
					Object o = ((DefaultMutableTreeNode)clicked.getLastPathComponent()).getUserObject();
					controller.getSettingsController().getNetworkTreeSettingsController().toggleVisibilityOf(o);
					
				}
					
				
				
			}
		});
		
		/**
		 * Tree expand/collapse Listener
		 */
		tree.addTreeWillExpandListener(new TreeWillExpandListener() {
			
			@Override
			public void treeWillExpand(TreeExpansionEvent event)
					throws ExpandVetoException {
				// TODO Auto-generated method stub
				TreePath clicked = event.getPath();
				/*
				System.out.println("Expand");
				System.out.println("Source: "+event.getSource());
				System.out.println("Path: "+event.getPath());*/
				if(mutexTreeUpdate || event == null || event.getPath() == null || event.getPath().getLastPathComponent()==null)
					return;
				/**
				 * Expand node
				 */
				NetworkTreeNodeStatus status = networkTreeSettings.getStatusOfObject(((DefaultMutableTreeNode)clicked.getLastPathComponent()).getUserObject());
				status.setExpanded(true);
				controller.notifyObservers();
				
			}
			
			@Override
			public void treeWillCollapse(TreeExpansionEvent event)
					throws ExpandVetoException {
				//System.out.println(event.getPath().getLastPathComponent());
				TreePath clicked = event.getPath();
				/*
				System.out.println("Collapse");
				System.out.println("Source: "+event.getSource());
				System.out.println("Path: "+event.getPath());*/
				// TODO Auto-generated method stub
				if(mutexTreeUpdate || event == null || event.getPath() == null || event.getPath().getLastPathComponent()==null)
					return;
				/**
				 * Collapse the node
				 */
				NetworkTreeNodeStatus status = networkTreeSettings.getStatusOfObject(((DefaultMutableTreeNode)clicked.getLastPathComponent()).getUserObject());
				status.setExpanded(false);
				controller.notifyObservers();
				
			}
		});
		this.setViewportView(tree);
		this.setPreferredSize(this.parent.getSize());
		
		
		/**
		 * MVC - Receive updates on model change, and remove listener on Frame
		 * close
		 */
		controller.addObserver(this);
		parent.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				controller.removeObserver(that);
				that.setVisible(false);
				tree.setVisible(false);
				rightClick.setVisible(false);
				parent.setVisible(false);
				parent.dispose();
				super.windowClosing(e);
			}
		});
		
		update(null, null);
	}




	@Override
	public void update(Observable o, Object arg) {
		mutexTreeUpdate = true;
		/**
		 * Current row inside the JTree. Just nodes which are visible are counted as JTree rows, so all Nodes which are inside collapsed Nodes are skipped.
		 */
		int row = 1;
		/**
		 * Indices of Rows which should be expanded
		 */
		LinkedList<Integer> expandedRows = new LinkedList<Integer>();
		
		root = new DefaultMutableTreeNode("Network");
			
		/**
		 * Counter for the rows in the tree autoExpand the expanded ones
		 */
		for (Link l : network.getLinks()) {
			if (l == null){
				System.err.println("Warning: link = null in NetworkTreePanel");
				continue;
			}
			
			NetworkTreeNodeStatus linkStatus = networkTreeSettings.getStatusOfObject(l);
			DefaultMutableTreeNode link = new DefaultMutableTreeNode(l);
			root.add(link);
			if(linkStatus.isExpanded())
				expandedRows.add(row++);
			else
				row++;
			
			
			for (Connection con : network.getConnections()) {
				if (con == null || !l.getConnections().contains(con))
					continue;
				DefaultMutableTreeNode connection = new DefaultMutableTreeNode(con);
				NetworkTreeNodeStatus connectionStatus = networkTreeSettings.getStatusOfObject(con);
				link.add(connection);
				if(linkStatus.isExpanded()){
					if(connectionStatus.isExpanded())
						expandedRows.add(row++);
					else
						row++;
				}
				
				for (Port p : con.getParticipants()) {
					if (p == null || !l.getDevices().contains(p.getOwner())
							|| !con.getParticipants().contains(p))
						continue;
					DefaultMutableTreeNode port = new DefaultMutableTreeNode(
							p.getOwner());
					connection.add(port);
					@SuppressWarnings("unused")//Later Hidden/Visible representation
					NetworkTreeNodeStatus deviceStatus = networkTreeSettings.getStatusOfObject(p.getOwner());
					// Leafs should not be expanded
					if(linkStatus.isExpanded()&&connectionStatus.isExpanded())
						row++;

				}
			}
		}
		/**
		 * Add devices without link to the network view
		 */
		Collection<SmartDevice> noLinkDevices = new LinkedList<SmartDevice>(
				network.getSmartDevices());
		noLinkDevices.removeIf(d -> (d.getLinks().size() > 0));
		if (noLinkDevices.size() > 0) {
			DefaultMutableTreeNode noLink = new DefaultMutableTreeNode(
					"No Link");
			DefaultMutableTreeNode noCon = new DefaultMutableTreeNode(
					"No Connection");
			noLink.add(noCon);
			for (SmartDevice d : noLinkDevices)
				noCon.add(new DefaultMutableTreeNode(d));
			root.add(noLink);
		}
		DefaultTreeModel model = new DefaultTreeModel(root);
		tree.setModel(model);
		expandTree(expandedRows);
		/**
		 * Repaint
		 */
		tree.repaint();
		mutexTreeUpdate = false;
	}
	
	/**
	 * Expands the tree, as specified in the NetworkTreeSettingsController
	 */
	private void expandTree(LinkedList<Integer> expandedRows) {
		tree.expandRow(0);
		for(Integer i:expandedRows)
			tree.expandRow(i);
	}




	/**
	 * Initializes the rightClickMenu
	 */
	private void initializeRightClickMenu() {
		rightClick = new JPopupMenu();
		
		mntmExpand.addActionListener(a->{
			if(clickedObject==null)return;
				networkTreeSettings.toggleExpanded(clickedObject);
		});
		
		rightClick.add(mntmExpand);
		
		mntmShow.addActionListener(a -> {
			if(clickedObject==null)return;
			networkTreeSettings.toggleVisibilityNonRecursive(clickedObject);
		});
		rightClick.add(mntmShow);
		

		mntmShowRecursive.addActionListener(a -> {
			if(clickedObject==null)return;
			networkTreeSettings.toggleVisibilityOf(clickedObject);
		});
		rightClick.add(mntmShowRecursive);
		
		mntmEdit.addActionListener(a->{
			if(clickedObject == null)return;
			if(clickedObject instanceof SmartDevice){
				SmartDeviceCreationPopUp p = new SmartDeviceCreationPopUp((SmartDevice) clickedObject, true, controller);
				p.setLocationRelativeTo(that);
				p.setEnabled(true);
				p.setVisible(true);
			}else if(clickedObject instanceof Connection){
				new ConnectionCreationDialog((Connection) clickedObject, controller, that);
			}else if(clickedObject instanceof Link){
				new LinkCreationDialog((Link) clickedObject, controller, that);
			}else{
				//Invalid object
			}
		});
		rightClick.add(mntmEdit);
		
		mntmDelete.addActionListener(a->{
			if(clickedObject == null)return;
			if(clickedObject instanceof SmartDevice){
				controller.getNetworkController().deleteSmartDevice((SmartDevice) clickedObject);
				controller.notifyObservers();
			}else if(clickedObject instanceof Connection){
				controller.getNetworkController().deleteConnection((Connection) clickedObject);
				controller.notifyObservers();
			}else if(clickedObject instanceof Link){
				controller.getNetworkController().deleteLink((Link) clickedObject);
				controller.notifyObservers();
			}else{
				//Invalid object
			}
		});
		rightClick.add(mntmDelete);
		parent.add(rightClick);
		
	}
	/**
	 * Shows the right click menu
	 * @param e Mouse event which triggred this method
	 * @param clicked clicked tree path
	 */
	private void showRightClickMenu(MouseEvent e, TreePath clicked) {
		if(!SwingUtilities.isRightMouseButton(e) || clicked == null ){
			/**
			 * Hide Menu, maybe create methods later ?
			 */
			rightClick.setVisible(false);
			rightClick.setEnabled(false);
		}else{
			clickedObject = ((DefaultMutableTreeNode)clicked.getLastPathComponent()).getUserObject();
			NetworkTreeNodeStatus status = networkTreeSettings.getStatusOfObject(clickedObject);
			if(clickedObject instanceof SmartDevice){
				//TODO: Specific actions
				mntmEdit.setText("Edit Device");
				mntmEdit.setEnabled(true);
				mntmDelete.setText("Delete Device");
				mntmDelete.setEnabled(true);
				
				mntmExpand.setText("Expand");
				mntmExpand.setEnabled(false);
				if(status.isVisible()){
					mntmShow.setText("Hide");
					mntmShowRecursive.setText("Hide recursive");					
				}else{
					mntmShow.setText("Show");
					mntmShowRecursive.setText("Show recursive");
				}
				mntmShow.setEnabled(true);
				mntmShowRecursive.setEnabled(false);
			} else if(clickedObject instanceof Connection){
				mntmEdit.setText("Edit Connection");
				mntmEdit.setEnabled(true);
				mntmDelete.setText("Delete Connection");
				mntmDelete.setEnabled(true);
				
				if(status.isExpanded()){
					mntmExpand.setText("Collapse");
				}else{
					mntmExpand.setText("Expand");
				}
				mntmExpand.setEnabled(true);
				if(status.isVisible()){
					mntmShowRecursive.setText("Hide recursive");					
					mntmShow.setText("Hide");
				}else{
					mntmShowRecursive.setText("Show recursive");
					mntmShow.setText("Show");
				}
				mntmShowRecursive.setEnabled(true);
				mntmShow.setEnabled(true);
			}else if(clickedObject instanceof Link){
				mntmEdit.setText("Edit Link");
				mntmEdit.setEnabled(true);
				mntmDelete.setText("Delete Link");
				mntmDelete.setEnabled(true);

				if(status.isExpanded()){
					mntmExpand.setText("Collapse");
				}else{
					mntmExpand.setText("Expand");
				}
				mntmExpand.setEnabled(true);
				if(status.isVisible()){
					mntmShow.setText("Hide");
					mntmShowRecursive.setText("Hide recursive");					
				}else{
					mntmShow.setText("Show");
					mntmShowRecursive.setText("Show recursive");
				}
				mntmShow.setEnabled(true);
				mntmShowRecursive.setEnabled(true);
			}else{
				mntmEdit.setText("Edit");
				mntmEdit.setEnabled(false);
				mntmDelete.setText("Delete");
				mntmDelete.setEnabled(false);

				mntmExpand.setText("Expand");
				mntmExpand.setEnabled(false);
				mntmShow.setText("Show");
				mntmShow.setEnabled(false);
				mntmShowRecursive.setText("Show recursive");
				mntmShowRecursive.setEnabled(false);
			}
			
			rightClick.show(tree,e.getX(), e.getY());
			rightClick.setEnabled(true);
			rightClick.setVisible(true);
			
		}
	}
}
