import * as THREE from 'three';
import TrackballControls from 'three-trackballcontrols';
import ClickerBox from "./ClickerBox";
import {getShapeCoordinates} from "./Shapes";
import {Vector3} from "three";

import {initiateScene} from './AppletControls.js';

const sceneNames = {
	 M : 'sceneM',
	 X : 'sceneX',
	 Y : 'sceneY',
	 Z : 'sceneZ'
}

const uvPointColors = {
	M : 0x000832,
	X : 0x590B64,
	Y : 0x156611,
	Z : 0x7A0C0C
}

var coordinatesMaxMinMain = {};

/**
 * Main function used to initiate DOM elements with lissteners and call other functions.
 * @param resize
 * @returns {{}}
 */
function runMainScript(resize) {


	var buttonCalculate = document.getElementById('buttonCalculate');
	var buttonRestartCamera = document.getElementById('buttonRestartCamera');
	var selectShape = document.getElementById('selectShape');
	var showPoint = document.getElementById("showPoint");

	var sceneCreated = false;
	var clicker = new ClickerBox();

	var scenesCanvases = {};
	const scenes = {};

	scenesCanvases.CanvasM = document.getElementById("scene_m");
	scenesCanvases.CanvasX = document.getElementById("scene_x");
	scenesCanvases.CanvasY = document.getElementById("scene_y");
	scenesCanvases.CanvasZ = document.getElementById("scene_z");

	scenes.sceneObjectsM = initiateScene(scenesCanvases.CanvasM);
	scenes.sceneObjectsX = initiateScene(scenesCanvases.CanvasX);
	scenes.sceneObjectsY = initiateScene(scenesCanvases.CanvasY);
	scenes.sceneObjectsZ = initiateScene(scenesCanvases.CanvasZ);


	// show scene if page is loaded
	setShapeToMatrix(selectShape);
	var coordinates = getControlCoordinates();

	Object.assign(scenes.sceneObjectsM, createScene(scenes.sceneObjectsM.scene, coordinates, sceneNames.M, showPoint.checked, clicker));
	Object.assign(scenes.sceneObjectsX, createScene(scenes.sceneObjectsX.scene, coordinates, sceneNames.X, showPoint.checked, clicker));
	Object.assign(scenes.sceneObjectsY, createScene(scenes.sceneObjectsY.scene, coordinates, sceneNames.Y, showPoint.checked, clicker));
	Object.assign(scenes.sceneObjectsZ, createScene(scenes.sceneObjectsZ.scene, coordinates, sceneNames.Z, showPoint.checked, clicker));

	sceneCreated = true;

	selectShape.addEventListener('change', function(){
		setShapeToMatrix(selectShape);
	});

	buttonCalculate.addEventListener('click', function(){
		coordinates = getControlCoordinates();
		var checkCoordinates = checkControlCoordinates(coordinates);
		if (checkCoordinates == true) {
			sceneCreated = true;
			Object.assign(scenes.sceneObjectsM, createScene(scenes.sceneObjectsM.scene, coordinates, sceneNames.M, showPoint.checked, clicker));
			Object.assign(scenes.sceneObjectsX, createScene(scenes.sceneObjectsX.scene, coordinates, sceneNames.X, showPoint.checked, clicker));
			Object.assign(scenes.sceneObjectsY, createScene(scenes.sceneObjectsY.scene, coordinates, sceneNames.Y, showPoint.checked, clicker));
			Object.assign(scenes.sceneObjectsZ, createScene(scenes.sceneObjectsZ.scene, coordinates, sceneNames.Z, showPoint.checked, clicker));
			if (showPoint.checked == true) addAllUVPoints(scenes, clicker);
			// centerAllCameras(scenes);

		} else if (checkCoordinates == false) {
			clearAllScenes(scenes);
		}
	});

	showPoint.addEventListener('change', function() {
		if ((showPoint.checked == true) && (sceneCreated)) {
			addAllUVPoints(scenes, clicker);
		} else {
			removeAllUVPoints(scenes);
		}
	});

	buttonRestartCamera.addEventListener('click', function(){
		centerAllCameras(scenes);
	});

	window.addEventListener('clicker', function() {
		// console.log(clicker.getValue());
		if ((showPoint.checked == true) && (sceneCreated)) {
			removeAllUVPoints(scenes);
			addAllUVPoints(scenes, clicker);
		}
	});

	return scenes;

}

/**
 * 
 * @param scene
 * @param coordinates
 * @param sceneName
 * @returns {{vector3PlatePoints: [], vectors3ControlPoints: [], coordinatesMaxMin: {minValue: number, minY: number, minX: number, maxZ: number, maxY: number, maxValue: number, maxX: number, minZ: number}}}
 */
