package ui;

import classes.Layer;
import classes.Project;
import classes.ProjectElement;
import enums.TrafficDefinitionLayerType;
import helpers.Broadcaster;
import interfaces.CurrentLayerChangedListener;
import interfaces.ProjectChangedListener;
import java.awt.BorderLayout;
import java.awt.ComponentOrientation;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import roadnetwork.RoadNetwork;
import trafficdefinition.TrafficDefinitionLayer;
import ui.controls.JTrafficLayerTree;
import ui.controls.JTrafficLayerTreeModel;
import ui.controls.ProjectTreeCellRenderer;

/**
 * Panel used to display and modify the current project's layer collection
 */
public class PnlLayers extends JPanel implements TreeSelectionListener, ProjectChangedListener, TreeWillExpandListener {

	private static final long serialVersionUID = 1L;

	private JButton btnDelete = null;

	private JButton btnDuplicate = null;

	private JButton btnNew = null;

	/**
	 * Collection of objects that will be notified when the user selects another
	 * layer
	 */
	private List<CurrentLayerChangedListener> currentLayerChangedListeners = new ArrayList<CurrentLayerChangedListener>(); // @jve:decl-index=0:

	/**
	 * Currently loaded project
	 */
	private Project project = null;

	private JScrollPane scrlLayerList = null;

	private JToolBar tlbLayers = null;

	private JTrafficLayerTree treeLayers = null;

	/**
	 * This is the default constructor
	 */
	public PnlLayers() {
		super();
		initialize();

		// Be informed when the loaded project is changed
		Broadcaster.addProjectChangedListener(this);

		treeLayers.addTreeWillExpandListener(this);
	}

	/**
	 * Registers the specified listener so that it will be notified when the
	 * current layer changes
	 * 
	 * @param currentLayerChangedListener
	 */
	public void addCurrentLayerChangedListener(CurrentLayerChangedListener currentLayerChangedListener) {
		currentLayerChangedListeners.add(currentLayerChangedListener);
	}

	/**
	 * Notifies the registered listeners that the current layer has changed
	 * 
	 * @param currentLayer
	 */
	public void NotifyCurrentLayerChanged(Layer<? extends ProjectElement> currentLayer) {
		for (CurrentLayerChangedListener currentLayerChangedListener : currentLayerChangedListeners) {
			currentLayerChangedListener.CurrentLayerChanged(currentLayer);
		}
	}

	/**
	 * Called when the project is closed, opened or created
	 */
	public void ProjectChanged(Project p) {
		project = p;

		// If the project was closed reset the component
		if (project == null) {
			treeLayers.removeTreeSelectionListener(this);

			treeLayers.setModel(new DefaultTreeModel(new DefaultMutableTreeNode("No project loaded")));
			treeLayers.updateUI();

			btnDelete.setEnabled(false);
			btnDuplicate.setEnabled(false);
			btnNew.setEnabled(false);
		}
		// Project opened or created
		else {
			// Create the tree nodes
			DefaultMutableTreeNode root = new DefaultMutableTreeNode("Project");
			DefaultMutableTreeNode mapNode = new DefaultMutableTreeNode(project.getRoadNetwork());
			DefaultMutableTreeNode layersNode = new DefaultMutableTreeNode("Traffic Layers");

			root.add(mapNode);

			for (TrafficDefinitionLayer layer : project.getTrafficLayers()) {
				DefaultMutableTreeNode layerNode = new DefaultMutableTreeNode(layer);

				layersNode.add(layerNode);
			}

			root.add(layersNode);

			JTrafficLayerTreeModel dtm = new JTrafficLayerTreeModel(root);

			treeLayers.setModel(dtm);

			// Select the map node initially
			DefaultMutableTreeNode initialSelection = mapNode;

			TreePath initialSelectionPath = new TreePath(((DefaultTreeModel) treeLayers.getModel()).getPathToRoot(initialSelection));

			treeLayers.addTreeSelectionListener(this);

			treeLayers.setSelectionPath(initialSelectionPath);
		}
	}

	/**
	 * 
	 * @param currentLayerChangedListener
	 */
	public void removeCurrentLayerChangedListener(CurrentLayerChangedListener currentLayerChangedListener) {
		if (currentLayerChangedListeners.contains(currentLayerChangedListener)) {
			currentLayerChangedListeners.remove(currentLayerChangedListener);
		}
	}

	public void treeWillCollapse(TreeExpansionEvent e) throws ExpandVetoException {
		//Dont let the user collapse the tree
		throw new ExpandVetoException(e);
	}

	public void treeWillExpand(TreeExpansionEvent e) throws ExpandVetoException {
	}

