/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @author       Felipe Alfonso <@bitnenfer>
 * @copyright    2020 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

var Class = require('../../../utils/Class');
var GetFastValue = require('../../../utils/object/GetFastValue');
var ShaderSourceFS = require('../shaders/BitmapMask-frag.js');
var ShaderSourceVS = require('../shaders/BitmapMask-vert.js');
var WebGLPipeline = require('../WebGLPipeline');

/**
 * @classdesc
 *
 * The Bitmap Mask Pipeline handles all of the bitmap mask rendering in WebGL for applying
 * alpha masks to Game Objects. It works by sampling two texture on the fragment shader and
 * using the fragments alpha to clip the region.
 *
 * The fragment shader it uses can be found in `shaders/src/BitmapMask.frag`.
 * The vertex shader it uses can be found in `shaders/src/BitmapMask.vert`.
 *
 * The default shader attributes for this pipeline are:
 *
 * `inPosition` (vec2, offset 0)
 *
 * The default shader uniforms for this pipeline are:
 *
 * `uResolution` (vec2)
 * `uMainSampler` (sampler2D)
 * `uMaskSampler` (sampler2D)
 * `uInvertMaskAlpha` (bool)
 *
 * @class BitmapMaskPipeline
 * @extends Phaser.Renderer.WebGL.WebGLPipeline
 * @memberof Phaser.Renderer.WebGL.Pipelines
 * @constructor
 * @since 3.0.0
 *
 * @param {Phaser.Types.Renderer.WebGL.WebGLPipelineConfig} config - The configuration options for this pipeline.
 */
var BitmapMaskPipeline = new Class({

    Extends: WebGLPipeline,

    initialize:

    function BitmapMaskPipeline (config)
    {
        config.fragShader = GetFastValue(config, 'fragShader', ShaderSourceFS),
        config.vertShader = GetFastValue(config, 'vertShader', ShaderSourceVS),
        config.vertexSize = GetFastValue(config, 'vertexSize', 8),
        config.vertexCapacity = GetFastValue(config, 'vertexCapacity', 3),
        config.vertices = GetFastValue(config, 'vertices', new Float32Array([ -1, 1, -1, -7, 7, 1 ]).buffer),
        config.attributes = GetFastValue(config, 'attributes', [
            {
                name: 'inPosition',
                size: 2,
                type: config.game.renderer.gl.FLOAT,
                normalized: false,
                offset: 0,
                enabled: false,
                location: -1
            }
        ]);

        WebGLPipeline.call(this, config);
    },

    /**
     * Called every time the pipeline is bound by the renderer.
     * Sets the shader program, vertex buffer and other resources.
     * Should only be called when changing pipeline.
     *
     * @method Phaser.Renderer.WebGL.Pipelines.BitmapMaskPipeline#bind
     * @since 3.50.0
     *
     * @param {boolean} [reset=false] - Should the pipeline be fully re-bound after a renderer pipeline clear?
     *
     * @return {this} This WebGLPipeline instance.
     */
    bind: function (reset)
    {
        if (reset === undefined) { reset = false; }

        WebGLPipeline.prototype.bind.call(this, reset);

        var renderer = this.renderer;
        var program = this.program;

        renderer.setFloat2(program, 'uResolution', this.width, this.height);
        renderer.setInt1(program, 'uMainSampler', 0);
        renderer.setInt1(program, 'uMaskSampler', 1);

        return this;
    },

    /**
     * Binds necessary resources and renders the mask to a separated framebuffer.
     * The framebuffer for the masked object is also bound for further use.
     *
     * @method Phaser.Renderer.WebGL.Pipelines.BitmapMaskPipeline#beginMask
     * @since 3.0.0
     *
     * @param {Phaser.GameObjects.GameObject} mask - GameObject used as mask.
     * @param {Phaser.GameObjects.GameObject} maskedObject - GameObject masked by the mask GameObject.
     * @param {Phaser.Cameras.Scene2D.Camera} camera - The camera rendering the current mask.
     */
    beginMask: function (mask, maskedObject, camera)
    {
        var renderer = this.renderer;
        var gl = this.gl;

        //  The renderable Game Object that is being used for the bitmap mask
        var bitmapMask = mask.bitmapMask;

        if (bitmapMask && gl)
        {
            renderer.flush();

            mask.prevFramebuffer = renderer.currentFramebuffer;

            renderer.setFramebuffer(mask.mainFramebuffer);

            gl.disable(gl.STENCIL_TEST);

            gl.clearColor(0, 0, 0, 0);

            gl.clear(gl.COLOR_BUFFER_BIT);

            if (renderer.currentCameraMask.mask !== mask)
            {
                renderer.currentMask.mask = mask;
                renderer.currentMask.camera = camera;
            }
        }
    },

    /**
     * The masked game objects framebuffer is unbound and its texture
     * is bound together with the mask texture and the mask shader and
     * a draw call with a single quad is processed. Here is where the
     * masking effect is applied.
     *
     * @method Phaser.Renderer.WebGL.Pipelines.BitmapMaskPipeline#endMask
     * @since 3.0.0
     *
     * @param {Phaser.GameObjects.GameObject} mask - GameObject used as a mask.
     */
    endMask: function (mask, camera)
    {
        var gl = this.gl;
        var renderer = this.renderer;

        //  The renderable Game Object that is being used for the bitmap mask
        var bitmapMask = mask.bitmapMask;

        if (bitmapMask && gl)
        {
            renderer.flush();

            //  First we draw the mask to the mask fb
            renderer.setFramebuffer(mask.maskFramebuffer);

            gl.clearColor(0, 0, 0, 0);
            gl.clear(gl.COLOR_BUFFER_BIT);

            renderer.setBlendMode(0, true);

            bitmapMask.renderWebGL(renderer, bitmapMask, 0, camera);

            renderer.flush();

            renderer.setFramebuffer(mask.prevFramebuffer);

            //  Is there a stencil further up the stack?
            var prev = renderer.getCurrentStencilMask();

            if (prev)
            {
                gl.enable(gl.STENCIL_TEST);

                prev.mask.applyStencil(renderer, prev.camera, true);
            }
            else
            {
                renderer.currentMask.mask = null;
            }

            //  Bind bitmap mask pipeline and draw
            renderer.pipelines.set(this);

            gl.activeTexture(gl.TEXTURE1);
            gl.bindTexture(gl.TEXTURE_2D, mask.maskTexture);

            gl.activeTexture(gl.TEXTURE0);
            gl.bindTexture(gl.TEXTURE_2D, mask.mainTexture);

            gl.uniform1i(gl.getUniformLocation(this.program, 'uInvertMaskAlpha'), mask.invertAlpha);

            //  Finally, draw a triangle filling the whole screen
            gl.drawArrays(this.topology, 0, 3);
        }
    }

});

module.exports = BitmapMaskPipeline;
