import { AbstractMesh, IShaderMaterialOptions, Scene, Vector2 } from '../babylonjs-import';
import { ProductMeshesGroupRenderInfo } from '../ProductMeshesGroupRenderInfo';
import { DITHER_SAMPLER, DITHER_SIZE, DITHERING, RESOLUTION, DITHER_DISTANCE_FADE } from '../ShaderConstants';
import { CullingMaterial, UniformsAndDefinesAndSamplers } from './CullingMaterial';
import { DitherTexture } from './DitherTexture';

export abstract class DitheringMaterial extends CullingMaterial {
    protected _ditherSize: number;
    protected _isDitherModeEnabled = true;

    abstract getFragmentSource(): string;
    abstract getVertexSource(): string;

    constructor(
        name: string,
        scene: Scene,
        renderingInfo: ProductMeshesGroupRenderInfo,
        options: Partial<IShaderMaterialOptions>,
        public readonly ditherTexture: DitherTexture,
        writeColorBlending: boolean,
        excludeIgnoreDepthWriteCuttingPlanes: boolean
    ) {
        super(name, scene, renderingInfo, options, writeColorBlending, excludeIgnoreDepthWriteCuttingPlanes);

        this._ditherSize = ditherTexture.getTexture(scene).getSize().width;

        this.ditherTexture = ditherTexture;

        this.visualSettings.ifc.transparency.onPropertyChanged.add((change) => {
            if (change.property === 'ditheringOptions') {
                // Updates the dither texture to calculate the new frames, no need to mark this material as dirty since all materials listen to changes in visualsettings
                this.ditherTexture.currentMode = this.visualSettings.ifc.transparency.ditheringOptions.mode;
                this.ditherTexture.animatedNoiseTextures =
                    this.visualSettings.ifc.transparency.ditheringOptions.animatedFrames;
            }
        });
    }

    getUniformsAndDefinesAndSamplers(): UniformsAndDefinesAndSamplers {
        const defines: string[] = [];
        const uniforms = [];
        const samplers = [];

        const uniformsAndDefines = super.getUniformsAndDefinesAndSamplers();

        if (this._isDitherModeEnabled) {
            defines.push(DITHERING);
            uniforms.push(...[RESOLUTION, DITHER_SIZE]);
            samplers.push(DITHER_SAMPLER);
        }

        return {
            uniforms: [...uniforms, ...uniformsAndDefines.uniforms],
            defines: [...defines, ...uniformsAndDefines.defines],
            samplers: [...samplers, ...uniformsAndDefines.samplers]
        };
    }

    override bindFunction(mesh: AbstractMesh): void {
        super.bindFunction(mesh);

        const effect = this.getEffect();
        effect.setTexture(DITHER_SAMPLER, this.ditherTexture.getTexture(mesh.getScene()));

        if (this._isDitherModeEnabled) {
            const scene = this.getScene();

            effect.setVector2(
                DITHER_SIZE,
                new Vector2(
                    scene.getEngine().getRenderWidth() / this._ditherSize,
                    scene.getEngine().getRenderHeight() / this._ditherSize
                )
            );

            effect.setFloat(
                DITHER_DISTANCE_FADE,
                this.visualSettings.ifc.transparency.ditheringOptions.distanceFade ? 1.0 : 0.0
            );

            this.getEffect().setVector2(
                RESOLUTION,
                new Vector2(scene.getEngine().getRenderWidth(), scene.getEngine().getRenderHeight())
            );
        }
    }
}
