
import * as THREE from "../../libs/three.js/build/three.module.js";
import {Annotation} from "../Annotation.js";
import {Measure} from "../utils/Measure.js";
import {CameraAnimation} from "../modules/CameraAnimation/CameraAnimation.js";
import {Utils} from "../utils.js";
import {PointSizeType} from "../defines.js";

function loadPointCloud(viewer, data){

	let loadMaterial = (target) => {

		if(data.material){

			if(data.material.activeAttributeName != null){
				target.activeAttributeName = data.material.activeAttributeName;
			}

			if(data.material.ranges != null){
				for(let range of data.material.ranges){

					if(range.name === "elevationRange"){
						target.elevationRange = range.value;
					}else if(range.name === "intensityRange"){
						target.intensityRange = range.value;
					}else{
						target.setRange(range.name, range.value);
					}

				}
			}

			if(data.material.size != null){
				target.size = data.material.size;
			}

			if(data.material.minSize != null){
				target.minSize = data.material.minSize;
			}

			if(data.material.pointSizeType != null){
				target.pointSizeType = PointSizeType[data.material.pointSizeType];
			}

			if(data.material.matcap != null){
				target.matcap = data.material.matcap;
			}

		}else if(data.activeAttributeName != null){
			target.activeAttributeName = data.activeAttributeName;
		}else{
			// no material data
		}

	};

	const promise = new Promise((resolve) => {

		const names = viewer.scene.pointclouds.map(p => p.name);
		const alreadyExists = names.includes(data.name);

		if(alreadyExists){
			resolve();
			return;
		}

		Potree.loadPointCloud(data.url, data.name, (e) => {
			const {pointcloud} = e;

			pointcloud.position.set(...data.position);
			pointcloud.rotation.set(...data.rotation);
			pointcloud.scale.set(...data.scale);

			loadMaterial(pointcloud.material);

			viewer.scene.addPointCloud(pointcloud);

			resolve(pointcloud);
		});
	});

	return promise;
}

function loadMeasurement(viewer, data){

	const duplicate = viewer.scene.measurements.find(measure => measure.uuid === data.uuid);
	if(duplicate){
		return;
	}

	const measure = new Measure();

	measure.uuid = data.uuid;
	measure.name = data.name;
	measure.showDistances = data.showDistances;
	measure.showCoordinates = data.showCoordinates;
	measure.showArea = data.showArea;
	measure.closed = data.closed;
	measure.showAngles = data.showAngles;
	measure.showHeight = data.showHeight;
	measure.showCircle = data.showCircle;
	measure.showAzimuth = data.showAzimuth;
	measure.showEdges = data.showEdges;
	// color

	for(const point of data.points){
		const pos = new THREE.Vector3(...point);
		measure.addMarker(pos);
	}

	viewer.scene.addMeasurement(measure);

}

function loadVolume(viewer, data){

	const duplicate = viewer.scene.volumes.find(volume => volume.uuid === data.uuid);
	if(duplicate){
		return;
	}

	let volume = new Potree[data.type];

	volume.uuid = data.uuid;
	volume.name = data.name;
	volume.position.set(...data.position);
	volume.rotation.set(...data.rotation);
	volume.scale.set(...data.scale);
	volume.visible = data.visible;
	volume.clip = data.clip;

	viewer.scene.addVolume(volume);
}

function loadCameraAnimation(viewer, data){

	const duplicate = viewer.scene.cameraAnimations.find(a => a.uuid === data.uuid);
	if(duplicate){
		return;
	}

	const animation = new CameraAnimation(viewer);

	animation.uuid = data.uuid;
	animation.name = data.name;
	animation.duration = data.duration;
	animation.t = data.t;
	animation.curveType = data.curveType;
	animation.visible = data.visible;
	animation.controlPoints = [];

	for(const cpdata of data.controlPoints){
		const cp = animation.createControlPoint();

		cp.position.set(...cpdata.position);
		cp.target.set(...cpdata.target);
	}

	viewer.scene.addCameraAnimation(animation);
}

function loadOrientedImages(viewer, images){

	const {cameraParamsPath, imageParamsPath} = images;

	const duplicate = viewer.scene.orientedImages.find(i => i.imageParamsPath === imageParamsPath);
	if(duplicate){
		return;
	}

	Potree.OrientedImageLoader.load(cameraParamsPath, imageParamsPath, viewer).then( images => {
		viewer.scene.addOrientedImages(images);
	});

}

