/**
 * creates rules used by the WFCSolver
 *
 *
 */
import {TypedSopNode} from './_Base';
import {NodeParamsConfig} from '../utils/params/ParamsConfig';
import {CoreGroup} from '../../../core/geometry/Group';
import {SopType} from '../../poly/registers/nodes/types/Sop';
import {InputCloneMode} from '../../poly/InputCloneMode';
import {Object3D, Quaternion} from 'three';
import {WFCTileSide, rotatedSide, tileSideUnrotated, neighbourTileSideUnrotated} from '../../../core/wfc/WFCCommon';
import {CoreWFCTileAttribute} from '../../../core/wfc/WFCAttributes';
import {createRuleObject} from '../../../core/wfc/WFCRule';

const identityQuaternion = new Quaternion();
const _q = new Quaternion();
// const EPS = 0.001;
function _angleIncrement(object: Object3D) {
	// when a tile rotation is 2 rotations ( 2x90 = 180 ),
	// the rotation.y is not 180, but 0,
	// and we therefore check the quaternion angle intead.
	if (Math.abs(object.rotation.y) > 0.1) {
		return object.rotation.y / (Math.PI / 2);
	}
	_q.setFromRotationMatrix(object.matrix);
	return _q.angleTo(identityQuaternion) / (Math.PI / 2);
}

class WFCRuleFromProximitySopParamsConfig extends NodeParamsConfig {}
const ParamsConfig = new WFCRuleFromProximitySopParamsConfig();

export class WFCRuleFromProximitySopNode extends TypedSopNode<WFCRuleFromProximitySopParamsConfig> {
	override paramsConfig = ParamsConfig;
	static override type() {
		return SopType.WFC_RULE_FROM_PROXIMITY;
	}

	override initializeNode() {
		this.io.inputs.setCount(2);
		this.io.inputs.initInputsClonedState([InputCloneMode.NEVER]);
	}

	override async cook(inputCoreGroups: CoreGroup[]) {
		const coreGroup0 = inputCoreGroups[0];
		const coreGroup1 = inputCoreGroups[1];
		const inputObjects = coreGroup0.threejsObjects();
		const tileObjects = coreGroup1.threejsObjects();
		const outputObjects: Object3D[] = [...inputObjects];

		const tileObjectsInGrid: Object3D[][][] = []; // x/y/z
		const _addTileObject = (tileObject: Object3D) => {
			const x = Math.round(tileObject.position.x);
			const y = Math.round(tileObject.position.y);
			const z = Math.round(tileObject.position.z);
			tileObjectsInGrid[x] = tileObjectsInGrid[x] || [];
			tileObjectsInGrid[x][y] = tileObjectsInGrid[x][y] || [];
			tileObjectsInGrid[x][y][z] = tileObject;
		};
		const _getTileObject = (x: number, y: number, z: number): Object3D | undefined => {
			x = Math.round(x);
			y = Math.round(y);
			z = Math.round(z);
			const xs = tileObjectsInGrid[x];
			if (!xs) {
				return;
			}
			const yz = tileObjectsInGrid[x][y];
			if (!yz) {
				return;
			}
			return tileObjectsInGrid[x][y][z];
		};
		const _addConnectionIfNeighbourFound = (
			currentObject: Object3D,
			xOffset: number,
			yOffset: number,
			zOffset: number
		) => {
			const x = Math.round(currentObject.position.x);
			const y = Math.round(currentObject.position.y);
			const z = Math.round(currentObject.position.z);
			const neighbour = _getTileObject(x + xOffset, y + yOffset, z + zOffset);
			if (!neighbour) {
				return;
			}

			const id0 = CoreWFCTileAttribute.getTileId(currentObject);
			const id1 = CoreWFCTileAttribute.getTileId(neighbour);
			const currentObjectSideUnrotated: WFCTileSide = tileSideUnrotated(xOffset, yOffset, zOffset);
			//xOffset < 0 ? 's' : xOffset > 0 ? 'n' : zOffset < 0 ? 'w' : zOffset > 0 ? 'e' : yOffset < 0 ? 'b' : 't';
			const currentObjectSide = rotatedSide(
				currentObjectSideUnrotated,
				Math.round(_angleIncrement(currentObject))
			);
			const neighbourSideUnrotated: WFCTileSide = neighbourTileSideUnrotated(xOffset, yOffset, zOffset);
			// xOffset < 0 ? 'n' : xOffset > 0 ? 's' : zOffset < 0 ? 'e' : zOffset > 0 ? 'w' : yOffset < 0 ? 't' : 'b';
			const neighbourSide = rotatedSide(neighbourSideUnrotated, Math.round(_angleIncrement(neighbour)));

			const ruleObject = createRuleObject({
				id0,
				id1,
				side0: currentObjectSide,
				side1: neighbourSide,
			});

			outputObjects.push(ruleObject);
		};
		for (const tileObject of tileObjects) {
			const isTile = CoreWFCTileAttribute.getIsTile(tileObject);
			if (!isTile) {
				continue;
			}
			_addTileObject(tileObject);
		}
		for (const tileObject of tileObjects) {
			const isTile = CoreWFCTileAttribute.getIsTile(tileObject);
			if (!isTile) {
				continue;
			}
			_addConnectionIfNeighbourFound(tileObject, -1, 0, 0);
			_addConnectionIfNeighbourFound(tileObject, +1, 0, 0);
			_addConnectionIfNeighbourFound(tileObject, 0, -1, 0);
			_addConnectionIfNeighbourFound(tileObject, 0, +1, 0);
			_addConnectionIfNeighbourFound(tileObject, 0, 0, -1);
			_addConnectionIfNeighbourFound(tileObject, 0, 0, +1);
		}

		this.setObjects(outputObjects);
	}
}