function createScene(scene, coordinates, sceneName) {

	clearScene(scene);

	var vectors3ControlPoints = getControlVectors(coordinates);
	var plateVectors = getPlateVectors(vectors3ControlPoints, sceneName);
	var vector3PlatePoints = plateVectors.vector3PlatePoints;
	var coordinatesMaxMin = plateVectors.coordinatesMaxMin;

	if (sceneName == 'sceneM') {
		addPoints(vectors3ControlPoints, scene);
		addControlPointsText(vectors3ControlPoints, scene);
		coordinatesMaxMinMain = plateVectors.coordinatesMaxMin; // TODO check reference to object
	}

	var verticesPlate = getVertices(vector3PlatePoints);

	// addMeshFromVertices(verticesPlate, 0x020080, 0x040040, 0.95, true, scene);n
	// addMeshFromVertices(verticesPlate, 0x277AAC, 0x1B58BB, 0.85, true, scene);
	addMeshFromVertices(verticesPlate, 0x277AAC, 0x277AAC, 0.85, true, scene);
	// addMeshFromVertices(verticesPlate, 0x, 0x040040, 0.95, true, scene);

	addAxis(scene, coordinatesMaxMin, sceneName);

	return {vectors3ControlPoints, vector3PlatePoints, coordinatesMaxMin};

}

/**
 * Function returns THREE.Vector3 representing point of Bezier Plate based on scene name.
 * @param u
 * @param v
 * @param controlVectors
 * @param sceneName
 * @returns {Vector3}
 */
function getPlateVector(u, v, controlVectors, sceneName) {
	var multiplyStep = 10;

	if (sceneName != 'sceneM') {
		var xSize = coordinatesMaxMinMain.maxX - coordinatesMaxMinMain.minX;
		var ySize = coordinatesMaxMinMain.maxY - coordinatesMaxMinMain.minY;
		var zSize = coordinatesMaxMinMain.maxZ - coordinatesMaxMinMain.minZ;
	}

	switch (sceneName) {
		case 'sceneX':
			var x = getCoordinate(u, v, controlVectors, "x");
			var y = u * ySize;
			var z = v * zSize;
			break;
		case 'sceneY':
			var x = u * xSize;
			var y = getCoordinate(u, v, controlVectors, "y");
			var z = v * zSize;
			break;
		case 'sceneZ':
			var x = u * xSize;
			var y = v * ySize;
			var z = getCoordinate(u, v, controlVectors, "z");
			break;
		case 'sceneM':
			var x = getCoordinate(u, v, controlVectors, "x");
			var y = getCoordinate(u, v, controlVectors, "y");
			var z = getCoordinate(u, v, controlVectors, "z");
			break;
	}

	return new THREE.Vector3(x, y, z);
}

/**
 * Function transforms control points coordinates from loaded object to array of THREE.Vector3 objects.
 * @param coordinates
 * @returns {[]}
 */
function getControlVectors(coordinates) {
	var vectors3ControlPoints = [];

	// create array of vector3 objects
	for (let i = 1; i < 17; i++) {
		let vector = new THREE.Vector3(coordinates.x[i], coordinates.y[i], coordinates.z[i],);
		vectors3ControlPoints.push(vector);
	}

	return vectors3ControlPoints;
}

/**
 * Function loads control point values from frontend matrix and returns them as object.
 * @returns {{}}
 */
function getControlCoordinates () {

	var coordinates = {};
	var x = {};
	var y = {};
	var z = {};

	for (let i = 0; i < 16; i++) {
		x[i+1] = document.getElementById(i + '-x').value;
		y[i+1] = document.getElementById(i + '-y').value;
		z[i+1] = document.getElementById(i + '-z').value;
	}

	coordinates['x'] = x;
	coordinates['y'] = y;
	coordinates['z'] = z;

	return coordinates;
}

/**
 * Function adds names to every control point in a scene.
 * @param vectors
 * @param scene
 */
function addControlPointsText(vectors, scene) {
	var stepFromPoint = 0.5;

	for (let i = 0; i < vectors.length; i++) {
		let pointNumber = i+1;
		let x = parseFloat(vectors[i].getComponent(0)) + stepFromPoint;
		let y = parseFloat(vectors[i].getComponent(1)) + stepFromPoint;
		let z = parseFloat(vectors[i].getComponent(2)) + stepFromPoint;
		addText(scene, 'P' + pointNumber, x, y, z);
	}
}

