import Node from '../core/Node.js';
import AttributeNode from '../core/AttributeNode.js';
import VaryNode from '../core/VaryNode.js';
import ModelNode from '../accessors/ModelNode.js';
import MathNode from '../math/MathNode.js';
import OperatorNode from '../math/OperatorNode.js';
import { transformDirection } from '../functions/MathFunctions.js';

class PositionNode extends Node {

	static LOCAL = 'local';
	static WORLD = 'world';
	static VIEW = 'view';
	static VIEW_DIRECTION = 'viewDirection';

	constructor( scope = PositionNode.POSITION ) {

		super( 'vec3' );

		this.scope = scope;

	}

	generate( builder, output ) {

		const type = this.getType( builder );
		const nodeData = builder.getDataFromNode( this, builder.shaderStage );
		const scope = this.scope;

		let localPositionNode = nodeData.localPositionNode;

		if ( localPositionNode === undefined ) {

			localPositionNode = new AttributeNode( 'position', 'vec3' );

			nodeData.localPositionNode = localPositionNode;

		}

		let outputNode = localPositionNode;

		if ( scope === PositionNode.WORLD ) {

			let worldPositionNode = nodeData.worldPositionNode;

			if ( worldPositionNode === undefined ) {

				const vertexPositionNode = transformDirection.call( { dir: localPositionNode, matrix: new ModelNode( ModelNode.WORLD_MATRIX ) } );

				worldPositionNode = new VaryNode( vertexPositionNode );

				nodeData.worldPositionNode = worldPositionNode;

			}

			outputNode = worldPositionNode;

		} else if ( scope === PositionNode.VIEW ) {

			let viewPositionNode = nodeData.viewPositionNode;

			if ( viewPositionNode === undefined ) {

				const vertexPositionNode = new OperatorNode( '*', new ModelNode( ModelNode.VIEW_MATRIX ), localPositionNode );

				viewPositionNode = new VaryNode( vertexPositionNode );

				nodeData.viewPositionNode = viewPositionNode;

			}

			outputNode = viewPositionNode;

		} else if ( scope === PositionNode.VIEW_DIRECTION ) {

			let viewDirPositionNode = nodeData.viewDirPositionNode;

			if ( viewDirPositionNode === undefined ) {

				const vertexPositionNode = new MathNode( MathNode.NEGATE, new PositionNode( PositionNode.VIEW ) );

				viewDirPositionNode = new MathNode( MathNode.NORMALIZE, new VaryNode( vertexPositionNode ) );

				nodeData.viewDirPositionNode = viewDirPositionNode;

			}

			outputNode = viewDirPositionNode;

		}

		const positionSnipped = outputNode.build( builder, type );

		return builder.format( positionSnipped, type, output );

	}

}

export default PositionNode;
