/*
 * 
 *   Copyright 2016 RIFT.IO Inc
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */
/**
 * Draw edges by dragging a VirtualNetworkFunctionConnectionPoint to VNFD, VLD or another VirtualNetworkFunctionConnectionPoint.
 *
 * If VLD does not exist between two VNFDs then add one.
 *
 * CSS for this class is defined in DescriptorGraph.scss.
 *
 */

import d3 from 'd3'
import CatalogItemsActions from '../../actions/CatalogItemsActions'
import SelectionManager from '../SelectionManager'
import DescriptorModelFactory from '../model/DescriptorModelFactory'

const line = d3.svg.line()
	.x(d => {
		return d.x;
	})
	.y(d => {
		return d.y;
	});

function mouseWithinPosition(position, mouseCoordinates, scale = 1) {
	const x = mouseCoordinates[0] / scale;
	const y = mouseCoordinates[1] / scale;
	const withinXBoundary = position.left < x && position.right > x;
	const withinYBoundary = position.top < y && position.bottom > y;
	return withinXBoundary && withinYBoundary;
}

function getContainerUnderMouse(comp, element, scale) {
	const mouseCoordinates = d3.mouse(element);
	return comp.descriptorsAndConnectionPoints().filter(d => {
		return DescriptorModelFactory.isConnectionPoint(d) ||
			DescriptorModelFactory.isInternalConnectionPoint(d) ||
			DescriptorModelFactory.isVirtualLink(d) ||
			DescriptorModelFactory.isInternalVirtualLink(d);
	}).filter(d => mouseWithinPosition(d.position, mouseCoordinates, scale));
}

export default class DescriptorGraphPathBuilder {

	constructor(graph) {
		this.graph = graph;
		this.containers = [];
	}

	descriptorsAndConnectionPoints() {
		return this.graph.g.selectAll('.descriptor, .connector');
	}

	descriptors() {
		return this.graph.containersGroup.selectAll('.descriptor');
	}

	connectionPoints() {
		return this.graph.connectorsGroup.selectAll('.connector');
	}

	paths() {
		return this.graph.g.selectAll('.connection');
	}

	addContainers(containers) {
		this.containers = this.containers.concat(containers);
	}

	static addConnection(srcConnector, dstConnector) {

		// return true on success; falsy otherwise to allow the caller to clean up accordingly

		if (!(srcConnector || dstConnector)) {
			return false;
		}

		if (srcConnector.canConnectTo && srcConnector.canConnectTo(dstConnector)) {

			const root = srcConnector.getRoot();

			if (DescriptorModelFactory.isVirtualLink(dstConnector) || DescriptorModelFactory.isInternalVirtualLink(dstConnector)) {
				dstConnector.addConnectionPoint(srcConnector);
			} else {
				if (root) {
					const vld = root.createVld();
					root.removeAnyConnectionsForConnector(srcConnector);
					root.removeAnyConnectionsForConnector(dstConnector);
					vld.addConnectionPoint(srcConnector);
					vld.addConnectionPoint(dstConnector);
				}
			}

			// notify catalog items have changed to force a redraw and update state accordingly
			CatalogItemsActions.catalogItemDescriptorChanged(root);

			return true;

		}

	}

	static addConnectionToVLD(srcSelection, dstSelection) {

		if (srcSelection[0].length === 0 || dstSelection[0].length === 0) {
			return false;
		}

		const dstVirtualLink = dstSelection.datum();
		const srcConnectionPoint = srcSelection.datum();

		dstVirtualLink.addConnectionPoint(srcConnectionPoint);

		// notify catalog items have changed to force a redraw and update state accordingly
		CatalogItemsActions.catalogItemDescriptorChanged(dstVirtualLink.getRoot());

		return true;

	}

