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


const LUT = [13,13,13,15,21,21,22,27,27,27,33,33,31,37,37,35,41,41,39,44,44,42,48,48,45,51,51,48,54,54,51,56,56,53,59,59,56,61,61,58,64,64,60,66,66,63,68,68,65,71,71,67,73,73,69,75,75,71,77,77,73,79,79,75,81,81,77,83,83,79,85,85,81,87,87,83,88,88,85,90,90,86,92,92,88,94,94,90,95,95,91,97,97,93,99,99,95,100,100,96,102,102,98,104,104,100,105,105,101,107,107,103,108,108,104,110,110,106,111,111,108,113,113,109,115,115,111,116,116,112,118,118,114,119,119,115,120,120,117,122,122,118,123,123,119,125,125,121,126,126,122,128,128,124,129,129,125,130,130,127,132,132,128,133,133,129,135,135,131,136,136,132,137,137,134,139,139,135,140,140,136,141,141,138,143,143,139,144,144,140,145,145,142,147,147,143,148,148,144,149,149,146,150,150,147,152,152,148,153,153,149,154,154,151,156,156,152,157,157,153,158,158,155,159,159,156,161,161,157,162,162,158,163,163,160,164,164,161,165,165,162,167,167,163,168,168,164,169,169,166,170,170,167,171,171,168,173,173,169,174,174,170,175,175,172,176,176,173,177,177,174,178,178,175,179,179,176,181,181,178,182,182,179,183,183,180,184,184,181,185,185,182,186,186,183,187,187,184,189,189,186,190,190,187,191,191,188,192,192,189,193,193,190,194,194,191,195,195,192,196,196,193,197,197,194,198,198,196,199,199,197,200,200,198,202,202,199,203,203,200,204,204,201,205,205,202,206,206,203,207,207,204,208,208,205,209,209,206,210,210,207,211,211,208,212,212,209,213,213,211,214,214,212,215,215,213,216,216,214,217,217,215,218,218,216,219,219,217,220,220,218,221,221,219,222,222,220,223,223,221,224,224,222,225,225,223,226,226,224,227,227,225,228,228,226,229,229,227,230,230,228,231,231,229,232,232,230,233,233,231,234,234,232,235,235,233,236,236,234,236,236,235,237,237,236,238,238,237,239,239,238,240,240,239,241,241,239,242,242,240,243,243,241,244,244,242,245,245,243,246,246,244,247,247,245,248,248,246,248,248,247,249,249,248,250,250,249,251,251,250,252,252,251,253,253,252,254,254,253,255,255,253,255,255,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255]
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: 5.0,


    //Bloom

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

    bloomSize: 0.0,


    //Vignette

    vignette: [0.0183006, 0.206881, 0.0884645, 2],

    vignetteCurve: 0.987163,


    //Grain

    grain: 0.337,

    grainSharpness: 0.693,

    
    colorLUT: LUT

}

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
    bloomTextures: THREE.WebGLRenderTarget[]
    bloomFsQuad: Pass.FullScreenQuad;
    bloomUniform = {

        tInput: { value: new THREE.Texture },
        uKernel: { value: new Float32Array(64*4) }

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

        tInput: { value: new THREE.Texture }
    
    }

    //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.bloomTextures = [];
        this.bloomTextures.push( new THREE.WebGLRenderTarget(256, 256, { format: THREE.RGBAFormat }) );
        this.bloomTextures.push( new THREE.WebGLRenderTarget(256, 256, { format: THREE.RGBAFormat }) );



        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: `
                
                uniform sampler2D tInput;

                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( new Uint8Array(parameterTest.colorLUT) ,parameterTest.colorLUT.length/3 , 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[1] ) * parameters.vignette[3], 2 * ( 1 - parameters.vignette[2] ) * 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: '',
            BLOOM_SAMPLES: 64,

            //Film Grain
            //GRAIN: '',

            //Sharpen
            //SHARPEN: '',

            //Vignette
            //VIGNETTE: '',

            //Tone Mapping
            ACES: '',     // REINHARD | HEJL | ACES

            //LUT  
            COLOR_LUT: '',

            SATURATION: '',

            //CONTRAST:''
        }

        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
        const SAMPLER_COUNT = 64;
        let valueSum = 0;
        let samplers = this.bloomUniform.uKernel.value;

        this.shrinkUniform.tInput.value = readBuffer.texture;
        renderer.setRenderTarget(this.bloomTextures[1]);
        this.shrinkFsQuad.render(renderer);
    
        for(let i = 0; i < SAMPLER_COUNT; i++ )
        {

            let step = 2 * i / (SAMPLER_COUNT - 1) - 1;
            let value = 4 * step;
            value = Math.exp(-0.5 * value * value) / 2.50662827463;
            
            valueSum += value;

            samplers[4 * i + 0] = step;
            samplers[4 * i + 1] = 0;
            samplers[4 * i + 2] = value;
            samplers[4 * i + 3] = 0;
 
        }
        for(let i = 0; i < SAMPLER_COUNT; i++ ) 
        {
            samplers[4 * i + 2] /= valueSum;
        }

        this.bloomUniform.tInput.value = this.bloomTextures[1].texture
        renderer.setRenderTarget(this.bloomTextures[0]);
        this.bloomFsQuad.render(renderer)


        for(let i = 0; i < SAMPLER_COUNT; i++ )
        {
            let s = samplers[4 * i + 0];
            s *= this.viewSize.x / this.viewSize.y;
            samplers[4 * i + 0] = 0;
            samplers[4 * i + 1] = s;
        }

        this.bloomUniform.tInput.value = this.bloomTextures[0].texture;
        renderer.setRenderTarget(this.bloomTextures[1]);
        this.bloomFsQuad.render(renderer);
  


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

        if ( this.renderToScreen ) {

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

		} else {

			renderer.setRenderTarget( writeBuffer );

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

			this.fsQuad.render( renderer );

        }
        

    }

    
}