/**
 * Function calls function to calculate every vertex of plate with specific step. Also calculates edge coordinates for
 * plate. Step defines how smooth will be final mesh.
 * @param vectors3ControlPoints
 * @param sceneName
 * @returns {{vector3PlatePoints: [], coordinatesMaxMin: {minValue: number, minY: number, minX: number, maxZ: number, maxY: number, maxValue: number, maxX: number, minZ: number}}}
 */
function getPlateVectors (vectors3ControlPoints, sceneName) {

	// set steps for calculating vectors
	const step = parseFloat('0.01');
	const multiplyStep = parseFloat('10');

	var vector3PlatePoints = [];

	var coordinatesMaxMin = {
		maxX: 0,
		minX: 0,
		maxY: 0,
		minY: 0,
		maxZ: 0,
		minZ: 0,
		maxValue: 0,
		minValue: 0
	}

	for (var i = 0.0; i <= 1.0; i = parseFloat((i + step).toFixed(2))) {
		var vector3PlatePointsTMP = [];
		for (var j = 0.0; j <= 1.0; j = parseFloat((j + step).toFixed(2))) {

			let platePointVector = getPlateVector(i, j, vectors3ControlPoints, sceneName);

			if (platePointVector.getComponent(0) > coordinatesMaxMin.maxX) coordinatesMaxMin.maxX = platePointVector.getComponent(0);
			if (platePointVector.getComponent(0) < coordinatesMaxMin.minX) coordinatesMaxMin.minX = platePointVector.getComponent(0);

			if (platePointVector.getComponent(1) > coordinatesMaxMin.maxY) coordinatesMaxMin.maxY = platePointVector.getComponent(1);
			if (platePointVector.getComponent(1) < coordinatesMaxMin.minY) coordinatesMaxMin.minY = platePointVector.getComponent(1);

			if (platePointVector.getComponent(2) > coordinatesMaxMin.maxZ) coordinatesMaxMin.maxZ = platePointVector.getComponent(2);
			if (platePointVector.getComponent(2) < coordinatesMaxMin.minZ) coordinatesMaxMin.minZ = platePointVector.getComponent(2);


			vector3PlatePointsTMP.push(platePointVector);

		}
		vector3PlatePoints.push(vector3PlatePointsTMP);
	}

	if (coordinatesMaxMin.maxX > coordinatesMaxMin.maxY) {
		coordinatesMaxMin.maxValue = coordinatesMaxMin.maxX;
	} else {
		coordinatesMaxMin.maxValue = coordinatesMaxMin.maxY;
	}
	if (coordinatesMaxMin.maxValue < coordinatesMaxMin.maxZ) {
		coordinatesMaxMin.maxValue = coordinatesMaxMin.maxZ;
	}

	if (coordinatesMaxMin.minX < coordinatesMaxMin.minY) {
		coordinatesMaxMin.minValue = coordinatesMaxMin.minX;
	} else {
		coordinatesMaxMin.minValue = coordinatesMaxMin.minY;
	}
	if (coordinatesMaxMin.minValue > coordinatesMaxMin.minZ) {
		coordinatesMaxMin.minValue = coordinatesMaxMin.minZ;
	}



	return {vector3PlatePoints, coordinatesMaxMin};
}

/**
 * Function transfers array of THREE.Vector3 objects to 2 arrays for 2 meshes which can be accepted by constructor
 * of Buffer Geometry.
 * @param vector3PlatePoints
 * @returns {{verticesA: [], verticesB: []}}
 */
function getVertices(vector3PlatePoints) {

	var verticesA = [];
	var verticesB = [];

	for (let i = 0; i < vector3PlatePoints.length - 1; i++) {
		for (let j = 0; j < vector3PlatePoints[i].length - 1; j++) {

			verticesA.push(vector3PlatePoints[i][j].x);
			verticesA.push(vector3PlatePoints[i][j].y);
			verticesA.push(vector3PlatePoints[i][j].z);

			verticesA.push(vector3PlatePoints[i + 1][j].x);
			verticesA.push(vector3PlatePoints[i + 1][j].y);
			verticesA.push(vector3PlatePoints[i + 1][j].z);

			verticesA.push(vector3PlatePoints[i][j + 1].x);
			verticesA.push(vector3PlatePoints[i][j + 1].y);
			verticesA.push(vector3PlatePoints[i][j + 1].z);


			verticesB.push(vector3PlatePoints[i + 1][j + 1].x);
			verticesB.push(vector3PlatePoints[i + 1][j + 1].y);
			verticesB.push(vector3PlatePoints[i + 1][j + 1].z);

			verticesB.push(vector3PlatePoints[i + 1][j].x);
			verticesB.push(vector3PlatePoints[i + 1][j].y);
			verticesB.push(vector3PlatePoints[i + 1][j].z);

			verticesB.push(vector3PlatePoints[i][j + 1].x);
			verticesB.push(vector3PlatePoints[i][j + 1].y);
			verticesB.push(vector3PlatePoints[i][j + 1].z);


		}
	}

	return {verticesA, verticesB};

}

