import { Vector3 } from 'three';

import { Vector3Node } from '../inputs/Vector3Node.js';

class VelocityNode extends Vector3Node {

	constructor( target, params ) {

		super();

		this.params = {};

		this.velocity = new Vector3();

		this.setTarget( target );
		this.setParams( params );

	}

	getReadonly( /*builder*/ ) {

		return false;

	}

	setParams( params ) {

		switch ( this.params.type ) {

			case 'elastic':

				delete this.moment;

				delete this.speed;
				delete this.springVelocity;

				delete this.lastVelocity;

				break;

		}

		this.params = params || {};

		switch ( this.params.type ) {

			case 'elastic':

				this.moment = new Vector3();

				this.speed = new Vector3();
				this.springVelocity = new Vector3();

				this.lastVelocity = new Vector3();

				break;

		}

	}

	setTarget( target ) {

		if ( this.target ) {

			delete this.position;
			delete this.oldPosition;

		}

		this.target = target;

		if ( target ) {

			this.position = target.getWorldPosition( this.position || new Vector3() );
			this.oldPosition = this.position.clone();

		}

	}

	updateFrameVelocity( /*frame*/ ) {

		if ( this.target ) {

			this.position = this.target.getWorldPosition( this.position || new Vector3() );
			this.velocity.subVectors( this.position, this.oldPosition );
			this.oldPosition.copy( this.position );

		}

	}

	updateFrame( frame ) {

		this.updateFrameVelocity( frame );

		switch ( this.params.type ) {

			case 'elastic':

				// convert to real scale: 0 at 1 values
				const deltaFps = frame.delta * ( this.params.fps || 60 );

				const spring = Math.pow( this.params.spring, deltaFps ),
					damping = Math.pow( this.params.damping, deltaFps );

				// fix relative frame-rate
				this.velocity.multiplyScalar( Math.exp( - this.params.damping * deltaFps ) );

				// elastic
				this.velocity.add( this.springVelocity );
				this.velocity.add( this.speed.multiplyScalar( damping ).multiplyScalar( 1 - spring ) );

				// speed
				this.speed.subVectors( this.velocity, this.lastVelocity );

				// spring velocity
				this.springVelocity.add( this.speed );
				this.springVelocity.multiplyScalar( spring );

				// moment
				this.moment.add( this.springVelocity );

				// damping
				this.moment.multiplyScalar( damping );

				this.lastVelocity.copy( this.velocity );
				this.value.copy( this.moment );

				break;

			default:

				this.value.copy( this.velocity );

		}

	}

	copy( source ) {

		super.copy( source );

		if ( source.target ) this.setTarget( source.target );

		this.setParams( source.params );

		return this;

	}

	toJSON( meta ) {

		const data = super.toJSON( meta );

		if ( this.target ) data.target = this.target.uuid;

		// clone params
		data.params = JSON.parse( JSON.stringify( this.params ) );

		return data;

	}

}

VelocityNode.prototype.nodeType = 'Velocity';

export { VelocityNode };