	/**
	 * Called when the user selects a layer in the tree
	 */
	public void valueChanged(javax.swing.event.TreeSelectionEvent e) {
		//Get the selected object
		Object newObject = ((DefaultMutableTreeNode) e.getNewLeadSelectionPath().getLastPathComponent()).getUserObject();

		// If the user has selected a traffic layer
		if (newObject instanceof TrafficDefinitionLayer) {
			// Inform the listeners that the current layer has changed
			NotifyCurrentLayerChanged((TrafficDefinitionLayer) newObject);

			// Enable buttons
			btnDelete.setEnabled(true);
			btnNew.setEnabled(true);
			btnDuplicate.setEnabled(true);
		}
		// If the user selected the map
		else if (newObject instanceof RoadNetwork) {
			// Inform the listeners that the current layer has changed
			NotifyCurrentLayerChanged((RoadNetwork) newObject);

			// Disable buttons
			btnDelete.setEnabled(false);
			btnNew.setEnabled(false);
			btnDuplicate.setEnabled(false);
		}
		// If the root or the layers node was selected
		else if (newObject instanceof String) {
			String o = (String) newObject;

			// If the user selected the root node then select the previously
			// selected node (i.e. don't let the user select the root node
			if (o.equals("Project")) {
				treeLayers.setSelectionPath(e.getOldLeadSelectionPath());

				return;
			}
			// If the user selected the layers node then select the first
			// layer
			else if (o.equals("Traffic Layers")) {
				TreePath tp = e.getNewLeadSelectionPath();

				DefaultMutableTreeNode firstLayerNode = ((DefaultMutableTreeNode) tp.getLastPathComponent()).getFirstLeaf();

				TreePath selectionPath = new TreePath(((DefaultTreeModel) treeLayers.getModel()).getPathToRoot(firstLayerNode));

				treeLayers.setSelectionPath(selectionPath);
			}
		}
	}

