import {
    DITHERING,
    VISIBILITY_PROPERTY_DEFINE,
    HIGHLIGHT_PROPERTY_DEFINE
} from '../ShaderConstants';
import { packedNormalPackSize } from '../PackedNormal';
import { AbstractMesh, Scene, Engine } from '../babylonjs-import';
import { TransparencyMode } from '../twinfinity-viewer';
import { UniformsAndDefinesAndSamplers, clipPlanesStrings } from './CullingMaterial';
import { LazyResizableRawTexture } from '../LazyResizableRawTexture';
import { DitheringMaterial } from './DitheringMaterial';
import { DitherTexture } from './DitherTexture';
import { transparentIfcShader } from './shaders/TransparentIfcShader';
import { ProductMeshesGroupRenderInfo } from '../ProductMeshesGroupRenderInfo';

const SIZE = 'size';
const PACK_SIZE = 'packSize';
const WORLD_MATRIX = 'worldMatrix';
const DIFFUSE_SAMPLER = 'diffuseSampler';
const PROPERTIES_SAMPLER = 'propertiesSampler';
const HIGHLIGHT_COLORS = 'highlightColors';

export class TransparentIfcMaterial extends DitheringMaterial {
    constructor(
        name: string,
        scene: Scene,
        renderingInfo: ProductMeshesGroupRenderInfo,
        public readonly diffuseTexture: LazyResizableRawTexture,
        public readonly propertiesTexture: LazyResizableRawTexture,
        ditherTexture: DitherTexture
    ) {
        super(
            name,
            scene,
            renderingInfo,
            {
                attributes: ['position', 'uv'],
                samplers: [DIFFUSE_SAMPLER, PROPERTIES_SAMPLER]
            },
            ditherTexture,
            true,
            false
        );
        this.visualSettings.ifc.color.onPropertyChanged.add(() => {
            this.isDirty = true;
        });

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

    public updateAlphaMode(): void {
        if (this.visualSettings.ifc.transparency.mode === TransparencyMode.Dithering) {
            this._isDitherModeEnabled = true;
            this.alpha = 1.0;
            this.alphaMode = Engine.ALPHA_DISABLE;
        } else {
            this._isDitherModeEnabled = false;
            this.alpha = 0.99;
            this.alphaMode = Engine.ALPHA_COMBINE;
        }
    }

    getUniformsAndDefinesAndSamplers(): UniformsAndDefinesAndSamplers {
        const uniformsAndSamplers = super.getUniformsAndDefinesAndSamplers();

        const defines: string[] = [
            'DITHERING_OPACITY_COEFFICIENT 1.0',
            VISIBILITY_PROPERTY_DEFINE,
            HIGHLIGHT_PROPERTY_DEFINE
        ];
        const uniforms = ['worldViewProjection', SIZE, PACK_SIZE, WORLD_MATRIX];
        const samplers = [DIFFUSE_SAMPLER, PROPERTIES_SAMPLER];

        if (this._isDitherModeEnabled) {
            defines.push(DITHERING);
        }

        // Babylonjs need to have every index of uniform arrays addded to the list of uniforms used by the shader
        for (let i = 0; i < 3; i++) {
            uniforms.push(HIGHLIGHT_COLORS + '[' + i + ']');
        }

        const aggregatedUniformsAndSamplers = {
            uniforms: [...uniformsAndSamplers.uniforms, ...uniforms],
            defines: [...uniformsAndSamplers.defines, ...defines],
            samplers: [...uniformsAndSamplers.samplers, ...samplers]
        };

        this.visualSettings.ifc.color._insertUniformsAndDefines(aggregatedUniformsAndSamplers);

        return aggregatedUniformsAndSamplers;
    }

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

        this.setFloat(PACK_SIZE, packedNormalPackSize);

        const wm = mesh.getWorldMatrix();
        this.getEffect().setMatrix(WORLD_MATRIX, wm);

        this.visualSettings.ifc.color._setEffectValues(effect);
        this.visualSettings.ifc._setEffectValues(effect);
    }

    public getFragmentSource(): string {
        return transparentIfcShader.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 transparentIfcShader.vertex();
    }
}
