const setup3dInfrastructure = () => {
    const initialWidth = window.innerWidth;
    const initialHeight = window.innerHeight;

    //
    // 1.1.1. Renderer
    const renderer = new THREE.WebGLRenderer({
        alpha: true,
        antialias: true,
        logarithmicDepthBuffer: true
    });

    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(initialWidth, initialHeight);
    renderer.setClearColor(0xff0000, 0);

    //
    // 1.1.2. Scene
    const scene = new THREE.Scene();

    //
    // 1.1.3. Camera
    /*
     fov — Camera frustum vertical field of view.
     aspect — Camera frustum aspect ratio.
     near — Camera frustum near plane.
     far — Camera frustum far plane.
    * */

    const params = {
        fov: 35,
        aspect: initialWidth / initialHeight,
        near: 1,
        far: 5500
    };
    const camera = new THREE.PerspectiveCamera(params.fov, params.aspect, params.near, params.far);

    function resize() {
        const width = window.innerWidth;
        const height = window.innerHeight;

        if (height < 1 || width < 1) return;

        // 3.1. Camera resize
        if (width > height) {
            this.camera.fov = 35;
        } else {
            this.camera.fov = (32 * height) / Math.min(width * 1.3, height);
        }
        this.camera.aspect = width / height;
        this.camera.updateProjectionMatrix();

        // 3.3. Renderer resize
        this.renderer.setSize(width, height);

        return this;
    }

    const infrastructure = {
        renderer,
        scene,
        camera
    };

    infrastructure.resize = resize.bind(infrastructure);

    return infrastructure;
};



const getCustomMaterial = (texture) => {

    const material = new THREE.MeshBasicMaterial({ color: 0xFFFFFF, map: texture, side: THREE.FrontSide });

    material.needsUpdate = true;

    return material;
};

const getEffectMaterial = (targetTexture, blendTexture) => new THREE.RawShaderMaterial({
    uniforms: {
        targetMap: {
            value: targetTexture
        },
        blendMap: {
            value: blendTexture
        }
    },
    vertexShader: `
      uniform mat4 projectionMatrix;
      uniform mat4 modelMatrix;
      uniform mat4 viewMatrix;

      attribute vec3 position;
      attribute vec3 normal;
      attribute vec2 uv;

      varying vec2 vUv;

      void main() {

        vUv = uv;

        gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 );

      }`,

    fragmentShader: `
    precision mediump float;

    uniform sampler2D targetMap;
    uniform sampler2D blendMap;

    varying vec2 vUv;

    void main() {

      vec4 targetTexel = texture2D( targetMap, vUv );
      vec4 blendTexel = texture2D( blendMap, vUv );
      
      // Hard Light
      vec3 mixColorRGB = vec3(greaterThan(blendTexel.rgb, vec3(0.5))) * (1.0 - (1.0 - targetTexel.rgb) * (1.0 - 2.0*(blendTexel.rgb - 0.5)));
      
      mixColorRGB += vec3(lessThanEqual(blendTexel.rgb, vec3(0.5))) * (targetTexel.rgb * (2.0 * blendTexel.rgb));

      gl_FragColor = vec4(mixColorRGB, 1.0);
      
    }`
}
);

const getPlaneLayer = (material, width, height) => {
    const geometry = new THREE.PlaneBufferGeometry(width, height);

    const plane = new THREE.Mesh(geometry, material);

    material.needsUpdate = true;

    return plane;
};


const appendRendererToDOMElement = (renderer, targetNode) => {
    targetNode.appendChild(renderer.domElement);
};

function start() {
    const targetTextureSrc = 'images/wall.jpg';
    const blendTextureSrc = 'images/stars.jpg';

    const hasWebGLSupport = (() => THREE.WEBGL.isWebGLAvailable() || THREE.WEBGL.isWebGL2Available())();

    const infrastructure = setup3dInfrastructure();

    infrastructure.camera.position.z = 1100;

    const targetTexture = new THREE.TextureLoader().load(targetTextureSrc);
    const blendTexture = new THREE.TextureLoader().load(blendTextureSrc);
    const material = getEffectMaterial(targetTexture, blendTexture);
    const plane = getPlaneLayer(material, 1024, 512);

    infrastructure.scene.add(plane);

    // Render:

    const onAnimationFrame = (infrastructure) => {
        if (infrastructure.resizeInProgress) infrastructure.resize();

        infrastructure.renderer.render(infrastructure.scene, infrastructure.camera);

        infrastructure.resizeInProgress = false;

        requestAnimationFrame(() => {
            onAnimationFrame(infrastructure);
        });
    };

    requestAnimationFrame(() => {
        onAnimationFrame(infrastructure);
    });

    //
    // Resize:
    window.addEventListener('resize', () => {
        if (infrastructure.resizeInProgress) return;
        infrastructure.resizeInProgress = true;
    });

    //
    // Add to DOM:
    appendRendererToDOMElement(infrastructure.renderer, document.getElementById('canvas-3d-container'));
}


start()


