package application;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.UUID;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.AnchorPane;
import javafx.scene.shape.Circle;

public class DraggableNode extends AnchorPane {
		
		@FXML private AnchorPane root_pane;
		@FXML private AnchorPane left_link_handle;
		@FXML private AnchorPane right_link_handle;
		@FXML private Label title_bar;
		@FXML private Label close_button;
		
		private EventHandler <MouseEvent> mLinkHandleDragDetected;
		private EventHandler <DragEvent> mLinkHandleDragDropped;
		private EventHandler <DragEvent> mContextLinkDragOver;
		private EventHandler <DragEvent> mContextLinkDragDropped;
		
		private EventHandler <DragEvent> mContextDragOver;
		private EventHandler <DragEvent> mContextDragDropped;
		
		private DragIconType mType = null;
		
		private Point2D mDragOffset = new Point2D (0.0, 0.0);
		
		private final DraggableNode self;
		
		private NodeLink mDragLink = null;
		private AnchorPane right_pane = null;

		private final List <String> mLinkIds = new ArrayList <String> ();

		public DraggableNode() {
			
			FXMLLoader fxmlLoader = new FXMLLoader(
					getClass().getResource("/DraggableNode.fxml")
					);
			
			fxmlLoader.setRoot(this); 
			fxmlLoader.setController(this);
			
			self = this;
			
			try { 
				fxmlLoader.load();
	        
			} catch (IOException exception) {
			    throw new RuntimeException(exception);
			}
			//provide a universally unique identifier for this object
			setId(UUID.randomUUID().toString());

		}
		
		@FXML
		private void initialize() {
			
			buildNodeDragHandlers();
			buildLinkDragHandlers();
						
			left_link_handle.setOnDragDetected(mLinkHandleDragDetected);
			right_link_handle.setOnDragDetected(mLinkHandleDragDetected);

			left_link_handle.setOnDragDropped(mLinkHandleDragDropped);
			right_link_handle.setOnDragDropped(mLinkHandleDragDropped);

			mDragLink = new NodeLink();
			mDragLink.setVisible(false);
			
			parentProperty().addListener(new ChangeListener() {

				@Override
				public void changed(ObservableValue observable,
						Object oldValue, Object newValue) {
					right_pane = (AnchorPane) getParent();
					
				}
				
			});
			
			Circle c =new Circle();
			c.setRadius(5.0f);
			this.getChildren().add(c);

		}

		public void registerLink(String linkId) {
			mLinkIds.add(linkId);
		}

		public void relocateToPoint (Point2D p) {

			//relocates the object to a point that has been converted to
			//scene coordinates
			Point2D localCoords = getParent().sceneToLocal(p);
			
			relocate ( 
					(int) (localCoords.getX() - mDragOffset.getX()),
					(int) (localCoords.getY() - mDragOffset.getY())
				);
		}
		
		public DragIconType getType () { return mType; }
		
		public void setType (DragIconType type) {
			
			mType = type;
			
			getStyleClass().clear();
			getStyleClass().add("dragicon");
			
			switch (mType) {
			
			case blue:
				getStyleClass().add("icon-blue");
			break;

			case red:
				getStyleClass().add("icon-red");			
			break;

			case green:
				getStyleClass().add("icon-green");
			break;

			case grey:
				getStyleClass().add("icon-grey");
			break;

			case purple:
				getStyleClass().add("icon-purple");
			break;

			case yellow:
				getStyleClass().add("icon-yellow");
			break;

			case black:
				getStyleClass().add("icon-black");
			break;
			
			default:
			break;
			}
		}
		
