import { Matrix } from '../../../maths/matrix/Matrix';
import { UniformGroup } from '../../../rendering/renderers/shared/shader/UniformGroup';
import { getAdjustedBlendModeBlend } from '../../../rendering/renderers/shared/state/getAdjustedBlendModeBlend';
import { State } from '../../../rendering/renderers/shared/state/State';
import { color32BitToUniform } from '../../graphics/gpu/colorToUniform';
import { ParticleBuffer } from './ParticleBuffer';
import { ParticleShader } from './shader/ParticleShader';
import type { InstructionSet } from '../../../rendering/renderers/shared/instructions/InstructionSet';
import type { RenderPipe } from '../../../rendering/renderers/shared/instructions/RenderPipe';
import type { Shader } from '../../../rendering/renderers/shared/shader/Shader';
import type { Renderer } from '../../../rendering/renderers/types';
import type { Container } from '../../container/Container';
import type { ParticleContainer } from './ParticleContainer';
export interface ParticleContainerAdaptor
{
    execute(particleContainerPop: ParticleContainerPipe, container: ParticleContainer): void;
}
/**
 * Renderer for Particles that is designer for speed over feature set.
 * @memberof scene
 */
export class ParticleContainerPipe implements RenderPipe<ParticleContainer>
{
    /** The default shader that is used if a sprite doesn't have a more specific one. */
    public defaultShader: Shader;
    public adaptor: ParticleContainerAdaptor;
    public readonly state = State.for2d();
    public readonly renderer: Renderer;
    private _gpuBufferHash: Record<number, ParticleBuffer> = Object.create(null);
    // eslint-disable-next-line max-len
    private readonly _destroyRenderableBound = this.destroyRenderable.bind(this) as unknown as (renderable: Container) => void;
    public readonly localUniforms = new UniformGroup({
        uTranslationMatrix: { value: new Matrix(), type: 'mat3x3<f32>' },
        uColor: { value: new Float32Array(4), type: 'vec4<f32>' },
        uRound: { value: 1, type: 'f32' },
        uResolution: { value: [0, 0], type: 'vec2<f32>' },
    });
    /**
     * @param renderer - The renderer this sprite batch works for.
     * @param adaptor
     */
    constructor(renderer: Renderer, adaptor: ParticleContainerAdaptor)
    {
        this.renderer = renderer;
        this.adaptor = adaptor;
        this.defaultShader = new ParticleShader();
        this.state = State.for2d();
    }
    public validateRenderable(_renderable: ParticleContainer): boolean
    {
        // always fine :D
        return false;
    }
    public addRenderable(renderable: ParticleContainer, instructionSet: InstructionSet)
    {
        this.renderer.renderPipes.batch.break(instructionSet);
        instructionSet.add(renderable);
    }
    public getBuffers(renderable: ParticleContainer): ParticleBuffer
    {
        return this._gpuBufferHash[renderable.uid] || this._initBuffer(renderable);
    }
    private _initBuffer(renderable: ParticleContainer): ParticleBuffer
    {
        this._gpuBufferHash[renderable.uid] = new ParticleBuffer({
            size: renderable.particleChildren.length,
            properties: renderable._properties,
        });
        renderable.on('destroyed', this._destroyRenderableBound);
        return this._gpuBufferHash[renderable.uid];
    }
    public updateRenderable(_renderable: ParticleContainer)
    {
        // nothing to be done here!
    }
    public destroyRenderable(renderable: ParticleContainer)
    {
        const buffer = this._gpuBufferHash[renderable.uid];
        buffer.destroy();
        this._gpuBufferHash[renderable.uid] = null;
        renderable.off('destroyed', this._destroyRenderableBound);
    }
    public execute(container: ParticleContainer): void
    {
        const children = container.particleChildren;
        if (children.length === 0)
        {
            return;
        }
        const renderer = this.renderer;
        const buffer = this.getBuffers(container);
        container.texture ||= children[0].texture;
        const state = this.state;
        buffer.update(children, container._childrenDirty);
        container._childrenDirty = false;
        state.blendMode = getAdjustedBlendModeBlend(container.blendMode, container.texture._source);
        const uniforms = this.localUniforms.uniforms;
        const transformationMatrix = uniforms.uTranslationMatrix;
        container.worldTransform.copyTo(transformationMatrix);
        transformationMatrix.prepend(renderer.globalUniforms.globalUniformData.projectionMatrix);
        uniforms.uResolution = renderer.globalUniforms.globalUniformData.resolution;
        uniforms.uRound = renderer._roundPixels | container._roundPixels;
        color32BitToUniform(
            container.groupColorAlpha,
            uniforms.uColor,
            0
        );
        this.adaptor.execute(this, container);
    }
    /** Destroys the ParticleRenderer. */
    public destroy(): void
    {
        if (this.defaultShader)
        {
            this.defaultShader.destroy();
            this.defaultShader = null;
        }
    }
}