import Filter from "../Filter";
import { defaultVert } from "../defaultVerts";
import { Point } from "../../math";
import FilterManager from "../../renderers/managers/FilterManager";
import RenderTarget from "../../renderers/renderTarget/RenderTarget";

const fragmentClamp = [
    "precision mediump float;",
    'varying vec2 vTextureCoord;',
    'uniform sampler2D uSampler;',

    'uniform vec2 uOffset;',
    'uniform vec4 filterClamp;',

    'void main(void)',
    '{',
    'vec4 color = vec4(0.0);',

    // Sample top left pixel
    'color += texture2D(uSampler, clamp(vec2(vTextureCoord.x - uOffset.x, vTextureCoord.y + uOffset.y), filterClamp.xy, filterClamp.zw));',

    // Sample top right pixel
    'color += texture2D(uSampler, clamp(vec2(vTextureCoord.x + uOffset.x, vTextureCoord.y + uOffset.y), filterClamp.xy, filterClamp.zw));',

    // Sample bottom right pixel
    'color += texture2D(uSampler, clamp(vec2(vTextureCoord.x + uOffset.x, vTextureCoord.y - uOffset.y), filterClamp.xy, filterClamp.zw));',

    // Sample bottom left pixel
    'color += texture2D(uSampler, clamp(vec2(vTextureCoord.x - uOffset.x, vTextureCoord.y - uOffset.y), filterClamp.xy, filterClamp.zw));',

    // Average
    'color *= 0.25;',

    'gl_FragColor = color;',
    '}'

].join("\n")

const fragment = [
    "precision mediump float;",
    'varying vec2 vTextureCoord;',
    'uniform sampler2D uSampler;',

    'uniform vec2 uOffset;',

    'void main(void)',
    '{',
    'vec4 color = vec4(0.0);',

    // Sample top left pixel
    'color += texture2D(uSampler, vec2(vTextureCoord.x - uOffset.x, vTextureCoord.y + uOffset.y));',

    // Sample top right pixel
    'color += texture2D(uSampler, vec2(vTextureCoord.x + uOffset.x, vTextureCoord.y + uOffset.y));',

    // Sample bottom right pixel
    'color += texture2D(uSampler, vec2(vTextureCoord.x + uOffset.x, vTextureCoord.y - uOffset.y));',

    // Sample bottom left pixel
    'color += texture2D(uSampler, vec2(vTextureCoord.x - uOffset.x, vTextureCoord.y - uOffset.y));',

    // Average
    'color *= 0.25;',

    'gl_FragColor = color;',
    '}'
].join("\n")

/**
 * A much faster blur than Gaussian blur, but more complicated to use.<br>
 * ![original](../tools/screenshots/dist/original.png)![filter](../tools/screenshots/dist/kawase-blur.png)
 *
 * @see https://software.intel.com/en-us/blogs/2014/07/15/an-investigation-of-fast-real-time-gpu-based-image-blur-algorithms
 * 
 * @param {number|number[]} [blur=4] - The blur of the filter. Should be greater than `0`. If
 *        value is an Array, setting kernels.
 * @param {number} [quality=3] - The quality of the filter. Should be an integer greater than `1`.
 * @param {boolean} [clamp=false] - Clamp edges, useful for removing dark edges
 *        from fullscreen filters or bleeding to the edge of filterArea.
 */
export class KawaseBlurFilter extends Filter {
    private _pixelSize: Point;
    private _clamp: boolean;
    private _kernels: any;
    private _blur: number;
    private _quality: number;
    constructor(blur = 4, quality = 3, clamp = false) {
        super(defaultVert, clamp ? fragmentClamp : fragment);
        this.uniforms.uOffset = new Float32Array(2);

        this._pixelSize = new Point();
        this.pixelSize = 1;
        this._clamp = clamp;
        this._kernels = null;

        // if `blur` is array , as kernels
        if (Array.isArray(blur)) {
            this.kernels = blur;
        }
        else {
            this._blur = blur;
            this.quality = quality;
        }
    }

    /**
     * Overrides apply
     * @private
     */
    apply(filterManager:FilterManager, input:RenderTarget, output:RenderTarget, clear) {
        const uvX = this.pixelSize.x / input.size.width;
        const uvY = this.pixelSize.y / input.size.height;
        let offset;

        if (this._quality === 1 || this._blur === 0) {
            offset = this._kernels[0] + 0.5;
            this.uniforms.uOffset[0] = offset * uvX;
            this.uniforms.uOffset[1] = offset * uvY;
            filterManager.applyFilter(this, input, output, clear);
        }
        else {
            const renderTarget = filterManager.getRenderTarget();

            let source = input;
            let target = renderTarget;
            let tmp;

            const last = this._quality - 1;

            for (let i = 0; i < last; i++) {
                offset = this._kernels[i] + 0.5;
                this.uniforms.uOffset[0] = offset * uvX;
                this.uniforms.uOffset[1] = offset * uvY;
                filterManager.applyFilter(this, source, target, true);

                tmp = source;
                source = target;
                target = tmp;
            }
            offset = this._kernels[last] + 0.5;
            this.uniforms.uOffset[0] = offset * uvX;
            this.uniforms.uOffset[1] = offset * uvY;
            filterManager.applyFilter(this, source, output, clear);

            filterManager.returnRenderTarget(renderTarget);
        }
    }

    /**
     * Auto generate kernels by blur & quality
     * @private
     */
    _generateKernels() {
        const blur = this._blur;
        const quality = this._quality;
        const kernels = [blur];

        if (blur > 0) {
            let k = blur;
            const step = blur / quality;

            for (let i = 1; i < quality; i++) {
                k -= step;
                kernels.push(k);
            }
        }

        this._kernels = kernels;
    }

    /**
     * The kernel size of the blur filter, for advanced usage.
     *
     * @member {number[]}
     * @default [0]
     */
    get kernels() {
        return this._kernels;
    }
    set kernels(value) {
        if (Array.isArray(value) && value.length > 0) {
            this._kernels = value;
            this._quality = value.length;
            this._blur = Math.max.apply(Math, value);
        }
        else {
            // if value is invalid , set default value
            this._kernels = [0];
            this._quality = 1;
        }
    }

    /**
     * Get the if the filter is clampped.
     *
     * @readonly
     * @member {boolean}
     * @default false
     */
    get clamp() {
        return this._clamp;
    }

    /**
     * Sets the pixel size of the filter. Large size is blurrier. For advanced usage.
     *
     * @member {Point|number|number[]}
     * @default [1, 1]
     */
    set pixelSize(value) {
        if (typeof value === 'number') {
            this._pixelSize.x = value;
            this._pixelSize.y = value;
        }
        else if (Array.isArray(value)) {
            this._pixelSize.x = value[0];
            this._pixelSize.y = value[1];
        }
        else if (value instanceof Point) {
            this._pixelSize.x = value.x;
            this._pixelSize.y = value.y;
        }
        else {
            // if value is invalid , set default value
            this._pixelSize.x = 1;
            this._pixelSize.y = 1;
        }
    }
    get pixelSize():any {
        return this._pixelSize;
    }

    /**
     * The quality of the filter, integer greater than `1`.
     *
     * @member {number}
     * @default 3
     */
    get quality() {
        return this._quality;
    }
    set quality(value) {
        this._quality = Math.max(1, Math.round(value));
        this._generateKernels();
    }

    /**
     * The amount of blur, value greater than `0`.
     *
     * @member {number}
     * @default 4
     */
    get blur() {
        return this._blur;
    }
    set blur(value) {
        this._blur = value;
        this._generateKernels();
    }
}


