

// import {Version} from "../../Version.js";
import {PointAttributes, PointAttribute, PointAttributeTypes} from "../../../loader/PointAttributes.js";
import {BrotliDecode} from "../../../../libs/brotli/decode.js";

const typedArrayMapping = {
	"int8":   Int8Array,
	"int16":  Int16Array,
	"int32":  Int32Array,
	"int64":  Float64Array,
	"uint8":  Uint8Array,
	"uint16": Uint16Array,
	"uint32": Uint32Array,
	"uint64": Float64Array,
	"float":  Float32Array,
	"double": Float64Array,
};

Potree = {};

function dealign24b(mortoncode){
	// see https://stackoverflow.com/questions/45694690/how-i-can-remove-all-odds-bits-in-c

	// input alignment of desired bits
	// ..a..b..c..d..e..f..g..h..i..j..k..l..m..n..o..p
	let x = mortoncode;

	//          ..a..b..c..d..e..f..g..h..i..j..k..l..m..n..o..p                     ..a..b..c..d..e..f..g..h..i..j..k..l..m..n..o..p 
	//          ..a.....c.....e.....g.....i.....k.....m.....o...                     .....b.....d.....f.....h.....j.....l.....n.....p 
	//          ....a.....c.....e.....g.....i.....k.....m.....o.                     .....b.....d.....f.....h.....j.....l.....n.....p 
	x = ((x & 0b001000001000001000001000) >>  2) | ((x & 0b000001000001000001000001) >> 0);
	//          ....ab....cd....ef....gh....ij....kl....mn....op                     ....ab....cd....ef....gh....ij....kl....mn....op
	//          ....ab..........ef..........ij..........mn......                     ..........cd..........gh..........kl..........op
	//          ........ab..........ef..........ij..........mn..                     ..........cd..........gh..........kl..........op
	x = ((x & 0b000011000000000011000000) >>  4) | ((x & 0b000000000011000000000011) >> 0);
	//          ........abcd........efgh........ijkl........mnop                     ........abcd........efgh........ijkl........mnop
	//          ........abcd....................ijkl............                     ....................efgh....................mnop
	//          ................abcd....................ijkl....                     ....................efgh....................mnop
	x = ((x & 0b000000001111000000000000) >>  8) | ((x & 0b000000000000000000001111) >> 0);
	//          ................abcdefgh................ijklmnop                     ................abcdefgh................ijklmnop
	//          ................abcdefgh........................                     ........................................ijklmnop
	//          ................................abcdefgh........                     ........................................ijklmnop
	x = ((x & 0b000000000000000000000000) >> 16) | ((x & 0b000000000000000011111111) >> 0);

	// sucessfully realigned! 
	//................................abcdefghijklmnop

	return x;
}

let mask_b0 = new Uint8Array([0, 1, 0, 1, 0, 1, 0, 1, 2, 3, 2, 3, 2, 3, 2, 3, 0, 1, 0, 1, 0, 1, 0, 1, 2, 3, 2, 3, 2, 3, 2, 3, 0, 1, 0, 1, 0, 1, 0, 1, 2, 3, 2, 3, 2, 3, 2, 3, 0, 1, 0, 1, 0, 1, 0, 1, 2, 3, 2, 3, 2, 3, 2, 3, 0, 1, 0, 1, 0, 1, 0, 1, 2, 3, 2, 3, 2, 3, 2, 3, 0, 1, 0, 1, 0, 1, 0, 1, 2, 3, 2, 3, 2, 3, 2, 3, 0, 1, 0, 1, 0, 1, 0, 1, 2, 3, 2, 3, 2, 3, 2, 3, 0, 1, 0, 1, 0, 1, 0, 1, 2, 3, 2, 3, 2, 3, 2, 3, 0, 1, 0, 1, 0, 1, 0, 1, 2, 3, 2, 3, 2, 3, 2, 3, 0, 1, 0, 1, 0, 1, 0, 1, 2, 3, 2, 3, 2, 3, 2, 3, 0, 1, 0, 1, 0, 1, 0, 1, 2, 3, 2, 3, 2, 3, 2, 3, 0, 1, 0, 1, 0, 1, 0, 1, 2, 3, 2, 3, 2, 3, 2, 3, 0, 1, 0, 1, 0, 1, 0, 1, 2, 3, 2, 3, 2, 3, 2, 3, 0, 1, 0, 1, 0, 1, 0, 1, 2, 3, 2, 3, 2, 3, 2, 3, 0, 1, 0, 1, 0, 1, 0, 1, 2, 3, 2, 3, 2, 3, 2, 3, 0, 1, 0, 1, 0, 1, 0, 1, 2, 3, 2, 3, 2, 3, 2, 3]);

