import { TempNode } from '../core/TempNode.js';

class MathNode extends TempNode {

	constructor( a, bOrMethod, cOrMethod, method ) {

		super();

		this.a = a;
		typeof bOrMethod !== 'string' ? this.b = bOrMethod : method = bOrMethod;
		typeof cOrMethod !== 'string' ? this.c = cOrMethod : method = cOrMethod;

		this.method = method;

	}

	getNumInputs( /*builder*/ ) {

		switch ( this.method ) {

			case MathNode.MIX:
			case MathNode.CLAMP:
			case MathNode.REFRACT:
			case MathNode.SMOOTHSTEP:
			case MathNode.FACEFORWARD:

				return 3;

			case MathNode.MIN:
			case MathNode.MAX:
			case MathNode.MOD:
			case MathNode.STEP:
			case MathNode.REFLECT:
			case MathNode.DISTANCE:
			case MathNode.DOT:
			case MathNode.CROSS:
			case MathNode.POW:

				return 2;

			default:

				return 1;

		}

	}

	getInputType( builder ) {

		const a = builder.getTypeLength( this.a.getType( builder ) );
		const b = this.b ? builder.getTypeLength( this.b.getType( builder ) ) : 0;
		const c = this.c ? builder.getTypeLength( this.c.getType( builder ) ) : 0;

		if ( a > b && a > c ) {

			return this.a.getType( builder );

		} else if ( b > c ) {

			return this.b.getType( builder );

		}

		return this.c.getType( builder );

	}

	getType( builder ) {

		switch ( this.method ) {

			case MathNode.LENGTH:
			case MathNode.DISTANCE:
			case MathNode.DOT:

				return 'f';

			case MathNode.CROSS:

				return 'v3';

		}

		return this.getInputType( builder );

	}

	generate( builder, output ) {

		let a, b, c;
		const al = this.a ? builder.getTypeLength( this.a.getType( builder ) ) : 0,
			bl = this.b ? builder.getTypeLength( this.b.getType( builder ) ) : 0,
			cl = this.c ? builder.getTypeLength( this.c.getType( builder ) ) : 0,
			inputType = this.getInputType( builder ),
			nodeType = this.getType( builder );

		switch ( this.method ) {

			// 1 input

			case MathNode.NEGATE:

				return builder.format( '( -' + this.a.build( builder, inputType ) + ' )', inputType, output );

			case MathNode.INVERT:

				return builder.format( '( 1.0 - ' + this.a.build( builder, inputType ) + ' )', inputType, output );

				// 2 inputs

			case MathNode.CROSS:

				a = this.a.build( builder, 'v3' );
				b = this.b.build( builder, 'v3' );

				break;

			case MathNode.STEP:

				a = this.a.build( builder, al === 1 ? 'f' : inputType );
				b = this.b.build( builder, inputType );

				break;

			case MathNode.MIN:
			case MathNode.MAX:
			case MathNode.MOD:

				a = this.a.build( builder, inputType );
				b = this.b.build( builder, bl === 1 ? 'f' : inputType );

				break;

				// 3 inputs

			case MathNode.REFRACT:

				a = this.a.build( builder, inputType );
				b = this.b.build( builder, inputType );
				c = this.c.build( builder, 'f' );

				break;

			case MathNode.MIX:

				a = this.a.build( builder, inputType );
				b = this.b.build( builder, inputType );
				c = this.c.build( builder, cl === 1 ? 'f' : inputType );

				break;

				// default

			default:

				a = this.a.build( builder, inputType );
				if ( this.b ) b = this.b.build( builder, inputType );
				if ( this.c ) c = this.c.build( builder, inputType );

				break;

		}

		// build function call

		const params = [];
		params.push( a );
		if ( b ) params.push( b );
		if ( c ) params.push( c );

		const numInputs = this.getNumInputs( builder );

		if ( params.length !== numInputs ) {

			throw Error( `Arguments not match used in "${this.method}". Require ${numInputs}, currently ${params.length}.` );

		}

		return builder.format( this.method + '( ' + params.join( ', ' ) + ' )', nodeType, output );

	}

	copy( source ) {

		super.copy( source );

		this.a = source.a;
		this.b = source.b;
		this.c = source.c;
		this.method = source.method;

		return this;

	}

	toJSON( meta ) {

		let data = this.getJSONNode( meta );

		if ( ! data ) {

			data = this.createJSONNode( meta );

			data.a = this.a.toJSON( meta ).uuid;
			if ( this.b ) data.b = this.b.toJSON( meta ).uuid;
			if ( this.c ) data.c = this.c.toJSON( meta ).uuid;

			data.method = this.method;

		}

		return data;

	}

}

// 1 input

MathNode.RAD = 'radians';
MathNode.DEG = 'degrees';
MathNode.EXP = 'exp';
MathNode.EXP2 = 'exp2';
MathNode.LOG = 'log';
MathNode.LOG2 = 'log2';
MathNode.SQRT = 'sqrt';
MathNode.INV_SQRT = 'inversesqrt';
MathNode.FLOOR = 'floor';
MathNode.CEIL = 'ceil';
MathNode.NORMALIZE = 'normalize';
MathNode.FRACT = 'fract';
MathNode.SATURATE = 'saturate';
MathNode.SIN = 'sin';
MathNode.COS = 'cos';
MathNode.TAN = 'tan';
MathNode.ASIN = 'asin';
MathNode.ACOS = 'acos';
MathNode.ARCTAN = 'atan';
MathNode.ABS = 'abs';
MathNode.SIGN = 'sign';
MathNode.LENGTH = 'length';
MathNode.NEGATE = 'negate';
MathNode.INVERT = 'invert';

// 2 inputs

MathNode.MIN = 'min';
MathNode.MAX = 'max';
MathNode.MOD = 'mod';
MathNode.STEP = 'step';
MathNode.REFLECT = 'reflect';
MathNode.DISTANCE = 'distance';
MathNode.DOT = 'dot';
MathNode.CROSS = 'cross';
MathNode.POW = 'pow';

// 3 inputs

MathNode.MIX = 'mix';
MathNode.CLAMP = 'clamp';
MathNode.REFRACT = 'refract';
MathNode.SMOOTHSTEP = 'smoothstep';
MathNode.FACEFORWARD = 'faceforward';

MathNode.prototype.nodeType = 'Math';
MathNode.prototype.hashProperties = [ 'method' ];

export { MathNode };