import { Vector2, Vector4 } from '@zephyr3d/base';
import { ShaderHelper } from '../material/shader/helper.js';
import '../material/lambert.js';
import '../material/blinn.js';
import '../material/unlit.js';
import '../material/particle.js';
import '@zephyr3d/device';
import { MaterialVaryingFlags } from '../values.js';
import { getDevice } from '../app/api.js';
import '../material/meshmaterial.js';
import '../material/grassmaterial.js';
import '../material/terrain-cm.js';
import '../material/pbrmr.js';
import '../material/pbrsg.js';
import '../material/pbrblueprint.js';
import '../material/sprite.js';
import '../material/sprite_std.js';
import '../utility/blueprint/material/texture.js';

let _drawableId = 0;
const boneTextureSize = new Vector2();
const instanceBindGroupTransfromTags = new WeakMap();
const drawableBindGroupTransfromTags = new WeakMap();
const bindGroupCache = {};
const usedBindGroups = new WeakMap();
function fetchBindGroup(skinning, morphing, instancing) {
    const hash = `${instancing}:${morphing}:${skinning}`;
    const bindGroups = bindGroupCache[hash];
    let bindGroup = null;
    if (bindGroups && bindGroups.length > 0) {
        bindGroup = bindGroups.pop();
    } else {
        const layout = ShaderHelper.getDrawableBindGroupLayout(skinning, morphing, instancing);
        bindGroup = getDevice().createBindGroup(layout);
    }
    usedBindGroups.set(bindGroup, hash);
    return bindGroup;
}
function releaseBindGroup(bindGroup) {
    if (bindGroup) {
        const hash = usedBindGroups.get(bindGroup);
        if (hash) {
            usedBindGroups.delete(bindGroup);
            drawableBindGroupTransfromTags.delete(bindGroup);
            const bindGroups = bindGroupCache[hash];
            if (bindGroups) {
                bindGroups.push(bindGroup);
            } else {
                bindGroupCache[hash] = [
                    bindGroup
                ];
            }
        } else {
            bindGroup.dispose();
        }
    }
}
function mixinDrawable(baseCls) {
    const cls = class extends baseCls {
        _mdRenderQueueRef;
        _mdDrawableBindGroup;
        _mdDrawableBindGroupInstanced;
        _mdDrawableBindGroupSkin;
        _mdDrawableBindGroupMorph;
        _mdDrawableBindGroupSkinMorph;
        _worldMatrixBuffer;
        _framestampBuffer;
        _currentWorldMatrixBuffer;
        _prevWorldMatrixBuffer;
        _drawableId;
        _objectColor;
        _nodeTransformTag;
        constructor(...args){
            super(...args);
            this._drawableId = ++_drawableId;
            this._objectColor = null;
            this._mdRenderQueueRef = [];
            this._mdDrawableBindGroup = null;
            this._mdDrawableBindGroupInstanced = new Map();
            this._mdDrawableBindGroupSkin = null;
            this._mdDrawableBindGroupMorph = null;
            this._mdDrawableBindGroupSkinMorph = null;
            this._worldMatrixBuffer = new Float32Array(4 * 9);
            this._framestampBuffer = new Int32Array(this._worldMatrixBuffer.buffer, 4 * 16);
            this._currentWorldMatrixBuffer = this._worldMatrixBuffer.subarray(0, 16);
            this._prevWorldMatrixBuffer = this._worldMatrixBuffer.subarray(20, 36);
            this._currentWorldMatrixBuffer.set(this.getNode().worldMatrix);
            this._framestampBuffer[0] = getDevice().frameInfo.frameCounter;
            this._framestampBuffer[1] = this._framestampBuffer[0];
            this._prevWorldMatrixBuffer.set(this.getNode().worldMatrix);
            this._nodeTransformTag = -1;
            this.getNode().on('transformchanged', ()=>{
                this.getNode().scene?.queueUpdateDrawable(this);
            });
        }
        getDrawableId() {
            return this._drawableId;
        }
        getObjectColor() {
            if (!this._objectColor) {
                const a = (this._drawableId & 0xff) / 255;
                const b = (this._drawableId >>> 8 & 0xff) / 255;
                const g = (this._drawableId >>> 16 & 0xff) / 255;
                const r = (this._drawableId >>> 24 & 0xff) / 255;
                this._objectColor = new Vector4(r, g, b, a);
            }
            return this._objectColor;
        }
        pushRenderQueueRef(ref) {
            this.renderQueueRefPrune();
            this._mdRenderQueueRef.push(ref);
        }
        renderQueueRefPrune(force = false) {
            for(let i = this._mdRenderQueueRef.length - 1; i >= 0; i--){
                const ref = this._mdRenderQueueRef[i].ref;
                if (force || ref.disposed) {
                    this._mdRenderQueueRef.splice(i, 1);
                    const bindGroup = this._mdDrawableBindGroupInstanced.get(ref);
                    if (bindGroup) {
                        releaseBindGroup(bindGroup);
                        this._mdDrawableBindGroupInstanced.delete(ref);
                    }
                }
            }
        }
        applyInstanceOffsetAndStride(renderQueue, stride, offset) {
            const drawableBindGroup = this.getDrawableBindGroup(getDevice(), true, renderQueue);
            drawableBindGroup.setValue(ShaderHelper.getInstanceDataStrideUniformName(), stride >> 2);
            drawableBindGroup.setValue(ShaderHelper.getInstanceDataOffsetUniformName(), offset >> 2);
        }
        applyTransformUniforms(renderQueue) {
            const instanceInfo = renderQueue.getInstanceInfo(this);
            const currentTag = this.getNode().transformTag;
            if (instanceInfo) {
                const tag = instanceBindGroupTransfromTags.get(instanceInfo) ?? -1;
                if (tag !== currentTag) {
                    instanceInfo.bindGroup.bindGroup.setRawData(ShaderHelper.getInstanceDataUniformName(), instanceInfo.offset * 4, this._worldMatrixBuffer, 0, 36);
                    instanceBindGroupTransfromTags.set(instanceInfo, currentTag);
                }
            } else {
                const drawableBindGroup = this.getDrawableBindGroup(getDevice(), false, renderQueue);
                const tag = drawableBindGroupTransfromTags.get(drawableBindGroup) ?? -1;
                if (tag !== currentTag) {
                    drawableBindGroup.setValue(ShaderHelper.getWorldMatrixUniformName(), this._currentWorldMatrixBuffer);
                    drawableBindGroup.setValue(ShaderHelper.getPrevWorldMatrixUniformName(), this._prevWorldMatrixBuffer);
                    drawableBindGroup.setValue(ShaderHelper.getPrevWorldMatrixFrameUniformName(), this._framestampBuffer[0]);
                    if (this.getBoneMatrices()) {
                        drawableBindGroup.setValue(ShaderHelper.getBoneInvBindMatrixUniformName(), this.invWorldMatrix);
                    }
                    drawableBindGroupTransfromTags.set(drawableBindGroup, currentTag);
                }
            }
        }
        applyMaterialUniformsAll() {
            for (const ref of this._mdRenderQueueRef){
                if (ref.ref) {
                    const instanceInfo = ref.ref.getInstanceInfo(this);
                    if (instanceInfo) {
                        this.applyMaterialUniforms(instanceInfo);
                    }
                }
            }
        }
        applyMaterialUniforms(instanceInfo) {
            const uniforms = this.getInstanceUniforms();
            if (uniforms) {
                instanceInfo.bindGroup.bindGroup.setRawData(ShaderHelper.getInstanceDataUniformName(), (instanceInfo.offset + ShaderHelper.MATERIAL_INSTANCE_DATA_OFFSET * 4) * 4, uniforms, 0, uniforms.length);
            }
        }
        updateState() {
            const currentTag = this.getNode().transformTag;
            if (this._nodeTransformTag !== currentTag) {
                this._nodeTransformTag = currentTag;
                const frame = getDevice().frameInfo.frameCounter;
                if (frame !== this._framestampBuffer[1]) {
                    this._prevWorldMatrixBuffer.set(this._currentWorldMatrixBuffer);
                    this._framestampBuffer[0] = frame;
                    this._framestampBuffer[1] = frame;
                }
                this._currentWorldMatrixBuffer.set(this.getNode().worldMatrix);
                for (const ref of this._mdRenderQueueRef){
                    if (ref.ref) {
                        this.applyTransformUniforms(ref.ref);
                    }
                }
            }
        }
        /** @internal */ bind(ctx, renderQueue) {
            const device = ctx.device;
            const drawableBindGroup = this.getDrawableBindGroup(device, !!ctx.instanceData, renderQueue);
            device.setBindGroup(1, drawableBindGroup);
            if (ctx.instanceData) {
                device.setBindGroup(3, ctx.instanceData.bindGroup.bindGroup);
            }
            if (ctx.materialFlags & MaterialVaryingFlags.SKIN_ANIMATION) {
                const boneTexture = this.getBoneMatrices();
                drawableBindGroup.setTexture(ShaderHelper.getBoneMatricesUniformName(), boneTexture);
                drawableBindGroup.setValue(ShaderHelper.getBoneInvBindMatrixUniformName(), this.invWorldMatrix);
                boneTextureSize.setXY(boneTexture.width, boneTexture.height);
                drawableBindGroup.setValue(ShaderHelper.getBoneTextureSizeUniformName(), boneTextureSize);
            }
            if (ctx.materialFlags & MaterialVaryingFlags.MORPH_ANIMATION) {
                const morphData = this.getMorphData();
                const morphInfo = this.getMorphInfo();
                drawableBindGroup.setTexture(ShaderHelper.getMorphDataUniformName(), morphData.texture.get());
                drawableBindGroup.setBuffer(ShaderHelper.getMorphInfoUniformName(), morphInfo.buffer.get());
            }
        }
        /** @internal */ getDrawableBindGroup(device, instancing, renderQueue) {
            const skinning = !!this.getBoneMatrices();
            const morphing = !!this.getMorphData();
            let bindGroup = instancing ? this._mdDrawableBindGroupInstanced.get(renderQueue) : skinning && morphing ? this._mdDrawableBindGroupSkinMorph : skinning ? this._mdDrawableBindGroupSkin : morphing ? this._mdDrawableBindGroupMorph : this._mdDrawableBindGroup;
            if (!bindGroup) {
                bindGroup = fetchBindGroup(skinning, morphing, instancing);
                if (instancing) {
                    this._mdDrawableBindGroupInstanced.set(renderQueue, bindGroup);
                } else if (skinning && morphing) {
                    this._mdDrawableBindGroupSkinMorph = bindGroup;
                } else if (skinning) {
                    this._mdDrawableBindGroupSkin = bindGroup;
                } else if (morphing) {
                    this._mdDrawableBindGroupMorph = bindGroup;
                } else {
                    this._mdDrawableBindGroup = bindGroup;
                }
                if (!instancing) {
                    bindGroup.setValue(ShaderHelper.getObjectColorUniformName(), this.getObjectColor());
                }
            }
            return bindGroup;
        }
        onDispose() {
            super.onDispose();
            this.renderQueueRefPrune(true);
            releaseBindGroup(this._mdDrawableBindGroup);
            releaseBindGroup(this._mdDrawableBindGroupSkin);
            releaseBindGroup(this._mdDrawableBindGroupMorph);
        }
    };
    return cls;
}

export { mixinDrawable };
//# sourceMappingURL=drawable_mixin.js.map
