import { Color, ColorSource, Filter, FilterSystem, GlProgram, RenderSurface, Texture } from 'pixi.js';

export class ChooserSelectionFilter extends Filter
{
    public uniforms: {
        uLineColor: Float32Array,
        uColor: Float32Array,
        uTime: number
    };

    private _lineColor!: Color;
    private _color!: Color;

    constructor(
        lineColor: ColorSource = [0.700, 0.880, 0.950],
        color: ColorSource = [0.290, 0.350, 0.390]
    )
    {
        const glProgram = GlProgram.from({
            vertex: `in vec2 aPosition;
            out vec2 vTextureCoord;

            uniform vec4 uInputSize;
            uniform vec4 uOutputFrame;
            uniform vec4 uOutputTexture;

            vec4 filterVertexPosition(void)
            {
                vec2 position = aPosition * uOutputFrame.zw + uOutputFrame.xy;
                position.x = position.x * (2.0 / uOutputTexture.x) - 1.0;
                position.y = position.y * (2.0*uOutputTexture.z / uOutputTexture.y) - uOutputTexture.z;
                return vec4(position, 0.0, 1.0);
            }

            vec2 filterTextureCoord(void)
            {
                return aPosition * (uOutputFrame.zw * uInputSize.zw);
            }

            void main(void)
            {
                gl_Position = filterVertexPosition();
                vTextureCoord = filterTextureCoord();
            }`,
            fragment: `
            in vec2 vTextureCoord;
            out vec4 finalColor;

            uniform sampler2D uTexture;
            uniform vec3 uLineColor;
            uniform vec3 uColor;
            uniform float uTime;

            void main(void) {
                vec4 currentColor = texture(uTexture, vTextureCoord);
                vec3 colorLine = uLineColor * currentColor.a;
                vec3 colorOverlay = uColor * currentColor.a;
                float fadeFactor = 0.75 + 0.25 * sin(uTime * 3.0);

                if(currentColor.r == 0.0 && currentColor.g == 0.0 && currentColor.b == 0.0 && currentColor.a > 0.0) {
                    finalColor = vec4(colorLine.r * fadeFactor, colorLine.g * fadeFactor, colorLine.b * fadeFactor, currentColor.a);
                } else if(currentColor.a > 0.0) {
                    finalColor = vec4(colorOverlay.r * fadeFactor, colorOverlay.g * fadeFactor, colorOverlay.b * fadeFactor, currentColor.a * 0.35);
                }
            }
            `,
            name: 'chooser-selection-filter',
        });

        super({
            gpuProgram: null,
            glProgram,
            resources: {
                chooserUniforms: {
                    uLineColor: { value: new Float32Array(3), type: 'vec3<f32>' },
                    uColor: { value: new Float32Array(3), type: 'vec3<f32>' },
                    uTime: { value: 0.0, type: 'f32' }
                }
            }
        });

        this.uniforms = this.resources.chooserUniforms.uniforms;

        this._lineColor = new Color();
        this.lineColor = lineColor;

        this._color = new Color();
        this.color = color;
    }

    public apply(
        filterManager: FilterSystem,
        input: Texture,
        output: RenderSurface,
        clearMode: boolean,
    ): void
    {
        filterManager.applyFilter(this, input, output, clearMode);
    }

    public get lineColor(): ColorSource
    {
        return this._lineColor.value as ColorSource;
    }

    public set lineColor(value: ColorSource)
    {
        this._lineColor.setValue(value);

        const [r, g, b] = this._lineColor.toArray();

        this.uniforms.uLineColor[0] = r;
        this.uniforms.uLineColor[1] = g;
        this.uniforms.uLineColor[2] = b;
    }

    public get color(): ColorSource
    {
        return this._color.value as ColorSource;
    }

    public set color(value: ColorSource)
    {
        this._color.setValue(value);

        const [r, g, b] = this._color.toArray();

        this.uniforms.uColor[0] = r;
        this.uniforms.uColor[1] = g;
        this.uniforms.uColor[2] = b;
    }

    public get time(): number
    {
        return this.uniforms.uTime;
    }

    public set time(value: number)
    {
        this.uniforms.uTime = value;
    }
}
