import {
	HalfFloatType,
	LinearFilter,
	NearestFilter,
	ShaderMaterial,
	Texture,
	UniformsUtils,
	WebGLRenderTarget
} from 'three';
import { Pass, FullScreenQuad } from './Pass.js';
import { SMAABlendShader, SMAAEdgesShader, SMAAWeightsShader } from '../shaders/SMAAShader.js';

/**
 * A pass for applying SMAA. Unlike {@link FXAAPass}, `SMAAPass` operates in
 * `linear-srgb` so this pass must be executed before {@link OutputPass}.
 *
 * ```js
 * const smaaPass = new SMAAPass();
 * composer.addPass( smaaPass );
 * ```
 *
 * @augments Pass
 * @three_import import { SMAAPass } from 'three/addons/postprocessing/SMAAPass.js';
 */
class SMAAPass extends Pass {

	/**
	 * Constructs a new SMAA pass.
	 */
	constructor( ) {

		super();

		// render targets

		this._edgesRT = new WebGLRenderTarget( 1, 1, {
			depthBuffer: false,
			type: HalfFloatType
		} );
		this._edgesRT.texture.name = 'SMAAPass.edges';

		this._weightsRT = new WebGLRenderTarget( 1, 1, {
			depthBuffer: false,
			type: HalfFloatType
		} );
		this._weightsRT.texture.name = 'SMAAPass.weights';

		// textures
		const scope = this;

		const areaTextureImage = new Image();
		areaTextureImage.src = this._getAreaTexture();
		areaTextureImage.onload = function () {

			// assigning data to HTMLImageElement.src is asynchronous (see #15162)
			scope._areaTexture.needsUpdate = true;

		};

		this._areaTexture = new Texture();
		this._areaTexture.name = 'SMAAPass.area';
		this._areaTexture.image = areaTextureImage;
		this._areaTexture.minFilter = LinearFilter;
		this._areaTexture.generateMipmaps = false;
		this._areaTexture.flipY = false;

		const searchTextureImage = new Image();
		searchTextureImage.src = this._getSearchTexture();
		searchTextureImage.onload = function () {

			// assigning data to HTMLImageElement.src is asynchronous (see #15162)
			scope._searchTexture.needsUpdate = true;

		};

		this._searchTexture = new Texture();
		this._searchTexture.name = 'SMAAPass.search';
		this._searchTexture.image = searchTextureImage;
		this._searchTexture.magFilter = NearestFilter;
		this._searchTexture.minFilter = NearestFilter;
		this._searchTexture.generateMipmaps = false;
		this._searchTexture.flipY = false;

		// materials - pass 1

		this._uniformsEdges = UniformsUtils.clone( SMAAEdgesShader.uniforms );

		this._materialEdges = new ShaderMaterial( {
			defines: Object.assign( {}, SMAAEdgesShader.defines ),
			uniforms: this._uniformsEdges,
			vertexShader: SMAAEdgesShader.vertexShader,
			fragmentShader: SMAAEdgesShader.fragmentShader
		} );

		// materials - pass 2

		this._uniformsWeights = UniformsUtils.clone( SMAAWeightsShader.uniforms );

		this._uniformsWeights[ 'tDiffuse' ].value = this._edgesRT.texture;
		this._uniformsWeights[ 'tArea' ].value = this._areaTexture;
		this._uniformsWeights[ 'tSearch' ].value = this._searchTexture;

		this._materialWeights = new ShaderMaterial( {
			defines: Object.assign( {}, SMAAWeightsShader.defines ),
			uniforms: this._uniformsWeights,
			vertexShader: SMAAWeightsShader.vertexShader,
			fragmentShader: SMAAWeightsShader.fragmentShader
		} );

		// materials - pass 3

		this._uniformsBlend = UniformsUtils.clone( SMAABlendShader.uniforms );
		this._uniformsBlend[ 'tDiffuse' ].value = this._weightsRT.texture;

		this._materialBlend = new ShaderMaterial( {
			uniforms: this._uniformsBlend,
			vertexShader: SMAABlendShader.vertexShader,
			fragmentShader: SMAABlendShader.fragmentShader
		} );

		this._fsQuad = new FullScreenQuad( null );

	}

	/**
	 * Performs the SMAA pass.
	 *
	 * @param {WebGLRenderer} renderer - The renderer.
	 * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
	 * destination for the pass.
	 * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
	 * previous pass from this buffer.
	 * @param {number} deltaTime - The delta time in seconds.
	 * @param {boolean} maskActive - Whether masking is active or not.
	 */
	render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive*/ ) {

		// pass 1

		this._uniformsEdges[ 'tDiffuse' ].value = readBuffer.texture;

		this._fsQuad.material = this._materialEdges;

		renderer.setRenderTarget( this._edgesRT );
		if ( this.clear ) renderer.clear();
		this._fsQuad.render( renderer );

		// pass 2

		this._fsQuad.material = this._materialWeights;

		renderer.setRenderTarget( this._weightsRT );
		if ( this.clear ) renderer.clear();
		this._fsQuad.render( renderer );

		// pass 3

		this._uniformsBlend[ 'tColor' ].value = readBuffer.texture;

		this._fsQuad.material = this._materialBlend;

		if ( this.renderToScreen ) {

			renderer.setRenderTarget( null );
			this._fsQuad.render( renderer );

		} else {

			renderer.setRenderTarget( writeBuffer );
			if ( this.clear ) renderer.clear();
			this._fsQuad.render( renderer );

		}

	}

	/**
	 * Sets the size of the pass.
	 *
	 * @param {number} width - The width to set.
	 * @param {number} height - The height to set.
	 */
	setSize( width, height ) {

		this._edgesRT.setSize( width, height );
		this._weightsRT.setSize( width, height );

		this._materialEdges.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height );
		this._materialWeights.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height );
		this._materialBlend.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height );

	}

	/**
	 * Frees the GPU-related resources allocated by this instance. Call this
	 * method whenever the pass is no longer used in your app.
	 */
	dispose() {

		this._edgesRT.dispose();
		this._weightsRT.dispose();

		this._areaTexture.dispose();
		this._searchTexture.dispose();

		this._materialEdges.dispose();
		this._materialWeights.dispose();
		this._materialBlend.dispose();

		this._fsQuad.dispose();

	}

	// internals

	_getAreaTexture() {

		return 'data:image/png;base64,[encoded data, 44272 characters]';

	}

	_getSearchTexture() {

		return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEIAAAAhCAAAAABIXyLAAAAAOElEQVRIx2NgGAWjYBSMglEwEICREYRgFBZBqDCSLA2MGPUIVQETE9iNUAqLR5gIeoQKRgwXjwAAGn4AtaFeYLEAAAAASUVORK5CYII=';

	}

}

export { SMAAPass };
