/**
 * Applies multiple rotations with one node
 *
 *
 *
 */
import {TypedSopNode} from './_Base';
import {CoreGroup, Object3DWithGeometry} from '../../../core/geometry/Group';
import {
	CoreTransform,
	ROTATION_ORDERS,
	RotationOrder,
	TransformTargetType,
	TRANSFORM_TARGET_TYPES,
} from '../../../core/Transform';
import {InputCloneMode} from '../../poly/InputCloneMode';
import {Vector3} from 'three/src/math/Vector3';
import {ParamOptions, MenuNumericParamOptions, VisibleIfParamOptions} from '../../params/utils/OptionsController';

const max_transform_count = 6;
const ROT_ORDER_DEFAULT = ROTATION_ORDERS.indexOf(RotationOrder.XYZ);
const ROT_ORDER_MENU_ENTRIES: MenuNumericParamOptions = {
	menu: {
		entries: ROTATION_ORDERS.map((order, v) => {
			return {name: order, value: v};
		}),
	},
};
function visible_for_count(count: number): ParamOptions {
	const list: VisibleIfParamOptions[] = [];
	for (let i = count + 1; i <= max_transform_count; i++) {
		list.push({
			count: i,
		});
	}
	return {visibleIf: list};
}

type VectorNumberParamPair = [Vector3Param, IntegerParam];

import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig';
import {IntegerParam} from '../../params/Integer';
import {Vector3Param} from '../../params/Vector3';
import {TypeAssert} from '../../poly/Assert';
import {Object3D} from 'three/src/core/Object3D';
import {BufferAttribute} from 'three/src/core/BufferAttribute';
import {CoreAttribute, Attribute} from '../../../core/geometry/Attribute';
class TransformMultiSopParamConfig extends NodeParamsConfig {
	/** @param defines if this applies to objects or geometries */
	applyOn = ParamConfig.INTEGER(TRANSFORM_TARGET_TYPES.indexOf(TransformTargetType.GEOMETRIES), {
		menu: {
			entries: TRANSFORM_TARGET_TYPES.map((target_type, i) => {
				return {name: target_type, value: i};
			}),
		},
	});
	/** @param number of transformations this can apply */
	count = ParamConfig.INTEGER(2, {
		range: [0, max_transform_count],
		rangeLocked: [true, true],
	});
	// 0
	sep0 = ParamConfig.SEPARATOR(null, {...visible_for_count(0)});
	/** @param transform 0 rotation order */
	rotationOrder0 = ParamConfig.INTEGER(ROT_ORDER_DEFAULT, {
		...ROT_ORDER_MENU_ENTRIES,
		...visible_for_count(0),
	});
	/** @param rotation 0 */
	r0 = ParamConfig.VECTOR3([0, 0, 0], {...visible_for_count(0)});
	// 1
	sep1 = ParamConfig.SEPARATOR(null, {...visible_for_count(1)});
	/** @param transform 1 rotation order */
	rotationOrder1 = ParamConfig.INTEGER(ROT_ORDER_DEFAULT, {
		...ROT_ORDER_MENU_ENTRIES,
		...visible_for_count(1),
	});
	/** @param rotation 1 */
	r1 = ParamConfig.VECTOR3([0, 0, 0], {...visible_for_count(1)});
	// 2
	sep2 = ParamConfig.SEPARATOR(null, {...visible_for_count(2)});
	/** @param transform 2 rotation order */
	rotationOrder2 = ParamConfig.INTEGER(ROT_ORDER_DEFAULT, {
		...ROT_ORDER_MENU_ENTRIES,
		...visible_for_count(2),
	});
	/** @param rotation 2 */
	r2 = ParamConfig.VECTOR3([0, 0, 0], {...visible_for_count(2)});
	// 3
	sep3 = ParamConfig.SEPARATOR(null, {...visible_for_count(3)});
	/** @param transform 3 rotation order */
	rotationOrder3 = ParamConfig.INTEGER(ROT_ORDER_DEFAULT, {
		...ROT_ORDER_MENU_ENTRIES,
		...visible_for_count(3),
	});
	/** @param rotation 3 */
	r3 = ParamConfig.VECTOR3([0, 0, 0], {...visible_for_count(3)});
	// 4
	sep4 = ParamConfig.SEPARATOR(null, {...visible_for_count(4)});
	/** @param transform 4 rotation order */
	rotationOrder4 = ParamConfig.INTEGER(ROT_ORDER_DEFAULT, {
		...ROT_ORDER_MENU_ENTRIES,
		...visible_for_count(4),
	});
	/** @param rotation 4 */
	r4 = ParamConfig.VECTOR3([0, 0, 0], {...visible_for_count(4)});
	// 5
	sep5 = ParamConfig.SEPARATOR(null, {...visible_for_count(5)});
	/** @param transform 5 rotation order */
	rotationOrder5 = ParamConfig.INTEGER(ROT_ORDER_DEFAULT, {
		...ROT_ORDER_MENU_ENTRIES,
		...visible_for_count(5),
	});
	/** @param rotation 5 */
	r5 = ParamConfig.VECTOR3([0, 0, 0], {...visible_for_count(5)});
}
const ParamsConfig = new TransformMultiSopParamConfig();