/**
 * Main function for adding axis to scene. Calls functions for calculating values, names, position of axis squares
 * and adds whole axis to scene.
 * @param scene
 * @param coordinatesMaxMin
 * @param sceneName
 */
function addAxis(scene, coordinatesMaxMin, sceneName) {

	let stepAxisName = -1;
	let stepAxisNameM = 2;

	const materialLine = new THREE.LineBasicMaterial( { color: 0x000000 } );

	const axisStart = new THREE.Vector3(coordinatesMaxMin.minX, coordinatesMaxMin.minY, coordinatesMaxMin.minZ);

	const pointsX = [axisStart];
	const pointsY = [axisStart];
	const pointsZ = [axisStart];

	pointsX.push(new THREE.Vector3(coordinatesMaxMin.maxX, coordinatesMaxMin.minY, coordinatesMaxMin.minZ));
	pointsY.push(new THREE.Vector3(coordinatesMaxMin.minX, coordinatesMaxMin.maxY, coordinatesMaxMin.minZ));
	pointsZ.push(new THREE.Vector3(coordinatesMaxMin.minX, coordinatesMaxMin.minY, coordinatesMaxMin.maxZ));

	const geometryXLine = new THREE.BufferGeometry().setFromPoints( pointsX );
	const geometryYLine = new THREE.BufferGeometry().setFromPoints( pointsY );
	const geometryZLine = new THREE.BufferGeometry().setFromPoints( pointsZ );

	const lineX = new THREE.Line( geometryXLine, materialLine );
	const lineY = new THREE.Line( geometryYLine, materialLine );
	const lineZ = new THREE.Line( geometryZLine, materialLine );

	scene.add(lineX);
	scene.add(lineY);
	scene.add(lineZ);

	var axisValues = getAxisTextValues(coordinatesMaxMin, sceneName);

	let axisOriginText = coordinatesMaxMin.minX.toFixed(2).toString() + ', ' + coordinatesMaxMin.minY.toFixed(2).toString() + ', ' + coordinatesMaxMin.minZ.toFixed(2).toString();
	addText(scene, axisOriginText, coordinatesMaxMin.minX + stepAxisName, coordinatesMaxMin.minY + stepAxisName, coordinatesMaxMin.minZ + stepAxisName);
	addText(scene, axisValues.x, coordinatesMaxMin.maxX, coordinatesMaxMin.minY + stepAxisName, coordinatesMaxMin.minZ + stepAxisName);
	addText(scene, axisValues.y, coordinatesMaxMin.minX + stepAxisName, coordinatesMaxMin.maxY, coordinatesMaxMin.minZ + stepAxisName);
	addText(scene, axisValues.z, coordinatesMaxMin.minX + stepAxisName, coordinatesMaxMin.minY + stepAxisName, coordinatesMaxMin.maxZ);

	addAxisSquares(coordinatesMaxMin, scene);
	var axisNames = getAxisName(sceneName);

	addText(scene, axisNames.x, coordinatesMaxMin.minX + stepAxisNameM, coordinatesMaxMin.minY + stepAxisName, coordinatesMaxMin.minZ + stepAxisName);
	addText(scene, axisNames.y, coordinatesMaxMin.minX + stepAxisName, coordinatesMaxMin.minY + stepAxisNameM, coordinatesMaxMin.minZ + stepAxisName);
	addText(scene, axisNames.z, coordinatesMaxMin.minX + stepAxisName, coordinatesMaxMin.minY + stepAxisName, coordinatesMaxMin.minZ + stepAxisNameM);

}

/**
 * Function calculates vectors for every axis square and adds this squares to scene.
 * @param coordinatesMaxMin
 * @param scene
 */
