import * as THREE from 'three';
import { PostEffect as PostEffectParameter } from '../assets/marmosets/MviewFiles'
import { Pass } from 'three/examples/jsm/postprocessing/Pass';


const parameterTest: PostEffectParameter = {

    toneMap: 2,         // 0 - Linear  1 - Reinhard  2 - Filmic(Hejl)

    brightness: [1, 1, 1, 6],

    contrast: [1, 1, 1, 1],

    bias: [1, 1, 1, 0.5],

    saturation: [1, 1, 1, 1],


    //Sharpen 

    sharpen: 0.0,

    sharpenLimit: 1.0,


    //Bloom

    bloomColor: [1, 0, 0, 1],

    bloomSize: 0.0625,


    //Vignette

    vignette: [0, 0, 0, 0],

    vignetteCurve: 0,


    //Grain

    grain: 0.5,

    grainSharpness: 1,

    
    colorLUT: []

}

let unifroms = {

    //Common
    uTime: { value: 0 },
    
    //Input
    tInput: { value: null },
    
    //Bloom
    tBloom: { value: new THREE.Texture },

    //Grain
    tGrain: { value: new THREE.Texture },

    //Color LUT
    tLUT: { value: new THREE.Texture },

    uScale: { value: new THREE.Vector3 },
    uBias: { value: new THREE.Vector3 },
    uSaturation: { value : new THREE.Vector3 },
    uSharpenKernel: { value: new THREE.Vector4 },
    uSharpness: { value: new THREE.Vector3 },
    uBloomColor: { value: new THREE.Vector3 },
    uVignetteAspect: { value: new THREE.Vector4 },
    uVignette: { value: new THREE.Vector4 },
    uGrainCoord: { value: new THREE.Vector4 },
    uGrainScaleBias: { value: new THREE.Vector2 }

}


export default class MarmosetPostPass implements Pass  {

    enabled: boolean;
    needsSwap: boolean;
    clear: boolean;
    renderToScreen: boolean;

    fsQuad: Pass.FullScreenQuad
    material: THREE.ShaderMaterial;

    viewSize: THREE.Vector2;


    //Bloom
    bloomTexture: THREE.WebGLRenderTarget
    bloomFsQuad: Pass.FullScreenQuad;
    bloomUniform = {

        tInput: { value: new THREE.Texture },
        uKernel: { value: [] }

    }
    shrinkTexture: THREE.WebGLRenderTarget
    shrinkFsQuad: Pass.FullScreenQuad
    shrinkUniform = {


    }

    //Film Grain
    noiseTexture: THREE.DataTexture;


    //LUT 
    LUTTexture: THREE.DataTexture;
 
    constructor() {

        this.enabled = true;
        this.needsSwap = false;
        this.clear = true;
        this.renderToScreen = true;

    
        this.viewSize = new THREE.Vector2();


        //Bloom
        this.bloomTexture = new THREE.WebGLRenderTarget(256, 256, { format: THREE.RGBAFormat });
        unifroms.tBloom.value = this.bloomTexture.texture;
        this.bloomFsQuad = new Pass.FullScreenQuad(new THREE.ShaderMaterial({

            defines: {

                BLOOM_SAMPLES: 64
            },

            uniforms: this.bloomUniform,

            vertexShader: `

                varying vec2 j;

                void main() {

                    j = uv;
                    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );

                }

            `,
            fragmentShader: `precision mediump float;

                uniform sampler2D tInput;
                uniform vec4 uKernel[BLOOM_SAMPLES];
                
                varying highp vec2 j;
                
                
                void main(void)
                {
                    vec3 c = vec3(0.0,0.0,0.0);
                    
                    for( int k=0;k < BLOOM_SAMPLES; ++k )
                    {
                        vec3 l = uKernel[k].xyz;
                        vec3 m = texture2D(tInput,j+l.xy).xyz;
                        m = max(m,vec3(0.0,0.0,0.0));
                        c += m*l.z;
                    }
                    
                    gl_FragColor.xyz = c;
                    gl_FragColor.w = 0.0;
                }
            
            `

        }))
        this.shrinkTexture = new THREE.WebGLRenderTarget(256,256)
        this.shrinkFsQuad = new Pass.FullScreenQuad( new THREE.ShaderMaterial({

            vertexShader: `

                varying vec2 vUv;

                void main() {

                    vUv = uv;
                    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );

                }

            `,

            fragmentShader: `
                
                varying mediump vec2 vUv;
                
                void main(void)
                {
                    float o = 0.25/256.0;

                    gl_FragColor = 0.25 * (
                        texture2D(tInput, vUv + vec2(o,o))
                        + texture2D(tInput, vUv + vec2(o,-o))
                        + texture2D(tInput, vUv + vec2(-o,o))
                        + texture2D(tInput, vUv + vec2(-o,-o))
                    ); 
                }
            `

        }));

        
        //Film Grain
        let noise = new Uint8Array(128*128);
        for(let i = 0; i < 16384; i++ ) { 

            noise[i] = 0.5 * (255 * Math.random() + 255 * Math.random()) 

        };
        this.noiseTexture = new THREE.DataTexture( noise, 128, 128, THREE.LuminanceFormat, THREE.UnsignedByteType, THREE.UVMapping, THREE.RepeatWrapping, THREE.RepeatWrapping);
        this.noiseTexture.needsUpdate = true;
        unifroms.tGrain.value = this.noiseTexture;


        //LUT
        this.LUTTexture = new THREE.DataTexture( noise, noise.length / 3 | 0 , 1, THREE.RGBFormat, THREE.UnsignedByteType);
        unifroms.tLUT.value = this.LUTTexture;

    
        this.build(parameterTest)
        this.material = this.generateMaterial();
        this.fsQuad = new Pass.FullScreenQuad( this.material );

    }