	render() {

		/*
			Strategy: compare mouse position with the position of all the selectable elements.
			On mouse down:
			    determine if there is a connection point under the mouse (the source)
			    determine if there is already a path connected on this source
			On mouse move:
			    draw a tracer line from the source to the mouse
			On mouse up:
			    determine if there is a connection point or VLD under the mouse (the destination)
			    take the respective action and notify
		 */

		const comp = this;
		const drawLine = line.interpolate('basis');

		comp.boundMouseDownHandler = function (d) {

			let hasInitializedMouseMoveHandler = false;

			const srcConnectionPoint = comp.connectionPoints().filter(d => {
				return DescriptorModelFactory.isConnectionPoint(d) || DescriptorModelFactory.isInternalConnectionPoint(d);
			}).filter(d => mouseWithinPosition(d.position, d3.mouse(this), comp.graph.scale));

			if (srcConnectionPoint[0].length) {

				const src = srcConnectionPoint.datum() || {};

				// determine if there is already a path on this connection point
				// if there is then hide it; the mouseup handler will clean up
				const existingPath = comp.paths().filter(d => {
					return d && d.parent && d.parent.type === 'vld' && d.key === src.key;
				});

				// create a new path to follow the mouse
				const path = comp.graph.paths.selectAll('.new-connection').data([srcConnectionPoint.datum()], DescriptorModelFactory.containerIdentity);
				path.enter().append('path').classed('new-connection', true);

				comp.boundMouseMoveHandler = function () {

					const mouseCoordinates = d3.mouse(this);

					path.attr({
						fill: 'red',
						stroke: 'red',
						'stroke-width': '4px',
						d: d => {
							const srcPosition = d.position.centerPoint();
							const dstPosition = {
								x: mouseCoordinates[0] / comp.graph.scale,
								y: mouseCoordinates[1] / comp.graph.scale
							};
							return drawLine([srcPosition, dstPosition]);
						}
					});

					if (!hasInitializedMouseMoveHandler) {

						hasInitializedMouseMoveHandler = true;

						SelectionManager.removeOutline();

						existingPath.style({
							opacity: 0
						});

						// allow for visual treatment of this drag operation
						srcConnectionPoint.classed('-selected', true);

						// allow for visual treatment of this drag operation
						comp.graph.svg.classed('-is-dragging-connection-point', true);

						// identify which descriptors are a valid drop target
						comp.descriptors().filter(d => {
							const validTarget = src.canConnectTo && src.canConnectTo(d);
							return validTarget;
						}).classed('-is-valid-drop-target', true);

						// identify which connection points are a valid drop target
						comp.connectionPoints().filter(d => {
							const validTarget = src.canConnectTo && src.canConnectTo(d);
							return validTarget;
						}).classed('-is-valid-drop-target', true);

					}

					const validDropTarget = getContainerUnderMouse(comp, this, comp.graph.scale);
					comp.graph.g.selectAll('.-is-drag-over').classed('-is-drag-over', false);
					SelectionManager.removeOutline();
					if (validDropTarget) {
						validDropTarget
							.filter(d => src.canConnectTo && src.canConnectTo(d))
							.classed('-is-drag-over', true)
							.each(function () {
								// warn must be a function so 'this' will = the path element
								SelectionManager.outlineSvg(this);
							});
					}

				};

				// determine what the interaction is and do it
				comp.boundMouseUpHandler = function () {

					// remove these handlers so they start fresh on the next drag operation
					comp.graph.svg
						.on('mouseup.edgeBuilder', null)
						.on('mousemove.edgeBuilder', null);

					// remove visual treatments
					comp.graph.svg.classed('-is-dragging-connection-point', false);
					comp.descriptors().classed('-is-valid-drop-target', false);
					comp.connectionPoints().classed('-is-valid-drop-target', false);
					comp.graph.g.selectAll('.-is-drag-over').classed('-is-drag-over', false);

					const dstSelection = getContainerUnderMouse(comp, this, comp.graph.scale);

					// determine if there is a connection point
					if (dstSelection[0].length) {
						if (DescriptorGraphPathBuilder.addConnection(src, dstSelection.datum())) {
							existingPath.remove();
						}
					}

					// if we hid an existing path restore it
					existingPath.style({
						opacity: null
					});

					// remove the tracer path
					path.remove();

					SelectionManager.refreshOutline();

				};

				// init drag handlers
				comp.graph.svg
					.on('mouseup.edgeBuilder', comp.boundMouseUpHandler)
					.on('mousemove.edgeBuilder', comp.boundMouseMoveHandler);
			}

		};

		// enable dragging features
		comp.graph.svg
			.on('mousedown.edgeBuilder', comp.boundMouseDownHandler);

	}

}