onmessage = function (event) {

	let {pointAttributes, scale, name, min, max, size, offset, numPoints} = event.data;

	let tStart = performance.now();

	let buffer = BrotliDecode(new Int8Array(event.data.buffer));
	let view = new DataView(buffer.buffer);
	
	let attributeBuffers = {};
	let attributeOffset = 0;

	let bytesPerPoint = 0;
	for (let pointAttribute of pointAttributes.attributes) {
		bytesPerPoint += pointAttribute.byteSize;
	}

	let gridSize = 32;
	let grid = new Uint32Array(gridSize ** 3);
	let toIndex = (x, y, z) => {

		// min is already subtracted
		let dx = gridSize * x / size.x;
		let dy = gridSize * y / size.y;
		let dz = gridSize * z / size.z;

		let ix = Math.min(parseInt(dx), gridSize - 1);
		let iy = Math.min(parseInt(dy), gridSize - 1);
		let iz = Math.min(parseInt(dz), gridSize - 1);

		let index = ix + iy * gridSize + iz * gridSize * gridSize;

		return index;
	};

	let numOccupiedCells = 0;
	let byteOffset = 0;
	for (let pointAttribute of pointAttributes.attributes) {
		

		if(["POSITION_CARTESIAN", "position"].includes(pointAttribute.name)){

			// let tStart = performance.now();

			let buff = new ArrayBuffer(numPoints * 4 * 3);
			let positions = new Float32Array(buff);
		
			for (let j = 0; j < numPoints; j++) {


				let mc_0 = view.getUint32(byteOffset +  4, true);
				let mc_1 = view.getUint32(byteOffset +  0, true);
				let mc_2 = view.getUint32(byteOffset + 12, true);
				let mc_3 = view.getUint32(byteOffset +  8, true);

				byteOffset += 16;

				let X = dealign24b((mc_3 & 0x00FFFFFF) >>> 0) 
						| (dealign24b(((mc_3 >>> 24) | (mc_2 << 8)) >>> 0) << 8);

				let Y = dealign24b((mc_3 & 0x00FFFFFF) >>> 1) 
						| (dealign24b(((mc_3 >>> 24) | (mc_2 << 8)) >>> 1) << 8)
						

				let Z = dealign24b((mc_3 & 0x00FFFFFF) >>> 2) 
						| (dealign24b(((mc_3 >>> 24) | (mc_2 << 8)) >>> 2) << 8)
						

				if(mc_1 != 0 || mc_2 != 0){
					X = X | (dealign24b((mc_1 & 0x00FFFFFF) >>> 0) << 16)
						| (dealign24b(((mc_1 >>> 24) | (mc_0 << 8)) >>> 0) << 24);

					Y = Y | (dealign24b((mc_1 & 0x00FFFFFF) >>> 1) << 16)
						| (dealign24b(((mc_1 >>> 24) | (mc_0 << 8)) >>> 1) << 24);

					Z = Z | (dealign24b((mc_1 & 0x00FFFFFF) >>> 2) << 16)
						| (dealign24b(((mc_1 >>> 24) | (mc_0 << 8)) >>> 2) << 24);
				}

				// =======================
				// VERIFY AGAINST LOOP VERSION
				// =======================
				// let reference;
				// { // correct reference

				// 	let mc_upper = view.getBigUint64(byteOffset + 0, true);
				// 	let mc_lower = view.getBigUint64(byteOffset + 8, true);

				// 	let X = 0n;
				// 	let Y = 0n;
				// 	let Z = 0n;

				// 	for(let k = 0n; k < 16n; k++){
				// 		let mask_lower = (mc_lower >> (3n * k)) & 0b111n;
				// 		let mask_upper = (mc_upper >> (3n * k)) & 0b111n;

				// 		X = X | (((mask_lower >> 0n) & 0b001n) << k);
				// 		X = X | ((((mask_upper >> 0n) & 0b001n) << k) << 16n);

				// 		Y = Y | (((mask_lower >> 1n) & 0b001n) << k);
				// 		Y = Y | ((((mask_upper >> 1n) & 0b001n) << k) << 16n);

				// 		Z = Z | (((mask_lower >> 2n) & 0b001n) << k);
				// 		Z = Z | ((((mask_upper >> 2n) & 0b001n) << k) << 16n);
				// 	}

				// 	reference = [X, Y, Z];
				// }
				// //dbgad += parseInt(reference[2]);
				// let [rX, rY, rZ] = reference;

				// if(X !== parseInt(rX)){
				// 	debugger;
				// }
				// if(Y !== parseInt(rY)){
				// 	debugger;
				// }
				// if(Z !== parseInt(rZ)){
				// 	debugger;
				// }

				


				// let mc_upper = view.getBigUint64(byteOffset + 0, true);
				// let mc_lower = view.getBigUint64(byteOffset + 8, true);
				// byteOffset += 16;

				// =======================
				// MAGIC NUMBERS 32BIT
				// =======================
				// let mc0 = parseInt((mc_lower >>  0n) & 0x00FFFFFFn);
				// let mc1 = parseInt((mc_lower >> 24n) & 0x00FFFFFFn);
				// let mc2 = parseInt((mc_lower >> 48n) & 0x00FFFFFFn);

				// let X = dealign24b(mc0 >> 0) | (dealign24b(mc1 >> 0) << 8) | (dealign24b(mc2 >> 0) << 16);
				// let Y = dealign24b(mc0 >> 1) | (dealign24b(mc1 >> 1) << 8) | (dealign24b(mc2 >> 1) << 16);
				// let Z = dealign24b(mc0 >> 2) | (dealign24b(mc1 >> 2) << 8) | (dealign24b(mc2 >> 2) << 16);


				// =======================
				// MAGIC NUMBERS BIGINT
				// =======================
				// let X = dealign(mc_lower >> 0n);// | (dealign(mc_upper >> 0n) << 16n);
				// let Y = dealign(mc_lower >> 1n);// | (dealign(mc_upper >> 1n) << 16n);
				// let Z = dealign(mc_lower >> 2n);// | (dealign(mc_upper >> 2n) << 16n);

				// =======================
				// LOOP
				// =======================
				// let X = 0n;
				// let Y = 0n;
				// let Z = 0n;

				// for(let k = 0n; k < 16n; k++){
				// 	let mask_lower = (mc_lower >> (3n * k)) & 0b111n;
				// 	let mask_upper = (mc_upper >> (3n * k)) & 0b111n;

				// 	X = X | (((mask_lower >> 0n) & 0b001n) << k);
				// 	X = X | ((((mask_upper >> 0n) & 0b001n) << k) << 16n);

				// 	Y = Y | (((mask_lower >> 1n) & 0b001n) << k);
				// 	Y = Y | ((((mask_upper >> 1n) & 0b001n) << k) << 16n);

				// 	Z = Z | (((mask_lower >> 2n) & 0b001n) << k);
				// 	Z = Z | ((((mask_upper >> 2n) & 0b001n) << k) << 16n);
				// }



				let x = parseInt(X) * scale[0] + offset[0] - min.x;
				let y = parseInt(Y) * scale[1] + offset[1] - min.y;
				let z = parseInt(Z) * scale[2] + offset[2] - min.z;

				let index = toIndex(x, y, z);
				let count = grid[index]++;
				if(count === 0){
					numOccupiedCells++;
				}

				positions[3 * j + 0] = x;
				positions[3 * j + 1] = y;
				positions[3 * j + 2] = z;
			}

			// let duration = performance.now() - tStart;
			// console.log(`xyz: ${duration.toFixed(1)}ms`);

			attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute };
		}else if(["RGBA", "rgba"].includes(pointAttribute.name)){

			let buff = new ArrayBuffer(numPoints * 4);
			let colors = new Uint8Array(buff);

			// for (let j = 0; j < numPoints; j++) {
			// 	let r = view.getUint16(byteOffset + 0, true);
			// 	let g = view.getUint16(byteOffset + 2, true);
			// 	let b = view.getUint16(byteOffset + 4, true);
			// 	byteOffset += 6;

			// 	colors[4 * j + 0] = r > 255 ? r / 256 : r;
			// 	colors[4 * j + 1] = g > 255 ? g / 256 : g;
			// 	colors[4 * j + 2] = b > 255 ? b / 256 : b;
			// }

			// let tStart = performance.now();

			for (let j = 0; j < numPoints; j++) {

				let mc_0 = view.getUint32(byteOffset +  4, true);
				let mc_1 = view.getUint32(byteOffset +  0, true);
				byteOffset += 8;

				let r = dealign24b((mc_1 & 0x00FFFFFF) >>> 0) 
						| (dealign24b(((mc_1 >>> 24) | (mc_0 << 8)) >>> 0) << 8);

				let g = dealign24b((mc_1 & 0x00FFFFFF) >>> 1) 
						| (dealign24b(((mc_1 >>> 24) | (mc_0 << 8)) >>> 1) << 8);

				let b = dealign24b((mc_1 & 0x00FFFFFF) >>> 2) 
						| (dealign24b(((mc_1 >>> 24) | (mc_0 << 8)) >>> 2) << 8);

				// let bits = mask_b0[mc_1 >>> 24];
				
				// if(((r >> 8) & 0b11) !== bits){
				// 	debugger;	
				// }

				// let r = dealign24b(mc0 >> 0) | (dealign24b(mc1 >> 0) << 8);
				// let g = dealign24b(mc0 >> 1) | (dealign24b(mc1 >> 1) << 8);
				// let b = dealign24b(mc0 >> 2) | (dealign24b(mc1 >> 2) << 8);


				colors[4 * j + 0] = r > 255 ? r / 256 : r;
				colors[4 * j + 1] = g > 255 ? g / 256 : g;
				colors[4 * j + 2] = b > 255 ? b / 256 : b;
			}
			// let duration = performance.now() - tStart;
			// console.log(`rgb: ${duration.toFixed(1)}ms`);

			attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute };
		}else{
			let buff = new ArrayBuffer(numPoints * 4);
			let f32 = new Float32Array(buff);

			let TypedArray = typedArrayMapping[pointAttribute.type.name];
			preciseBuffer = new TypedArray(numPoints);

			let [offset, scale] = [0, 1];

			const getterMap = {
				"int8":   view.getInt8,
				"int16":  view.getInt16,
				"int32":  view.getInt32,
				// "int64":  view.getInt64,
				"uint8":  view.getUint8,
				"uint16": view.getUint16,
				"uint32": view.getUint32,
				// "uint64": view.getUint64,
				"float":  view.getFloat32,
				"double": view.getFloat64,
			};
			const getter = getterMap[pointAttribute.type.name].bind(view);

			// compute offset and scale to pack larger types into 32 bit floats
			if(pointAttribute.type.size > 4){
				let [amin, amax] = pointAttribute.range;
				offset = amin;
				scale = 1 / (amax - amin);
			}

			for(let j = 0; j < numPoints; j++){
				// let pointOffset = j * bytesPerPoint;
				let value = getter(byteOffset, true);
				byteOffset += pointAttribute.byteSize;

				f32[j] = (value - offset) * scale;
				preciseBuffer[j] = value;
			}

			attributeBuffers[pointAttribute.name] = { 
				buffer: buff,
				preciseBuffer: preciseBuffer,
				attribute: pointAttribute,
				offset: offset,
				scale: scale,
			};
		}

		// attributeOffset += pointAttribute.byteSize;


	}

	let occupancy = parseInt(numPoints / numOccupiedCells);
	// console.log(`${name}: #points: ${numPoints}: #occupiedCells: ${numOccupiedCells}, occupancy: ${occupancy} points/cell`);

	{ // add indices
		let buff = new ArrayBuffer(numPoints * 4);
		let indices = new Uint32Array(buff);

		for (let i = 0; i < numPoints; i++) {
			indices[i] = i;
		}
		
		attributeBuffers["INDICES"] = { buffer: buff, attribute: PointAttribute.INDICES };
	}


	{ // handle attribute vectors
		let vectors = pointAttributes.vectors;

		for(let vector of vectors){

			let {name, attributes} = vector;
			let numVectorElements = attributes.length;
			let buffer = new ArrayBuffer(numVectorElements * numPoints * 4);
			let f32 = new Float32Array(buffer);

			let iElement = 0;
			for(let sourceName of attributes){
				let sourceBuffer = attributeBuffers[sourceName];
				let {offset, scale} = sourceBuffer;
				let view = new DataView(sourceBuffer.buffer);

				const getter = view.getFloat32.bind(view);

				for(let j = 0; j < numPoints; j++){
					let value = getter(j * 4, true);

					f32[j * numVectorElements + iElement] = (value / scale) + offset;
				}

				iElement++;
			}

			let vecAttribute = new PointAttribute(name, PointAttributeTypes.DATA_TYPE_FLOAT, 3);

			attributeBuffers[name] = { 
				buffer: buffer, 
				attribute: vecAttribute,
			};

		}

	}


	let duration = performance.now() - tStart;
	let pointsPerMs = numPoints / duration;
	// console.log(`duration: ${duration.toFixed(1)}ms, #points: ${numPoints}, points/ms: ${pointsPerMs.toFixed(1)}`);

	let message = {
		buffer: buffer,
		attributeBuffers: attributeBuffers,
		density: occupancy,
	};

	let transferables = [];
	for (let property in message.attributeBuffers) {
		transferables.push(message.attributeBuffers[property].buffer);
	}
	// transferables.push(buffer);

	postMessage(message, transferables);
};