		public void buildNodeDragHandlers() {
			
			mContextDragOver = new EventHandler <DragEvent>() {

				//dragover to handle node dragging in the right pane view
				@Override
				public void handle(DragEvent event) {		
			
					event.acceptTransferModes(TransferMode.ANY);				
					relocateToPoint(new Point2dSerial( event.getSceneX(), event.getSceneY()));

					event.consume();
				}
			};
			
			//dragdrop for node dragging
			mContextDragDropped = new EventHandler <DragEvent> () {
		
				@Override
				public void handle(DragEvent event) {
				
					getParent().setOnDragOver(null);
					getParent().setOnDragDropped(null);
					
					event.setDropCompleted(true);
					
					event.consume();
				}
			};
			
			//close button click
			close_button.setOnMouseClicked( new EventHandler <MouseEvent> () {

				@Override
				public void handle(MouseEvent event) {
					AnchorPane parent  = (AnchorPane) self.getParent();
					parent.getChildren().remove(self);

					//iterate each link id connected to this node
					//find it's corresponding component in the right-hand
					//AnchorPane and delete it.
					
					//Note:  other nodes connected to these links are not being
					//notified that the link has been removed.
					for (ListIterator <String> iterId = mLinkIds.listIterator();
							iterId.hasNext();) {
						
						String id = iterId.next();

						for (ListIterator <Node> iterNode = parent.getChildren().listIterator();
								iterNode.hasNext();) {
							
							Node node = iterNode.next();
							
							if (node.getId() == null)
								continue;
						
							if (node.getId().equals(id))
								iterNode.remove();
						}
						
						iterId.remove();
					}
				}
				
			});
			
			//drag detection for node dragging
			title_bar.setOnDragDetected ( new EventHandler <MouseEvent> () {

				@Override
				public void handle(MouseEvent event) {

					getParent().setOnDragOver(null);
					getParent().setOnDragDropped(null);

					getParent().setOnDragOver (mContextDragOver);
					getParent().setOnDragDropped (mContextDragDropped);

	                //begin drag ops
	                mDragOffset = new Point2D(event.getX(), event.getY());
	                
	                relocateToPoint(
	                		new Point2D(event.getSceneX(), event.getSceneY())
	                		);
	                
	                ClipboardContent content = new ClipboardContent();
					DragContainer container = new DragContainer();
					
					container.addData ("type", mType.toString());
					content.put(DragContainer.AddNode, container);
					
	                startDragAndDrop (TransferMode.ANY).setContent(content);                
	                
	                event.consume();					
				}
				
			});		
		}
		
		private void buildLinkDragHandlers() {
			
			mLinkHandleDragDetected = new EventHandler <MouseEvent> () {

				@Override
				public void handle(MouseEvent event) {
					
					getParent().setOnDragOver(null);
					getParent().setOnDragDropped(null);
					
					getParent().setOnDragOver(mContextLinkDragOver);
					getParent().setOnDragDropped(mContextLinkDragDropped);
					
					//Set up user-draggable link
					right_pane.getChildren().add(0,mDragLink);					
					
					mDragLink.setVisible(false);

					Point2D p = new Point2D(
							getLayoutX() + (getWidth() / 2.0),
							getLayoutY() + (getHeight() / 2.0)
							);

					mDragLink.setStart(p);					
					
					//Drag content code
	                ClipboardContent content = new ClipboardContent();
	                DragContainer container = new DragContainer ();
	                
	                //pass the UUID of the source node for later lookup
	                container.addData("source", getId());

	                content.put(DragContainer.AddLink, container);
					
					startDragAndDrop (TransferMode.ANY).setContent(content);	

					event.consume();
				}
			};

			mLinkHandleDragDropped = new EventHandler <DragEvent> () {

				@Override
				public void handle(DragEvent event) {

					getParent().setOnDragOver(null);
					getParent().setOnDragDropped(null);
										
					//get the drag data.  If it's null, abort.  
					//This isn't the drag event we're looking for.
					DragContainer container = 
							(DragContainer) event.getDragboard().getContent(DragContainer.AddLink);
								
					if (container == null)
						return;

					//hide the draggable NodeLink and remove it from the right-hand AnchorPane's children
					mDragLink.setVisible(false);
					right_pane.getChildren().remove(0);
					
					AnchorPane link_handle = (AnchorPane) event.getSource();
					
					ClipboardContent content = new ClipboardContent();
					
					//pass the UUID of the target node for later lookup
					container.addData("target", getId());
					
					content.put(DragContainer.AddLink, container);
					
					event.getDragboard().setContent(content);
					event.setDropCompleted(true);
					event.consume();				
				}
			};

			mContextLinkDragOver = new EventHandler <DragEvent> () {

				@Override
				public void handle(DragEvent event) {
					event.acceptTransferModes(TransferMode.ANY);
					
					//Relocate end of user-draggable link
					if (!mDragLink.isVisible())
						mDragLink.setVisible(true);
					
					mDragLink.setEnd(new Point2D(event.getX(), event.getY()));
					
					event.consume();
					
				}
			};

			//drop event for link creation
			mContextLinkDragDropped = new EventHandler <DragEvent> () {

				@Override
				public void handle(DragEvent event) {
					System.out.println("context link drag dropped");
					
					getParent().setOnDragOver(null);
					getParent().setOnDragDropped(null);

					//hide the draggable NodeLink and remove it from the right-hand AnchorPane's children
					mDragLink.setVisible(false);
					right_pane.getChildren().remove(0);
					
					event.setDropCompleted(true);
					event.consume();
				}
				
			};
			
		}
		
		
}