import {Vector3, Object3D, Mesh, Line3, Box3} from 'three';
import {Attribute} from '../Attribute';

export type Vector3_8 = [Vector3, Vector3, Vector3, Vector3, Vector3, Vector3, Vector3, Vector3];

export const DEFAULT_POSITIONS: Vector3_8 = [
	new Vector3(0, 0, 0),
	new Vector3(1, 0, 0),
	new Vector3(1, 0, 1),
	new Vector3(0, 0, 1),
	//
	new Vector3(0, 1, 0),
	new Vector3(1, 1, 0),
	new Vector3(1, 1, 1),
	new Vector3(0, 1, 1),
];

const _v3 = new Vector3();
const lb0 = new Line3();
const lb1 = new Line3();
const lt0 = new Line3();
const lt1 = new Line3();
const pb0 = new Vector3();
const pb1 = new Vector3();
const pt0 = new Vector3();
const pt1 = new Vector3();
const pb = new Vector3();
const pt = new Vector3();
const target = new Vector3();

export interface CubeLatticeDeformOptions {
	offset: Vector3;
	moveObjectPosition: boolean;
}
const _box3 = new Box3();
const _box3Size = new Vector3();
const _originalObjectPosition = new Vector3();
const _positionDelta = new Vector3();
const _positionRatio = new Vector3();

export function cubeLatticeDeform(object: Object3D, points: Vector3_8, options: CubeLatticeDeformOptions) {
	const geometry = (object as Mesh).geometry;
	if (!geometry) {
		return;
	}
	const positionAttribute = geometry.attributes[Attribute.POSITION];
	if (!positionAttribute) {
		return;
	}
	const positionArray = positionAttribute.array;
	const {offset, moveObjectPosition} = options;

	if (moveObjectPosition) {
		_box3.setFromObject(object);
		_box3.getSize(_box3Size);
		_originalObjectPosition.copy(object.position);
		_positionRatio.copy(object.position).sub(_box3.min).divide(_box3Size);
	}

	lb0.start = points[0];
	lb0.end = points[1];
	lb1.start = points[3];
	lb1.end = points[2];
	lt0.start = points[4];
	lt0.end = points[5];
	lt1.start = points[7];
	lt1.end = points[6];

	const pointsCount = positionArray.length / 3;
	for (let i = 0; i < pointsCount; i++) {
		_v3.fromArray(positionArray, i * 3);
		_v3.add(offset);

		interpolate(_v3, lb0, lb1, lt0, lt1, target);
		target.toArray(positionArray, i * 3);
	}

	if (moveObjectPosition) {
		geometry.computeBoundingBox();
		_box3.setFromObject(object);
		_box3.getCenter(_positionDelta);
		_positionDelta.sub(_originalObjectPosition);
		object.position.add(_positionDelta);
		object.updateMatrix();

		for (let i = 0; i < pointsCount; i++) {
			_v3.fromArray(positionArray, i * 3);
			_v3.sub(_positionDelta);
			_v3.toArray(positionArray, i * 3);
		}
	}

	positionAttribute.needsUpdate = true;
	geometry.computeVertexNormals();
}

function interpolate(p: Vector3, lb0: Line3, lb1: Line3, lt0: Line3, lt1: Line3, target: Vector3) {
	pb0.copy(lb0.start).lerp(lb0.end, p.x);
	pb1.copy(lb1.start).lerp(lb1.end, p.x);
	pt0.copy(lt0.start).lerp(lt0.end, p.x);
	pt1.copy(lt1.start).lerp(lt1.end, p.x);
	pb.copy(pb0).lerp(pb1, p.z);
	pt.copy(pt0).lerp(pt1, p.z);
	target.copy(pb).lerp(pt, p.y);
}
