import { I3DMLoaderBase } from '../base/I3DMLoaderBase.js';
import { DefaultLoadingManager, Matrix4, InstancedMesh, Vector3, Quaternion } from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';

const tempPos = new Vector3();
const tempQuat = new Quaternion();
const tempSca = new Vector3();
const tempMat = new Matrix4();
export class I3DMLoader extends I3DMLoaderBase {

	constructor( manager = DefaultLoadingManager ) {

		super();
		this.manager = manager;

	}

	parse( buffer ) {

		return super
			.parse( buffer )
			.then( i3dm => {

				const { featureTable, batchTable } = i3dm;
				const gltfBuffer = i3dm.glbBytes.slice().buffer;
				return new Promise( ( resolve, reject ) => {

					const manager = this.manager;
					const loader = manager.getHandler( 'path.gltf' ) || new GLTFLoader( manager );
					loader.parse( gltfBuffer, null, model => {

						const INSTANCES_LENGTH = featureTable.getData( 'INSTANCES_LENGTH' );

						// RTC_CENTER
						// QUANTIZED_VOLUME_OFFSET
						// QUANTIZED_VOLUME_SCALE
						// EAST_NORTH_UP

						const POSITION = featureTable.getData( 'POSITION', INSTANCES_LENGTH, 'FLOAT', 'VEC3' );

						// POSITION_QUANTIZED
						// NORMAL_UP
						// NORMAL_RIGHT
						// NORMAL_UP_OCT32P
						// NORMAL_RIGHT_OCT32P
						// SCALE
						// SCALE_NON_UNIFORM
						// BATCH_ID

						const instanceMap = new Map();
						const instances = [];
						model.scene.traverse( child => {

							if ( child.isMesh ) {

								const { geometry, material } = child;
								const instancedMesh = new InstancedMesh( geometry, material, INSTANCES_LENGTH );
								instances.push( instancedMesh );
								instanceMap.set( child, instancedMesh );

							}

						} );

						const averageVector = new Vector3();
						const transformedVector = new Vector3();
						for ( let i = 0; i < INSTANCES_LENGTH; i ++ ) {

							// TODO: handle quantized position
							averageVector.x += POSITION[ i * 3 + 0 ] / INSTANCES_LENGTH;
							averageVector.y += POSITION[ i * 3 + 1 ] / INSTANCES_LENGTH;
							averageVector.z += POSITION[ i * 3 + 2 ] / INSTANCES_LENGTH;

						}

						// replace the meshes with instanced meshes
						instanceMap.forEach( ( instancedMesh, mesh ) => {

							const parent = mesh.parent;
							if ( parent ) {

								// Mesh have no children
								parent.remove( mesh );
								parent.add( instancedMesh );

								// Center the instance around an average point to avoid jitter at large scales.
								transformedVector
									.copy( averageVector )
									.applyQuaternion( parent.quaternion )
									.multiply( parent.scale );
								instancedMesh.add( transformedVector );

							}

						} );

						for ( let i = 0; i < INSTANCES_LENGTH; i ++ ) {

							// TODO: handle quantized position
							tempPos.set(
								POSITION[ i * 3 + 0 ] - averageVector.x,
								POSITION[ i * 3 + 1 ] - averageVector.y,
								POSITION[ i * 3 + 2 ] - averageVector.z,
							);

							// TODO: handle normal orientation features
							tempQuat.set( 0, 0, 0, 1 );

							// TODO: handle scale features
							tempSca.set( 1, 1, 1 );

							tempMat.compose( tempPos, tempQuat, tempSca );

							for ( let j = 0, l = instances.length; j < l; j ++ ) {

								const instance = instances[ j ];
								instance.setMatrixAt( i, tempMat );

							}

						}

						model.batchTable = batchTable;
						model.featureTable = featureTable;

						model.scene.batchTable = batchTable;
						model.scene.featureTable = featureTable;

						resolve( model );

					}, reject );

				} );

			} );

	}

}