function addAxisSquares(coordinatesMaxMin, scene) {

	const originCoordinate = new THREE.Vector3(coordinatesMaxMin.minX, coordinatesMaxMin.minY, coordinatesMaxMin.minZ);

	const pointsXY = [[],[]];
	const pointsXZ = [[],[]];
	const pointsYZ = [[],[]];

	pointsXY[0].push(new THREE.Vector3(coordinatesMaxMin.minX, coordinatesMaxMin.minY, coordinatesMaxMin.minZ));
	pointsXY[0].push(new THREE.Vector3(coordinatesMaxMin.minX, coordinatesMaxMin.maxY, coordinatesMaxMin.minZ));
	pointsXY[1].push(new THREE.Vector3(coordinatesMaxMin.maxX, coordinatesMaxMin.minY, coordinatesMaxMin.minZ));
	pointsXY[1].push(new THREE.Vector3(coordinatesMaxMin.maxX, coordinatesMaxMin.maxY, coordinatesMaxMin.minZ));

	pointsXZ[0].push(new THREE.Vector3(coordinatesMaxMin.minX, coordinatesMaxMin.minY, coordinatesMaxMin.minZ));
	pointsXZ[0].push(new THREE.Vector3(coordinatesMaxMin.minX, coordinatesMaxMin.minY, coordinatesMaxMin.maxZ));
	pointsXZ[1].push(new THREE.Vector3(coordinatesMaxMin.maxX, coordinatesMaxMin.minY, coordinatesMaxMin.minZ));
	pointsXZ[1].push(new THREE.Vector3(coordinatesMaxMin.maxX, coordinatesMaxMin.minY, coordinatesMaxMin.maxZ));

	pointsYZ[0].push(new THREE.Vector3(coordinatesMaxMin.minX, coordinatesMaxMin.minY, coordinatesMaxMin.minZ));
	pointsYZ[0].push(new THREE.Vector3(coordinatesMaxMin.minX, coordinatesMaxMin.minY, coordinatesMaxMin.maxZ));
	pointsYZ[1].push(new THREE.Vector3(coordinatesMaxMin.minX, coordinatesMaxMin.maxY, coordinatesMaxMin.minZ));
	pointsYZ[1].push(new THREE.Vector3(coordinatesMaxMin.minX, coordinatesMaxMin.maxY, coordinatesMaxMin.maxZ));

	var verticesXY = getVertices(pointsXY);
	var verticesXZ = getVertices(pointsXZ);
	var verticesYZ = getVertices(pointsYZ);
	addMeshFromVertices(verticesXY, 'black', 'black', 0.1, true, scene);
	addMeshFromVertices(verticesXZ, 'black', 'black', 0.1, true, scene);
	addMeshFromVertices(verticesYZ, 'black', 'black', 0.1, true, scene);

}

/**
 * Function returns object with values for every axis in a scene based on scene name and edge coordinates.
 * @param coordinatesMaxMin
 * @param sceneName
 * @returns {{}}
 */
function getAxisTextValues(coordinatesMaxMin, sceneName) {

	var axisValues = {};

	switch (sceneName) {
		case 'sceneM':
			axisValues.x = coordinatesMaxMin.maxX.toFixed(2).toString();
			axisValues.y = coordinatesMaxMin.maxY.toFixed(2).toString();
			axisValues.z = coordinatesMaxMin.maxZ.toFixed(2).toString();
			break;
		case 'sceneX':
			axisValues.x = coordinatesMaxMin.maxX.toFixed(2).toString();
			axisValues.y = '1';
			axisValues.z = '1';
			break;
		case 'sceneY':
			axisValues.x = '1';
			axisValues.y = coordinatesMaxMin.maxY.toFixed(2).toString();
			axisValues.z = '1';
			break;
		case 'sceneZ':
			axisValues.x = '1';
			axisValues.y = '1';
			axisValues.z = coordinatesMaxMin.maxZ.toFixed(2).toString();
			break;
	}

	return axisValues;
}

/**
 * Function returns object with names of axises based on specific scene name.
 * @param sceneName
 * @returns {{}}
 */
function getAxisName(sceneName) {
	var axisNames = {}

	switch (sceneName) {
		case 'sceneM':
			axisNames.x = 'x';
			axisNames.y = 'y';
			axisNames.z = 'z';
			break;
		case 'sceneX':
			axisNames.x = 'x';
			axisNames.y = 'u';
			axisNames.z = 'v';
			break;
		case 'sceneY':
			axisNames.x = 'u';
			axisNames.y = 'y';
			axisNames.z = 'v';
			break;
		case 'sceneZ':
			axisNames.x = 'u';
			axisNames.y = 'v';
			axisNames.z = 'z';
			break;
	}

	return axisNames;
}

/**
 * Function called to iterate through object of scenes and call addUVPoint function for every scene.
 * @param scenesObjects
 * @param clicker
 */
function addAllUVPoints(scenesObjects, clicker) {

	addUVPoint(scenesObjects.sceneObjectsM.vectors3ControlPoints, scenesObjects.sceneObjectsM.coordinatesMaxMin, sceneNames.M, uvPointColors.M, clicker, scenesObjects.sceneObjectsM.scene);
	addUVPoint(scenesObjects.sceneObjectsX.vectors3ControlPoints, scenesObjects.sceneObjectsX.coordinatesMaxMin, sceneNames.X, uvPointColors.X, clicker, scenesObjects.sceneObjectsX.scene);
	addUVPoint(scenesObjects.sceneObjectsY.vectors3ControlPoints, scenesObjects.sceneObjectsY.coordinatesMaxMin, sceneNames.Y, uvPointColors.Y, clicker, scenesObjects.sceneObjectsY.scene);
	addUVPoint(scenesObjects.sceneObjectsZ.vectors3ControlPoints, scenesObjects.sceneObjectsZ.coordinatesMaxMin, sceneNames.Z, uvPointColors.Z, clicker, scenesObjects.sceneObjectsZ.scene);

}