	/**
	 * This method initializes btnDelete
	 * @return  javax.swing.JButton
	 * @uml.property  name="btnDelete"
	 */
	private JButton getBtnDelete() {
		if (btnDelete == null) {
			btnDelete = new JButton();
			btnDelete.setIcon(new ImageIcon(getClass().getResource("/resources/icons/deletelayer.png")));
			btnDelete.setToolTipText("Delete layer");
			btnDelete.setEnabled(false);
			btnDelete.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					DeleteCurrentTrafficLayer();
				}
			});
		}
		return btnDelete;
	}

	/**
	 * This method initializes btnDuplicate
	 * @return  javax.swing.JButton
	 * @uml.property  name="btnDuplicate"
	 */
	private JButton getBtnDuplicate() {
		if (btnDuplicate == null) {
			btnDuplicate = new JButton();
			btnDuplicate.setEnabled(false);
			btnDuplicate.setIcon(new ImageIcon(getClass().getResource("/resources/icons/duplicatelayer.png")));
			btnDuplicate.setToolTipText("Duplicate layer");
			btnDuplicate.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					DuplicateCurrentTrafficLayer();
				}
			});
		}
		return btnDuplicate;
	}

	/**
	 * This method initializes btnNew
	 * @return  javax.swing.JButton
	 * @uml.property  name="btnNew"
	 */
	private JButton getBtnNew() {
		if (btnNew == null) {
			btnNew = new JButton();
			btnNew.setIcon(new ImageIcon(getClass().getResource("/resources/icons/addlayer.png")));
			btnNew.setToolTipText("Add new layer");
			btnNew.setSize(new Dimension(28, 20));
			btnNew.setEnabled(false);
			btnNew.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					CreateNewTrafficLayer(null);
				}
			});
		}
		return btnNew;
	}

	/**
	 * This method initializes scrlLayerList
	 * @return  javax.swing.JScrollPane
	 * @uml.property  name="scrlLayerList"
	 */
	private JScrollPane getScrlLayerList() {
		if (scrlLayerList == null) {
			scrlLayerList = new JScrollPane();
			scrlLayerList.setPreferredSize(new Dimension(100, 100));
			scrlLayerList.setViewportView(getTreeLayers());
		}
		return scrlLayerList;
	}

	/**
	 * This method initializes tlbLayers
	 * @return  javax.swing.JToolBar
	 * @uml.property  name="tlbLayers"
	 */
	private JToolBar getTlbLayers() {
		if (tlbLayers == null) {
			tlbLayers = new JToolBar();
			tlbLayers.setSize(new Dimension(211, 30));
			tlbLayers.setFloatable(false);
			tlbLayers.setPreferredSize(new Dimension(18, 26));
			tlbLayers.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
			tlbLayers.add(getBtnNew());
			tlbLayers.add(getBtnDelete());
			tlbLayers.add(getBtnDuplicate());
		}
		return tlbLayers;
	}

	/**
	 * This method initializes treeLayers
	 * @return  javax.swing.JTree
	 * @uml.property  name="treeLayers"
	 */
	private JTree getTreeLayers() {
		if (treeLayers == null) {
			treeLayers = new JTrafficLayerTree();
			treeLayers.setShowsRootHandles(false);
			treeLayers.setToggleClickCount(-1);
			treeLayers.setModel(new DefaultTreeModel(new DefaultMutableTreeNode("No project loaded")));
			treeLayers.setEditable(true);
			treeLayers.setCellRenderer(new ProjectTreeCellRenderer());
			treeLayers.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
		}
		return treeLayers;
	}

	/**
	 * This method initializes this
	 * 
	 */
	private void initialize() {
		this.setSize(162, 200);
		this.setLayout(new BorderLayout());
		this.add(getScrlLayerList(), BorderLayout.CENTER);
		this.add(getTlbLayers(), BorderLayout.SOUTH);

	}

	/**
	 * Creates a new traffic layer
	 * 
	 * @param type The new layer's type
	 */
	protected void CreateNewTrafficLayer(TrafficDefinitionLayerType type) {
		//If the type was not specified then ask the user
		if(type==null){
			String s = (String) JOptionPane.showInputDialog(this,
															"Select the type of the new layer",
															"Create new traffic definition layer",
															JOptionPane.INFORMATION_MESSAGE,
															(Icon)new ImageIcon(getClass().getResource("/resources/icons/addlayer.png")),
															new String[]{"User-defined","Activity-based","Random"},
															"User-Defined"
															);
			
			if ((s != null) && (s.length() > 0)) {
				if(s.equals("User-defined")){
					type = TrafficDefinitionLayerType.UserDefined;
				}
				else if(s.equals("Activity-based")){
					type = TrafficDefinitionLayerType.ActivityBased;
				}
				else if(s.equals("Random")){
					type = TrafficDefinitionLayerType.Random;
				}
			}
			else{
				return;
			}
		}
		
		//Create the layer
		TrafficDefinitionLayer newLayer = project.createNewLayer(type);

		//Add the node to the tree
		DefaultMutableTreeNode newLayerNode = new DefaultMutableTreeNode(newLayer);

		DefaultMutableTreeNode layersNode = (DefaultMutableTreeNode) ((DefaultMutableTreeNode) treeLayers.getModel().getRoot()).getChildAt(1);

		layersNode.add(newLayerNode);

		//Select the new layer
		treeLayers.setSelectionPath(new TreePath(newLayerNode.getPath()));

		treeLayers.updateUI();
	}

	/**
	 * Deletes the currently selected traffic layer
	 * 
	 */
	protected void DeleteCurrentTrafficLayer() {
		//Get the selected traffic layer
		DefaultMutableTreeNode selectedLayerNode = (DefaultMutableTreeNode) treeLayers.getSelectionPath().getLastPathComponent();
		TrafficDefinitionLayer selectedLayer = (TrafficDefinitionLayer) selectedLayerNode.getUserObject();

		if (JOptionPane.showConfirmDialog(this, "Delete layer " + selectedLayer.getName() + " ?") == JOptionPane.YES_OPTION) {
			// Delete the layer
			project.DeleteLayer((TrafficDefinitionLayer) selectedLayerNode.getUserObject());

			// If this was the last traffic layer add a new layer to the project
			if (selectedLayerNode.getSiblingCount() == 1) {
				JOptionPane.showMessageDialog(this, "The project must have at least one traffic layer. An empty layer will be inserted to the project.");

				CreateNewTrafficLayer(TrafficDefinitionLayerType.UserDefined);
			}

			// Remove the layer node from the tree
			DefaultMutableTreeNode layersNode = (DefaultMutableTreeNode) ((DefaultMutableTreeNode) treeLayers.getModel().getRoot()).getChildAt(1);

			layersNode.remove(selectedLayerNode);

			// Select the first layer
			treeLayers.setSelectionPath(new TreePath(((DefaultMutableTreeNode) layersNode.getFirstChild()).getPath()));

			treeLayers.updateUI();
		}
	}

	/**
	 * Creates a new traffic layer that is a copy of the currently selected one
	 * 
	 */
	protected void DuplicateCurrentTrafficLayer() {
		DefaultMutableTreeNode selectedLayerNode = (DefaultMutableTreeNode) treeLayers.getSelectionPath().getLastPathComponent();

		TrafficDefinitionLayer newLayer = project.createDuplicateLayer((TrafficDefinitionLayer) selectedLayerNode.getUserObject());

		DefaultMutableTreeNode newLayerNode = new DefaultMutableTreeNode(newLayer);

		DefaultMutableTreeNode layersNode = (DefaultMutableTreeNode) ((DefaultMutableTreeNode) treeLayers.getModel().getRoot()).getChildAt(1);

		layersNode.add(newLayerNode);

		treeLayers.setSelectionPath(new TreePath(newLayerNode.getPath()));

		treeLayers.updateUI();
	}

} // @jve:decl-index=0:visual-constraint="10,10"