function loadGeopackage(viewer, geopackage){

	const path = geopackage.path;

	const duplicate = viewer.scene.geopackages.find(i => i.path === path);
	if(duplicate){
		return;
	}

	const projection = viewer.getProjection();

	proj4.defs("WGS84", "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs");
	proj4.defs("pointcloud", projection);
	const transform = proj4("WGS84", "pointcloud");
	const params = {
		transform: transform,
	};

	Potree.GeoPackageLoader.loadUrl(path, params).then(data => {
		viewer.scene.addGeopackage(data);
	});
	

}

function loadSettings(viewer, data){
	if(!data){
		return;
	}

	viewer.setPointBudget(data.pointBudget);
	viewer.setFOV(data.fov);
	viewer.setEDLEnabled(data.edlEnabled);
	viewer.setEDLRadius(data.edlRadius);
	viewer.setEDLStrength(data.edlStrength);
	viewer.setBackground(data.background);
	viewer.setMinNodeSize(data.minNodeSize);
	viewer.setShowBoundingBox(data.showBoundingBoxes);
}

function loadView(viewer, view){
	viewer.scene.view.position.set(...view.position);
	viewer.scene.view.lookAt(...view.target);
}

function loadAnnotationItem(item){

	const annotation = new Annotation({
		position: item.position,
		title: item.title,
		cameraPosition: item.cameraPosition,
		cameraTarget: item.cameraTarget,
	});


	annotation.description = item.description;
	annotation.uuid = item.uuid;

	if(item.offset){
		annotation.offset.set(...item.offset);
	}

	return annotation;
}

function loadAnnotations(viewer, data){

	if(!data){
		return;
	}

	const findDuplicate = (item) => {

		let duplicate = null;

		viewer.scene.annotations.traverse( a => {
			if(a.uuid === item.uuid){
				duplicate = a;
			}
		});

		return duplicate;
	};

	const traverse = (item, parent) => {

		const duplicate = findDuplicate(item);
		if(duplicate){
			return;
		}

		const annotation = loadAnnotationItem(item);

		for(const childItem of item.children){
			traverse(childItem, annotation);
		}

		parent.add(annotation);

	};

	for(const item of data){
		traverse(item, viewer.scene.annotations);
	}

}

function loadProfile(viewer, data){
	
	const {name, points} = data;

	const duplicate = viewer.scene.profiles.find(profile => profile.uuid === data.uuid);
	if(duplicate){
		return;
	}

	let profile = new Potree.Profile();
	profile.name = name;
	profile.uuid = data.uuid;

	profile.setWidth(data.width);

	for(const point of points){
		profile.addMarker(new THREE.Vector3(...point));
	}
	
	viewer.scene.addProfile(profile);
}

function loadClassification(viewer, data){
	if(!data){
		return;
	}

	const classifications = data;

	viewer.setClassifications(classifications);
}

export async function loadProject(viewer, data){

	if(data.type !== "Potree"){
		console.error("not a valid Potree project");
		return;
	}

	loadSettings(viewer, data.settings);

	loadView(viewer, data.view);

	const pointcloudPromises = [];
	for(const pointcloud of data.pointclouds){
		const promise = loadPointCloud(viewer, pointcloud);
		pointcloudPromises.push(promise);
	}

	for(const measure of data.measurements){
		loadMeasurement(viewer, measure);
	}

	for(const volume of data.volumes){
		loadVolume(viewer, volume);
	}

	for(const animation of data.cameraAnimations){
		loadCameraAnimation(viewer, animation);
	}

	for(const profile of data.profiles){
		loadProfile(viewer, profile);
	}

	if(data.orientedImages){
		for(const images of data.orientedImages){
			loadOrientedImages(viewer, images);
		}
	}

	loadAnnotations(viewer, data.annotations);

	loadClassification(viewer, data.classification);

	// need to load at least one point cloud that defines the scene projection,
	// before we can load stuff in other projections such as geopackages
	//await Promise.any(pointcloudPromises); // (not yet supported)
	Utils.waitAny(pointcloudPromises).then( () => {
		if(data.geopackages){
			for(const geopackage of data.geopackages){
				loadGeopackage(viewer, geopackage);
			}
		}
	});

	await Promise.all(pointcloudPromises);
}