/**
 * Function called to add UV point to specific scene based on name of scene.
 * @param vectors3ControlPoints
 * @param coordinatesMaxMin
 * @param sceneName
 * @param color
 * @param clicker
 * @param scene
 */
function addUVPoint(vectors3ControlPoints,coordinatesMaxMin, sceneName, color, clicker, scene) {

	var namePrefix = 'uvpoint';

	var vectorUV = getUVPointCoordinates(vectors3ControlPoints, clicker, sceneName);
	addPoint(vectorUV, 10, color, scene, namePrefix + sceneName);

	if (sceneName == sceneNames.M) {
		addPoint(new THREE.Vector3(vectorUV.getComponent(0),coordinatesMaxMin.minY,coordinatesMaxMin.minZ), 10, uvPointColors.X, scene, namePrefix + sceneNames.X);
		addPoint(new THREE.Vector3(coordinatesMaxMin.minX,vectorUV.getComponent(1),coordinatesMaxMin.minZ), 10, uvPointColors.Y, scene, namePrefix + sceneNames.Y);
		addPoint(new THREE.Vector3(coordinatesMaxMin.minX,coordinatesMaxMin.minY,vectorUV.getComponent(2)), 10, uvPointColors.Z, scene, namePrefix + sceneNames.Z);
	}

}

/**
 * Function returns vector which is representing UV point for specific scene based on name of scene.
 * @param vectors3ControlPoints
 * @param clicker
 * @param sceneName
 * @returns {Vector3}
 */
function getUVPointCoordinates(vectors3ControlPoints, clicker, sceneName) {

	var multiplyStep = 10; // TODO add calculation

	var clickerValues = clicker.getValue();

	var u = clickerValues.u;
	var v = clickerValues.v;

	var vector = getPlateVector(u, v, vectors3ControlPoints, sceneName);

	return vector;

}

/**
 * Function called to iterate through object of scenes and call removeUVPoint function for every scene.
 * @param scenesObjects
 */
function removeAllUVPoints(scenesObjects) {

	removeUVPoint(sceneNames.M, scenesObjects.sceneObjectsM.scene);
	removeUVPoint(sceneNames.X, scenesObjects.sceneObjectsM.scene);
	removeUVPoint(sceneNames.Y, scenesObjects.sceneObjectsM.scene);
	removeUVPoint(sceneNames.Z, scenesObjects.sceneObjectsM.scene);
	removeUVPoint(sceneNames.X, scenesObjects.sceneObjectsX.scene);
	removeUVPoint(sceneNames.Y, scenesObjects.sceneObjectsY.scene);
	removeUVPoint(sceneNames.Z, scenesObjects.sceneObjectsZ.scene);

}

/**
 * Function gets UV point from scene based on name of scene and deletes this point.
 * @param sceneName
 * @param scene
 */
function removeUVPoint(sceneName, scene) {

	var namePrefix = 'uvpoint';
	var object = scene.getObjectByName(namePrefix + sceneName);
	scene.remove(object);

}

/**
 * Function called to iterate through object of scenes and call clearScene function for every scene.
 * @param scenesObjects
 */
function clearAllScenes(scenesObjects) {

	clearScene(scenesObjects.sceneObjectsM.scene);
	clearScene(scenesObjects.sceneObjectsX.scene);
	clearScene(scenesObjects.sceneObjectsY.scene);
	clearScene(scenesObjects.sceneObjectsZ.scene);

}

/**
 * Function iterates through every object in scene and deletes them.
 * @param scene
 */
function clearScene (scene) {
	while(scene.children.length > 0){
		scene.remove(scene.children[0]);
	}
}

/**
 * Function called to iterate through object of scenes and call centerCamera function for every scene.
 * @param scenesObjects
 */