export class TransformMultiSopNode extends TypedSopNode<TransformMultiSopParamConfig> {
	params_config = ParamsConfig;
	static type() {
		return 'transformMulti';
	}

	static displayedInputNames(): string[] {
		return ['objects to transform', 'objects to copy initial transform from'];
	}

	initializeNode() {
		this.io.inputs.setCount(1, 2);
		this.io.inputs.initInputsClonedState([InputCloneMode.FROM_NODE, InputCloneMode.NEVER]);

		this.scene().dispatchController.onAddListener(() => {
			this.params.onParamsCreated('params_label', () => {
				this.params.label.init([this.p.applyOn], () => {
					return TRANSFORM_TARGET_TYPES[this.pv.applyOn];
				});
			});
		});

		this.params.onParamsCreated('cache param pairs', () => {
			this._rot_and_index_pairs = [
				[this.p.r0, this.p.rotationOrder0],
				[this.p.r1, this.p.rotationOrder1],
				[this.p.r2, this.p.rotationOrder2],
				[this.p.r3, this.p.rotationOrder3],
				[this.p.r4, this.p.rotationOrder4],
				[this.p.r5, this.p.rotationOrder5],
			];
		});
	}

	private _core_transform = new CoreTransform();
	private _rot_and_index_pairs: VectorNumberParamPair[] | undefined;
	cook(input_contents: CoreGroup[]) {
		const objects = input_contents[0].objectsWithGeo();
		const src_object = input_contents[1] ? input_contents[1].objectsWithGeo()[0] : undefined;

		this._apply_transforms(objects, src_object);

		this.setObjects(objects);
	}

	private _apply_transforms(objects: Object3DWithGeometry[], src_object: Object3DWithGeometry | undefined) {
		const mode = TRANSFORM_TARGET_TYPES[this.pv.applyOn];
		switch (mode) {
			case TransformTargetType.GEOMETRIES: {
				return this._apply_matrix_to_geometries(objects, src_object);
			}
			case TransformTargetType.OBJECTS: {
				return this._apply_matrix_to_objects(objects, src_object);
			}
		}
		TypeAssert.unreachable(mode);
	}

	private _apply_matrix_to_geometries(objects: Object3DWithGeometry[], src_object: Object3DWithGeometry | undefined) {
		if (!this._rot_and_index_pairs) {
			return;
		}

		if (src_object) {
			const src_geometry = src_object.geometry;
			if (src_geometry) {
				const attributes_to_copy = [Attribute.POSITION, Attribute.NORMAL, Attribute.TANGENT];

				for (let attrib_name of attributes_to_copy) {
					const src = src_geometry.attributes[attrib_name] as BufferAttribute | null;
					for (let object of objects) {
						const geometry = object.geometry;
						const dest = geometry.attributes[attrib_name] as BufferAttribute | null;
						if (src && dest) {
							CoreAttribute.copy(src, dest);
						}
					}
				}
			}
		}

		let pair: VectorNumberParamPair;
		for (let i = 0; i < this.pv.count; i++) {
			pair = this._rot_and_index_pairs[i];
			const matrix = this._matrix(pair[0].value, pair[1].value);
			for (let object of objects) {
				object.geometry.applyMatrix4(matrix);
			}
		}
	}

	private _apply_matrix_to_objects(objects: Object3D[], src_object: Object3D | undefined) {
		if (!this._rot_and_index_pairs) {
			return;
		}
		if (src_object) {
			for (let object of objects) {
				object.matrix.copy(src_object.matrix);
				// TODO: This would not be required if objects generated in SOP has matrixAutoUpdate=false
				object.matrix.decompose(object.position, object.quaternion, object.scale);
			}
		}

		let pair: VectorNumberParamPair;
		for (let i = 0; i < this.pv.count; i++) {
			pair = this._rot_and_index_pairs[i];
			const matrix = this._matrix(pair[0].value, pair[1].value);
			for (let object of objects) {
				object.applyMatrix4(matrix);
			}
		}
	}

	private _t = new Vector3(0, 0, 0);
	private _s = new Vector3(1, 1, 1);
	private _scale = 1;
	private _matrix(r: Vector3, rot_order_index: number) {
		return this._core_transform.matrix(this._t, r, this._s, this._scale, ROTATION_ORDERS[rot_order_index]);
	}
}
