import {BaseSopOperation} from './_Base';
import {BufferGeometry, Vector2, Vector3, PlaneGeometry, Quaternion, BoxGeometry, Box3} from 'three';
import {CoreTransform, rotateGeometry} from '../../../core/Transform';
import {CoreGroup} from '../../../core/geometry/Group';
import {InputCloneMode} from '../../../engine/poly/InputCloneMode';
import {isBooleanTrue} from '../../../core/BooleanValue';
import {DefaultOperationParams} from '../../../core/operations/_Base';
import {ObjectType} from '../../../core/geometry/Constant';
const tmpBox = new Box3();
const tmpBoxPreRotation = new Box3();
const tmpSize = new Vector3();
const tmpCenter = new Vector3();
const DEFAULT_UP = new Vector3(0, 0, 1);
const q = new Quaternion();
// const size = new Vector3();
// const center = new Vector3();
interface PlaneSopParams extends DefaultOperationParams {
	size: Vector2;
	useSegmentsCount: boolean;
	stepSize: number;
	segments: Vector2;
	direction: Vector3;
	center: Vector3;
	asLines: boolean;
}
export const DEFAULT_PARAMS: PlaneSopParams = {
	size: new Vector2(1, 1),
	useSegmentsCount: false,
	stepSize: 1,
	segments: new Vector2(1, 1),
	direction: new Vector3(0, 1, 0),
	center: new Vector3(0, 0, 0),
	asLines: false,
};
const _segmentsCount = new Vector2(1, 1);
export class PlaneSopOperation extends BaseSopOperation {
	static override readonly DEFAULT_PARAMS = DEFAULT_PARAMS;
	static override readonly INPUT_CLONED_STATE = InputCloneMode.NEVER;
	static override type(): Readonly<'plane'> {
		return 'plane';
	}

	private _coreTransform = new CoreTransform();

	override cook(inputCoreGroups: CoreGroup[], params: PlaneSopParams) {
		const coreGroup = inputCoreGroups[0];
		if (coreGroup) {
			return this._cookWithInput(coreGroup, params);
		} else {
			return this._cookWithoutInput(params);
		}
	}
	private _cookWithoutInput(params: PlaneSopParams) {
		const geometry = this._createPlane(params.size, params);

		// convert to buffer geo, as some render problems can occur otherwise
		// geometry = BufferGeometryUtils.mergeGeometries([geometry])
		// console.log(geometry, geometry.isBufferGeometry)
		rotateGeometry(geometry, DEFAULT_UP, params.direction);

		const matrix = this._coreTransform.translationMatrix(params.center);
		geometry.applyMatrix4(matrix);

		const object = this._createPlaneObject(geometry, params);
		return this.createCoreGroupFromObjects([object]);
	}

	private _cookWithInput(coreGroup: CoreGroup, params: PlaneSopParams) {
		coreGroup.boundingBox(tmpBoxPreRotation);
		tmpBoxPreRotation.getCenter(tmpCenter);

		// create box
		coreGroup.boundingBox(tmpBox);
		tmpBox.getSize(tmpSize);
		tmpBox.getCenter(tmpCenter);
		const boxGeometry = new BoxGeometry(tmpSize.x, tmpSize.y, tmpSize.z, 1, 1, 1);

		// rotate box
		function _applyInputQuaternion(_q: Quaternion) {
			boxGeometry.applyQuaternion(_q);
			boxGeometry.computeBoundingBox();
		}
		function _setInputRotation() {
			q.setFromUnitVectors(DEFAULT_UP, params.direction);
			_applyInputQuaternion(q);
		}
		// function _resetInputRotation() {
		// 	q.invert();
		// 	_applyInputQuaternion(q);
		// }
		_setInputRotation();
		const bboxPostRotation = boxGeometry.boundingBox!; //coreGroup.boundingBox(true);
		bboxPostRotation.getSize(tmpSize);
		// bboxPreRotation.getCenter(center); // debug
		// _resetInputRotation();

		const size2d = new Vector2(tmpSize.x, tmpSize.y);
		const geometry = this._createPlane(size2d, params);

		rotateGeometry(geometry, DEFAULT_UP, params.direction);
		geometry.translate(tmpCenter.x, tmpCenter.y, tmpCenter.z);

		const object = this._createPlaneObject(geometry, params);
		return this.createCoreGroupFromObjects([object]);
	}

	private _createPlaneObject(geometry: BufferGeometry, params: PlaneSopParams) {
		return BaseSopOperation.createObject(geometry, params.asLines ? ObjectType.LINE_SEGMENTS : ObjectType.MESH);
	}

	private _createPlane(size: Vector2, params: PlaneSopParams) {
		size = size.clone();
		if (isBooleanTrue(params.useSegmentsCount)) {
			_segmentsCount.x = Math.floor(params.segments.x);
			_segmentsCount.y = Math.floor(params.segments.y);
		} else {
			if (params.stepSize > 0) {
				size.x = Math.max(size.x, params.stepSize);
				size.y = Math.max(size.y, params.stepSize);
				_segmentsCount.x = Math.floor(size.x / params.stepSize);
				_segmentsCount.y = Math.floor(size.y / params.stepSize);
				size.x = _segmentsCount.x * params.stepSize;
				size.y = _segmentsCount.y * params.stepSize;
			}
		}
		const geometry = new PlaneGeometry(size.x, size.y, _segmentsCount.x, _segmentsCount.y);
		if (isBooleanTrue(params.asLines)) {
			const gridX = Math.floor(_segmentsCount.x);
			const gridY = Math.floor(_segmentsCount.y);

			const gridX1 = gridX + 1;
			// const gridY1 = gridY + 1;
			const indices: number[] = [];
			for (let iy = 0; iy < gridY; iy++) {
				for (let ix = 0; ix < gridX; ix++) {
					const a = ix + gridX1 * iy;
					const b = ix + gridX1 * (iy + 1);
					const d = ix + 1 + gridX1 * iy;

					indices.push(a, b);
					indices.push(a, d);
					const lastX = ix == gridX - 1;
					const lastY = iy == gridY - 1;
					if (lastX || lastY) {
						const c = ix + 1 + gridX1 * (iy + 1);
						if (lastX) {
							indices.push(d, c);
						}
						if (lastY) {
							indices.push(b, c);
						}
					}
				}
			}
			geometry.setIndex(indices);
		}
		return geometry;
	}
}