function centerAllCameras(scenesObjects) {

	scenesObjects.sceneObjectsM.controls.reset();
	scenesObjects.sceneObjectsX.controls.reset();
	scenesObjects.sceneObjectsY.controls.reset();
	scenesObjects.sceneObjectsZ.controls.reset();

	centerCamera(scenesObjects.sceneObjectsM.camera, scenesObjects.sceneObjectsM.controls, scenesObjects.sceneObjectsM.scene.getObjectByName( "mesh_a" ), scenesObjects.sceneObjectsM.coordinatesMaxMin);
	centerCamera(scenesObjects.sceneObjectsX.camera, scenesObjects.sceneObjectsX.controls, scenesObjects.sceneObjectsX.scene.getObjectByName( "mesh_a" ), scenesObjects.sceneObjectsX.coordinatesMaxMin);
	centerCamera(scenesObjects.sceneObjectsY.camera, scenesObjects.sceneObjectsY.controls, scenesObjects.sceneObjectsY.scene.getObjectByName( "mesh_a" ), scenesObjects.sceneObjectsY.coordinatesMaxMin);
	centerCamera(scenesObjects.sceneObjectsZ.camera, scenesObjects.sceneObjectsZ.controls, scenesObjects.sceneObjectsZ.scene.getObjectByName( "mesh_a" ), scenesObjects.sceneObjectsZ.coordinatesMaxMin);

}

/**
 * Function centers Camera to the middle of coordinates.
 * @param camera
 * @param controls
 * @param object
 * @param coordinatesMaxMin
 */
function centerCamera(camera, controls, object, coordinatesMaxMin) {

	const stepZoom = 1;
	const cameraZoom = (coordinatesMaxMin.maxValue - coordinatesMaxMin.minValue) * stepZoom;

	const position = getCenterPoint(object);

	const x = (position.getComponent(0));
	const y = (position.getComponent(1));
	const z = (position.getComponent(2));

	camera.position.x = cameraZoom;
	camera.position.y = cameraZoom;
	camera.position.z = cameraZoom;
	controls.target.set(x, y, z);

}

/**
 * Function creates mesh from array of Vertices. Two meshes are created for purpose of color difference.
 * @param verticesPlate
 * @param colorA
 * @param colorB
 * @param opacity
 * @param transparentBool
 * @param scene
 * @returns {{meshB: Mesh<BufferGeometry, MeshBasicMaterial>, meshA: Mesh<BufferGeometry, MeshBasicMaterial>}}
 */
function addMeshFromVertices(verticesPlate, colorA, colorB, opacity, transparentBool, scene) {
	const MeshAGeometry = new THREE.BufferGeometry();
	const MeshBGeometry = new THREE.BufferGeometry();
	const MeshAVertices = new Float32Array(verticesPlate.verticesA);
	const MeshBVertices = new Float32Array(verticesPlate.verticesB);
	MeshAGeometry.setAttribute('position', new THREE.BufferAttribute(MeshAVertices, 3));
	MeshBGeometry.setAttribute('position', new THREE.BufferAttribute(MeshBVertices, 3));
	const MeshAMaterial = new THREE.MeshBasicMaterial({color: colorA, opacity: opacity, transparent: transparentBool, side: THREE.DoubleSide});
	const MeshBMaterial = new THREE.MeshBasicMaterial({color: colorB, opacity: opacity, transparent: transparentBool, side: THREE.DoubleSide});
	const meshA = new THREE.Mesh(MeshAGeometry, MeshAMaterial);
	const meshB = new THREE.Mesh(MeshBGeometry, MeshBMaterial);
	meshA.name = 'mesh_a';
	meshB.name = 'mesh_b';

	scene.add(meshA);
	scene.add(meshB);

	return{meshA, meshB}
}

/**
 * Function returns Vector which is center Vector of specific Mesh in a scene, which is passed as parameter.
 * @param mesh
 * @returns {Vector3}
 */
function getCenterPoint(mesh) {
	var geometry = mesh.geometry;
	geometry.computeBoundingBox();
	var center = new THREE.Vector3();
	geometry.boundingBox.getCenter( center );
	mesh.localToWorld( center );
	return center;
}

/**
 * Main function used for calculating point on Bezier Point based on control points.
 * @param u
 * @param v
 * @param vectors
 * @param axisName
 * @returns {number}
 */