    build( parameters: PostEffectParameter ) {

        this.computerParameters( parameters, this.viewSize.x, this.viewSize.y );
   
    }

    private computerParameters( parameters: PostEffectParameter, screenWidth: number, screenHeight: number ) {

        unifroms.uScale.value.set( parameters.contrast[0] * parameters.contrast[3], parameters.contrast[1] * parameters.contrast[3], parameters.contrast[2] * parameters.contrast[3] );
        unifroms.uBias.value.set( parameters.bias[0] * parameters.bias[3], parameters.bias[1] * parameters.bias[3], parameters.bias[2] * parameters.bias[3] );
        unifroms.uBias.value.x = -unifroms.uBias.value.x * unifroms.uScale.value.x + unifroms.uBias.value.x;
        unifroms.uBias.value.y = -unifroms.uBias.value.y * unifroms.uScale.value.y + unifroms.uBias.value.y;
        unifroms.uBias.value.z = -unifroms.uBias.value.z * unifroms.uScale.value.z + unifroms.uBias.value.z;
        
        
        let brightness = new THREE.Vector3( parameters.brightness[0] * parameters.brightness[3], parameters.brightness[1] * parameters.brightness[3], parameters.brightness[2] * parameters.brightness[3] )
        unifroms.uScale.value.x *= brightness.x;
        unifroms.uScale.value.y *= brightness.y;
        unifroms.uScale.value.z *= brightness.z;

        unifroms.uBias.value.set( parameters.bias[0] * brightness.x, parameters.bias[1] * brightness.y, parameters.bias[2] * brightness.z );
        unifroms.uSaturation.value.set( parameters.saturation[0] * parameters.saturation[3], parameters.saturation[1] * parameters.saturation[3], parameters.saturation[2] * parameters.saturation[3] );
        unifroms.uBloomColor.value.set( parameters.bloomColor[0] * parameters.bloomColor[3], parameters.bloomColor[1] * parameters.bloomColor[3], parameters.bloomColor[2] * parameters.bloomColor[3] );
        
        unifroms.uSharpness.value.set( parameters.sharpen, 0.25 * parameters.sharpen, parameters.sharpenLimit );
        unifroms.uSharpenKernel.value.set( 1 / screenWidth, 0, 0, 1 / screenHeight );


        let e = screenWidth > screenHeight ? screenWidth : screenHeight;
       
        unifroms.uVignetteAspect.value.set( screenWidth / e, screenHeight / e, 0.5* screenWidth / e, 0.5 * screenHeight / e );
        unifroms.uVignette.value.set( 2 * ( 1 - parameters.vignette[0] ) * parameters.vignette[3], 2 * ( 1 - parameters.vignette[0] ) * parameters.vignette[3], 2 * ( 1 - parameters.vignette[0] ) * parameters.vignette[3], parameters.vignetteCurve )

       
        var stepX = 1 / 128,//this.noiseTexture.width, 
            stepY = 1 / 128,//this.noiseTexture.height, 
        
            g = 1 - parameters.grainSharpness;

        unifroms.uGrainCoord.value.set( stepX * screenWidth, stepY * screenHeight, 0.5* stepX * g, 0.5 * stepY * g);
        unifroms.uGrainScaleBias.value.set( 2 * parameters.grain, -parameters.grain);
       

     

        return unifroms;

    }

