/**
 * computes forces based on position, velocity, forces and mass.
 *
 * @remarks
 *
 * This node is most useful inside particles systems
 *
 *
 */

import {Number3, PolyDictionary} from '../../../types/GlobalTypes';
import {TypedGlNode} from './_Base';
import {GlConnectionPointType, GlConnectionPoint} from '../utils/io/connections/Gl';
import {ShadersCollectionController} from './code/utils/ShadersCollectionController';
import {UniformGLDefinition, FunctionGLDefinition} from './utils/GLDefinition';
import {GlConstant} from '../../../core/geometry/GlConstant';
import {ThreeToGl} from '../../../core/ThreeToGl';
import Physics from './gl/physics.glsl';

enum AccelerationGlInput {
	POSITION = 'position',
	VELOCITY = 'velocity',
	MASS = 'mass',
	FORCE = 'force',
}
enum AccelerationGlOutput {
	POSITION = 'position',
	VELOCITY = 'velocity',
}

const INPUT_NAMES: AccelerationGlInput[] = [
	AccelerationGlInput.POSITION,
	AccelerationGlInput.VELOCITY,
	AccelerationGlInput.MASS,
	AccelerationGlInput.FORCE,
];
const OUTPUT_NAMES: AccelerationGlOutput[] = [AccelerationGlOutput.POSITION, AccelerationGlOutput.VELOCITY];
const INPUT_DEFAULT_VALUE: PolyDictionary<number | Number3> = {
	[AccelerationGlInput.POSITION]: [0, 0, 0],
	[AccelerationGlInput.VELOCITY]: [0, 0, 0],
	[AccelerationGlInput.MASS]: 1,
	[AccelerationGlInput.FORCE]: [0, -9.8, 0],
};

import {NodeParamsConfig} from '../utils/params/ParamsConfig';
class AccelerationGlParamsConfig extends NodeParamsConfig {
	// position = ParamConfig.VECTOR3([0, 0, 0]);
	// velocity = ParamConfig.VECTOR3([0, 0, 0]);
	// mass = ParamConfig.FLOAT(1);
	// force = ParamConfig.VECTOR3([0, -9.8, 0]);
}
const ParamsConfig = new AccelerationGlParamsConfig();

export class AccelerationGlNode extends TypedGlNode<AccelerationGlParamsConfig> {
	override paramsConfig = ParamsConfig;
	static override type() {
		return 'acceleration';
	}

	override initializeNode() {
		super.initializeNode();

		this.io.outputs.setNamedOutputConnectionPoints([
			new GlConnectionPoint(AccelerationGlOutput.POSITION, GlConnectionPointType.VEC3),
			new GlConnectionPoint(AccelerationGlOutput.VELOCITY, GlConnectionPointType.VEC3),
		]);

		this.io.connection_points.set_expected_input_types_function(this._expected_input_types.bind(this));
		this.io.connection_points.set_expected_output_types_function(this._expected_output_types.bind(this));
		this.io.connection_points.set_input_name_function(this._gl_input_name.bind(this));
		this.io.connection_points.set_output_name_function(this._gl_output_name.bind(this));
	}

	protected _expected_input_types(): GlConnectionPointType[] {
		const type: GlConnectionPointType =
			this.io.connection_points.first_input_connection_type() || GlConnectionPointType.VEC3;
		return [type, type, GlConnectionPointType.FLOAT, type];
	}
	private _expected_output_types(): GlConnectionPointType[] {
		const in_type = this._expected_input_types()[0];
		return [in_type, in_type];
	}
	protected _gl_input_name(index: number) {
		return INPUT_NAMES[index];
	}
	protected _gl_output_name(index: number) {
		return OUTPUT_NAMES[index];
	}
	override paramDefaultValue(name: string) {
		return INPUT_DEFAULT_VALUE[name];
	}
	override setLines(shaders_collection_controller: ShadersCollectionController) {
		const outputConnectionPoints = this.io.outputs.namedOutputConnectionPoints()
		if(!outputConnectionPoints){
			return
		}
		const var_type = outputConnectionPoints[0].type();
		const delta_definition = new UniformGLDefinition(this, GlConnectionPointType.FLOAT, GlConstant.DELTA_TIME);
		const function_definition = new FunctionGLDefinition(this, Physics);
		shaders_collection_controller.addDefinitions(this, [delta_definition, function_definition]);

		const input_position = ThreeToGl.any(this.variableForInput(AccelerationGlInput.POSITION));
		const input_velocity = ThreeToGl.any(this.variableForInput(AccelerationGlInput.VELOCITY));
		const input_mass = ThreeToGl.float(this.variableForInput(AccelerationGlInput.MASS));
		const input_force = ThreeToGl.any(this.variableForInput(AccelerationGlInput.FORCE));
		const position_result = this.glVarName(AccelerationGlOutput.POSITION);
		const velocity_result = this.glVarName(AccelerationGlOutput.VELOCITY);

		const velocity_args = [input_velocity, input_force, input_mass, GlConstant.DELTA_TIME].join(', ');
		const velocity_body_line = `${var_type} ${velocity_result} = velFromAccel(${velocity_args})`;

		const position_args = [input_position, velocity_result, GlConstant.DELTA_TIME].join(', ');
		const position_body_line = `${var_type} ${position_result} = posFromVel(${position_args})`;

		shaders_collection_controller.addBodyLines(this, [velocity_body_line, position_body_line]);
	}
}