function getCoordinate(u, v, vectors, axisName) {

	const M = new THREE.Matrix4();

	M.set(-1, 3, -3, 1,
		3, -6, 3, 0,
		-3, 3, 0, 0,
		1, 0, 0, 0);

	const Mt = new THREE.Matrix4();

	Mt.set(-1, 3, -3, 1,
		3, -6, 3, 0,
		-3, 3, 0, 0,
		1, 0, 0, 0);

	M.transpose();

	const U = new THREE.Vector4(Math.pow(u, 3), Math.pow(u, 2), u, 1);
	const V = new THREE.Vector4(Math.pow(v, 3), Math.pow(v, 2), v, 1);

	const UM = U.applyMatrix4(M);

	const G = new THREE.Matrix4();

	G.set(vectors[0][axisName], vectors[1][axisName], vectors[2][axisName], vectors[3][axisName],
		vectors[4][axisName], vectors[5][axisName], vectors[6][axisName], vectors[7][axisName],
		vectors[8][axisName], vectors[9][axisName], vectors[10][axisName], vectors[11][axisName],
		vectors[12][axisName], vectors[13][axisName], vectors[14][axisName], vectors[15][axisName],);

	const GUM = UM.applyMatrix4(G);

	// const GUMM = GUM.multiply(Mt);
	const GUMM = GUM.applyMatrix4(Mt);

	var GUMMV = multiplyVectors(GUMM.toArray(), V.toArray());

	return GUMMV;

}

/**
 * Function used for scalar multiplying vectors which are passed as array.
 * @param v1
 * @param v2
 * @returns {number}
 */
function multiplyVectors(v1, v2) {

	var result = 0;

	for (let i = 0; i < 4; i++) {
		result += v1[i] * v2[i];
	}

	return result;

}

/**
 * Function used for adding points to scene based on array os points.
 * @param vectors
 * @param scene
 */
function addPoints(vectors, scene){
	for (let i = 0; i < vectors.length; i++) {
		addPoint(vectors[i], 4, 'black', scene);
	}
}

function addPoint(vector ,size, color, scene, name) {
	var dotGeometry = new THREE.Geometry();
	// var dotGeometry = new THREE.SphereGeometry( 1, 10, 10 );
	var dotMaterial = new THREE.PointsMaterial({size: size, sizeAttenuation: false, color: color});
	dotGeometry.vertices.push(vector);
	var dot = new THREE.Points(dotGeometry, dotMaterial);
	if (name) dot.name = name;
	scene.add(dot);
}

/**
 * Function used to set values from specific predefined shape to frontend matrix of control points.
 * @param selectShape
 */
function setShapeToMatrix(selectShape) {

	var selectValue = selectShape.value;

	// document.getElementById('0-x').value;

	var shapeCoordinates = getShapeCoordinates(selectValue);

	for (let i = 0; i < 16; i++) {
		document.getElementById(i + '-x').value = shapeCoordinates.x[i+1];
		document.getElementById(i + '-y').value = shapeCoordinates.y[i+1];
		document.getElementById(i + '-z').value = shapeCoordinates.z[i+1];
	}

}

/**
 * Function for checking control points set to frontend matrix. Function alerts which values are missing, if any is
 * missing. Function returns true if every value of every point is set and false if some is not set.
 * @param coordinates
 * @returns {boolean}
 */
function checkControlCoordinates(coordinates) {

	var alertMessage = 'Chybi hodnoty pro body ';
	var doAlert = false;
	for (let i = 0; i < 16; i++) {

		if (coordinates.x[i+1] == '') {
			alertMessage += ' P' + i + '-x';
			doAlert = true;
		}
		if (coordinates.y[i+1] == '') {
			alertMessage += ' P' + i + '-y';
			doAlert = true;
		}
		if (coordinates.z[i+1] == '') {
			alertMessage += ' P' + i + '-z';
			doAlert = true;
		}

	}

	if (doAlert == true) {
		alert(alertMessage);
		return false;
	}

	return true;

}

/**
 * Function for creating sprite text in a scene which is passed as a parameter along with text and position for text.
 * @param scene
 * @param text
 * @param posX
 * @param posY
 * @param posZ
 */
function addText(scene, text, posX, posY, posZ) {

	var	actualFontSize = 0.5;

	var canvas = document.createElement('canvas');
	var context = canvas.getContext('2d');

	var metrics = context.measureText(text);
	var textWidth = metrics.width;
	var	textHeight = 10;
	canvas.width = textWidth;
	canvas.height = textHeight;

	context.fillStyle = '#000000';
	context.font = 'normal ' + textHeight + 'px Arial';
	context.textAlign = 'center';
	context.textBaseline = 'middle';
	context.fillStyle = '#000000';
	context.fillText(text, textWidth / 2, textHeight / 2);

	var texture = new THREE.Texture(canvas);
	texture.needsUpdate = true;
	var material = new THREE.SpriteMaterial({ map: texture });
	var sprite = new THREE.Sprite( material );

	var textObject = new THREE.Object3D();
	textObject.textHeight = actualFontSize;
	textObject.textWidth = (textWidth / textHeight) * textObject.textHeight;
	sprite.scale.set(textWidth / textHeight * actualFontSize, actualFontSize, 1);
	sprite.position.set(posX, posY, posZ);

	textObject.add(sprite);

	scene.add(textObject);

}



export default runMainScript;