/**
 * Allows easy position of lights, or any object around another one.
 *
 * @remarks
 * This node transforms its children with latitude and longitude controls, instead of typical translate and rotate. It makes it more intuitive to position objects such as lights.
 *
 */
import {TypedObjNode} from './_Base';
import {Group} from 'three/src/objects/Group';
import {FlagsControllerD} from '../utils/FlagsController';
import {AxesHelper} from 'three/src/helpers/AxesHelper';
import {HierarchyController} from './utils/HierarchyController';
import {Matrix4} from 'three/src/math/Matrix4';
import {Vector3} from 'three/src/math/Vector3';
import {MathUtils} from 'three/src/math/MathUtils';
import {Quaternion} from 'three/src/math/Quaternion';
import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig';
class PolarTransformObjParamConfig extends NodeParamsConfig {
	center = ParamConfig.VECTOR3([0, 0, 0]);
	longitude = ParamConfig.FLOAT(0, {
		range: [0, 360],
	});
	latitude = ParamConfig.FLOAT(0, {
		range: [-180, 180],
	});
	depth = ParamConfig.FLOAT(1, {
		range: [0, 10],
	});
}
const ParamsConfig = new PolarTransformObjParamConfig();

const HOOK_NAME = '_cook_main_without_inputs_when_dirty';
const AXIS_VERTICAL = new Vector3(0, 1, 0);
const AXIS_HORIZONTAL = new Vector3(-1, 0, 0);

export class PolarTransformObjNode extends TypedObjNode<Group, PolarTransformObjParamConfig> {
	params_config = ParamsConfig;
	static type() {
		return 'polarTransform';
	}
	readonly hierarchy_controller: HierarchyController = new HierarchyController(this);
	public readonly flags: FlagsControllerD = new FlagsControllerD(this);
	private _helper = new AxesHelper(1);

	create_object() {
		const group = new Group();
		group.matrixAutoUpdate = false;
		return group;
	}
	initializeNode() {
		this.hierarchy_controller.initializeNode();

		if (!this.dirtyController.has_hook(HOOK_NAME)) {
			this.dirtyController.addPostDirtyHook(HOOK_NAME, this._cook_main_without_inputs_when_dirty_bound);
		}

		this._updateHelperHierarchy();
		this._helper.matrixAutoUpdate = false;
		this.flags.display.add_hook(() => {
			this._updateHelperHierarchy();
		});
	}
	private _updateHelperHierarchy() {
		if (this.flags.display.active()) {
			this.object.add(this._helper);
		} else {
			this.object.remove(this._helper);
		}
	}

	// TODO: this will have to be checked via the parent, when I will have obj managers at lower levels than root
	private _cook_main_without_inputs_when_dirty_bound = this._cook_main_without_inputs_when_dirty.bind(this);
	private async _cook_main_without_inputs_when_dirty() {
		await this.cookController.cook_main_without_inputs();
	}

	private _centerMatrix = new Matrix4();
	private _longitudeMatrix = new Matrix4();
	private _latitudeMatrix = new Matrix4();
	private _depthMatrix = new Matrix4();
	private _fullMatrix = new Matrix4();
	private _decomposed = {
		t: new Vector3(),
		q: new Quaternion(),
		s: new Vector3(),
	};
	cook() {
		const object = this.object;

		this._centerMatrix.identity();
		this._longitudeMatrix.identity();
		this._latitudeMatrix.identity();
		this._depthMatrix.identity();
		this._centerMatrix.makeTranslation(this.pv.center.x, this.pv.center.y, this.pv.center.z);
		this._longitudeMatrix.makeRotationAxis(AXIS_VERTICAL, MathUtils.degToRad(this.pv.longitude));
		this._latitudeMatrix.makeRotationAxis(AXIS_HORIZONTAL, MathUtils.degToRad(this.pv.latitude));
		this._depthMatrix.makeTranslation(0, 0, this.pv.depth);
		this._fullMatrix
			.copy(this._centerMatrix)
			.multiply(this._longitudeMatrix)
			.multiply(this._latitudeMatrix)
			.multiply(this._depthMatrix);

		this._fullMatrix.decompose(this._decomposed.t, this._decomposed.q, this._decomposed.s);
		object.position.copy(this._decomposed.t);
		object.quaternion.copy(this._decomposed.q);
		object.scale.copy(this._decomposed.s);
		object.updateMatrix();

		this.cookController.end_cook();
	}
}
