import { AbstractMesh, Scene } from '../babylonjs-import';
import { CullingMaterial, UniformsAndDefinesAndSamplers, clipPlanesStrings } from './CullingMaterial';
import {
    DIFFUSE_SAMPLER,
    OUTLINE_PROPERTY_DEFINE,
    PROPERTIES_SAMPLER,
    VISIBILITY_PROPERTY_DEFINE
} from '../ShaderConstants';
import { TextureObject } from '../texture-object';
import { LazyResizableRawTexture } from '../LazyResizableRawTexture';
import { gpuPickingShader } from './shaders/GpuPickingShader';
import { ProductMeshesGroupRenderInfo } from '../ProductMeshesGroupRenderInfo';

const SIZE = 'size';
const WORLD_MATRIX = 'worldMatrix';
const IS_IFC_PICKING_MATERIAL = 'isIFcPickingMaterial';
const READ_ALPHA_PROPERTY_BYTE = 'readAlphaPropertyByte';

export class GpuPickingMaterial extends CullingMaterial {
    constructor(
        name: string,
        scene: Scene,
        renderingInfo: ProductMeshesGroupRenderInfo,
        public readonly diffuseTexture: LazyResizableRawTexture,
        public readonly propertiesTexture?: LazyResizableRawTexture,
        public readonly isOutlineDiscerner: boolean = false
    ) {
        super(
            name,
            scene,
            renderingInfo,
            {
                attributes: ['position', 'uv'],
                samplers: []
            },
            false,
            false
        );

        this.visualSettings.ifc.transparency.onPropertyChanged.add((change) => {
            if (change.property === 'ditheringOptions') {
                this.isDirty = true;
            }
        });
    }

    public getFragmentSource(): string {
        return gpuPickingShader.fragment({
            clipPlaneUniforms: (): string => {
                return clipPlanesStrings.CLIP_PLANE_UNIFORMS;
            },
            clipPlaneDiscarding: (): string => {
                return clipPlanesStrings.CLIP_PLANE_DISCARDING;
            },
            clipPlaneColorBlending: (): string => {
                return clipPlanesStrings.CLIP_PLANE_COLOR_BLENDING;
            }
        });
    }

    public getVertexSource(): string {
        return gpuPickingShader.vertex({
            IsNotIfcProductFlag: (): string => {
                return `const float isNotIfcProductFlag = float(${TextureObject.IsNotIfcProductFlag.toString()});`;
            }
        });
    }

    public getUniformsAndDefinesAndSamplers(): UniformsAndDefinesAndSamplers {
        const uniformsAndDefinesAndSamplers = super.getUniformsAndDefinesAndSamplers();
        const uniforms = ['worldViewProjection', SIZE, WORLD_MATRIX];
        const defines: string[] = [VISIBILITY_PROPERTY_DEFINE];
        const samplers: string[] = [DIFFUSE_SAMPLER];

        if (this.propertiesTexture || this.isOutlineDiscerner) {
            defines.push(READ_ALPHA_PROPERTY_BYTE);
            samplers.push(PROPERTIES_SAMPLER);
        }

        if (this.propertiesTexture) {
            defines.push(IS_IFC_PICKING_MATERIAL);
        }

        if (this.isOutlineDiscerner) {
            defines.push(OUTLINE_PROPERTY_DEFINE);
        }

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

    override bindFunction(mesh: AbstractMesh): void {
        super.bindFunction(mesh);
        const effect = this.getEffect();
        effect.setTexture(DIFFUSE_SAMPLER, this.diffuseTexture.getTexture(mesh.getScene()));
        if (this.propertiesTexture) {
            effect.setTexture(PROPERTIES_SAMPLER, this.propertiesTexture.getTexture(mesh.getScene()));
        }
        effect.setMatrix(WORLD_MATRIX, mesh.getWorldMatrix());
    }
}