    private generateMaterial() {

        let material = new THREE.ShaderMaterial();

        material.uniforms = unifroms;

        material.defines = {

            BLOOM: '',
            BLOOM_SAMPLES: 64,

            GRAIN: '',
            //COLOR_LUT: ''
        }

        material.vertexShader = `

            varying vec2 j;

            void main() {

                j = uv;
                gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );

            }

        `

        material.fragmentShader = `
        
            precision mediump float;

            uniform float uTime;

            uniform sampler2D tInput;
            
            #ifdef BLOOM
            uniform sampler2D tBloom;
            #endif 
            
            #ifdef GRAIN
            uniform sampler2D tGrain;
            #endif 
            
            #ifdef COLOR_LUT
            uniform sampler2D tLUT;
            #endif 
            
            uniform vec3 uScale;
            uniform vec3 uBias;
            uniform vec3 uSaturation;
            uniform vec4 uSharpenKernel;
            uniform vec3 uSharpness;
            uniform vec3 uBloomColor;
            uniform vec4 uVignetteAspect;
            uniform vec4 uVignette;
            uniform vec4 uGrainCoord;
            uniform vec2 uGrainScaleBias;
            
            varying vec2 j;
            
            vec3 iY(vec3 c)
            {
                vec3 iZ=sqrt(c);
                return(iZ-iZ*c)+c*(0.4672*c+vec3(0.5328));
            }
            
            void main(void)
            {
                vec4 jc=texture2D(tInput,j);
                vec3 c=jc.xyz;
            
                #ifdef SHARPEN
                vec3 hM=texture2D(tInput,j+uSharpenKernel.xy).xyz;
                hM+=texture2D(tInput,j-uSharpenKernel.xy).xyz;
                hM+=texture2D(tInput,j+uSharpenKernel.zw).xyz;
                hM+=texture2D(tInput,j-uSharpenKernel.zw).xyz;
                vec3 jd = uSharpness.x * c - uSharpness.y * hM;
                c += clamp(jd,-uSharpness.z,uSharpness.z);
                #endif 
            
                #ifdef BLOOM
                c+=uBloomColor*texture2D(tBloom,j).xyz;
                #endif 
            
                #ifdef VIGNETTE
                vec2 je=j*uVignetteAspect.xy-uVignetteAspect.zw;
                vec3 v=clamp(vec3(1.0,1.0,1.0)-uVignette.xyz*dot(je,je),0.0,1.0);
                vec3 jf=v*v;
                jf*=v;
                c*=mix(v,jf,uVignette.w);
                #endif 
            
                #ifdef SATURATION
                float gray=dot(c,vec3(0.3,0.59,0.11));
                c=mix(vec3(gray,gray,gray),c,uSaturation);
                #endif 
            
                #ifdef CONTRAST
                c=c*uScale+uBias;
                #endif 
            
                #ifdef GRAIN
                float jh= uGrainScaleBias.x*texture2D(tGrain,j*uGrainCoord.xy+uGrainCoord.zw).x+uGrainScaleBias.y;       
                c+= c * jh;
                #endif 
            
                #ifdef REINHARD
                {
                    c*=1.8;
                    float ji=dot(c,vec3(0.3333));
                    c=clamp(c/(1.0+ji),0.0,1.0);
                }
                #elif defined(HEJL)
                {
                    const highp float jj=0.22,jk=0.3,jl=.1,jm=0.2,jn=.01,jo=0.3;
                    const highp float ju=1.25;
                    highp vec3 eO=max(vec3(0.0),c-vec3(.004));
                    c=(eO*((ju*jj)*eO+ju*vec3(jl*jk,jl*jk,jl*jk))+ju*vec3(jm*jn,jm*jn,jm*jn))/(eO*(jj*eO+vec3(jk,jk,jk))+vec3(jm*jo,jm*jo,jm*jo))-ju*vec3(jn/jo,jn/jo,jn/jo);
                }
                #elif defined(ACES)
                {
                    vec3 a=c*(c+0.0245786)-0.000090537;
                    vec3 b=c*(0.983729*c+0.4329510)+0.238081;
                    c=a/b;
                }
                #endif 
            
                #ifdef COLOR_LUT
                c=clamp(c,0.0,1.0);
                c=(255.0/256.0)*c+vec3(0.5/256.0);
                c.x=texture2D(tLUT,c.xx).x;
                c.y=texture2D(tLUT,c.yy).y;
                c.z=texture2D(tLUT,c.zz).z;
                c*=c;
                #endif 
            
                gl_FragColor.xyz=iY(c);
                gl_FragColor.w=jc.w;

                vec3 color = texture2D(tInput, j).xyz;
                //color.r = abs(sin(uTime));

                //gl_FragColor = texture2D(tBloom, j);
            }
        

        `
        material.needsUpdate = true

        return material;

    }


    setSize(width: number, height: number): void {
        //throw new Error("Method not implemented.");

        //this.computerParameters( parameterTest, this.viewSize.x, this.viewSize.y );

    }

    
    render(renderer: THREE.WebGLRenderer, writeBuffer: THREE.WebGLRenderTarget, readBuffer: THREE.WebGLRenderTarget, deltaTime: number, maskActive: boolean): void {
        //throw new Error("Method not implemented.");

        renderer.getSize(this.viewSize)        
        this.computerParameters( parameterTest, this.viewSize.x, this.viewSize.y );

        writeBuffer.setSize(this.viewSize.x, this.viewSize.y);
        readBuffer.setSize(this.viewSize.x, this.viewSize.y);


        //Render Bloom
        this.bloomUniform.tInput.value = readBuffer.texture;
        renderer.setRenderTarget(this.bloomTexture);
        this.bloomFsQuad.render(renderer);

        
        //Others
        this.material.uniforms['uTime'].value += deltaTime
        this.material.uniforms['tInput'].value = readBuffer.texture;
        this.material.uniforms['tBloom'].value = this.bloomTexture.texture;
        
        

        if ( this.renderToScreen ) {

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

		} else {

			renderer.setRenderTarget( writeBuffer );

			if ( this.clear ) renderer.clear();

			this.fsQuad.render( renderer );

        }
        

    }

    
}
