import TempNode from '../core/TempNode.js';
import { addMethodChaining, nodeArray, nodeObject, nodeObjects, float } from '../tsl/TSLCore.js';

/**
 * This module represents the call of a {@link FunctionNode}. Developers are usually not confronted
 * with this module since they use the predefined TSL syntax `wgslFn` and `glslFn` which encapsulate
 * this logic.
 *
 * @augments TempNode
 */
class FunctionCallNode extends TempNode {

	static get type() {

		return 'FunctionCallNode';

	}

	/**
	 * Constructs a new function call node.
	 *
	 * @param {?FunctionNode} functionNode - The function node.
	 * @param {Object<string, Node>} [parameters={}] - The parameters for the function call.
	 */
	constructor( functionNode = null, parameters = {} ) {

		super();

		/**
		 * The function node.
		 *
		 * @type {?FunctionNode}
		 * @default null
		 */
		this.functionNode = functionNode;

		/**
		 * The parameters of the function call.
		 *
		 * @type {Object<string, Node>}
		 * @default {}
		 */
		this.parameters = parameters;

	}

	/**
	 * Sets the parameters of the function call node.
	 *
	 * @param {Object<string, Node>} parameters - The parameters to set.
	 * @return {FunctionCallNode} A reference to this node.
	 */
	setParameters( parameters ) {

		this.parameters = parameters;

		return this;

	}

	/**
	 * Returns the parameters of the function call node.
	 *
	 * @return {Object<string, Node>} The parameters of this node.
	 */
	getParameters() {

		return this.parameters;

	}

	/**
	 * Returns the type of this function call node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @returns {string} The type of this node.
	 */
	getNodeType( builder ) {

		return this.functionNode.getNodeType( builder );

	}

	/**
	 * Returns the function node of this function call node.
	 *
	 * @param {NodeBuilder} builder - The current node builder.
	 * @param {string} [name] - The name of the member.
	 * @returns {string} The type of the member.
	 */
	getMemberType( builder, name ) {

		return this.functionNode.getMemberType( builder, name );

	}

	generate( builder ) {

		const params = [];

		const functionNode = this.functionNode;

		const inputs = functionNode.getInputs( builder );
		const parameters = this.parameters;

		const generateInput = ( node, inputNode ) => {

			const type = inputNode.type;
			const pointer = type === 'pointer';

			let output;

			if ( pointer ) output = '&' + node.build( builder );
			else output = node.build( builder, type );

			return output;

		};

		if ( Array.isArray( parameters ) ) {

			if ( parameters.length > inputs.length ) {

				console.error( 'THREE.TSL: The number of provided parameters exceeds the expected number of inputs in \'Fn()\'.' );

				parameters.length = inputs.length;

			} else if ( parameters.length < inputs.length ) {

				console.error( 'THREE.TSL: The number of provided parameters is less than the expected number of inputs in \'Fn()\'.' );

				while ( parameters.length < inputs.length ) {

					parameters.push( float( 0 ) );

				}

			}

			for ( let i = 0; i < parameters.length; i ++ ) {

				params.push( generateInput( parameters[ i ], inputs[ i ] ) );

			}

		} else {

			for ( const inputNode of inputs ) {

				const node = parameters[ inputNode.name ];

				if ( node !== undefined ) {

					params.push( generateInput( node, inputNode ) );

				} else {

					console.error( `THREE.TSL: Input '${ inputNode.name }' not found in \'Fn()\'.` );

					params.push( generateInput( float( 0 ), inputNode ) );

				}

			}

		}

		const functionName = functionNode.build( builder, 'property' );

		return `${ functionName }( ${ params.join( ', ' ) } )`;

	}

}

export default FunctionCallNode;

export const call = ( func, ...params ) => {

	params = params.length > 1 || ( params[ 0 ] && params[ 0 ].isNode === true ) ? nodeArray( params ) : nodeObjects( params[ 0 ] );

	return nodeObject( new FunctionCallNode( nodeObject( func ), params ) );

};

addMethodChaining( 'call', call );
