import { Texture } from '../../platform/graphics/texture.js';
import { SEMANTIC_POSITION, ADDRESS_CLAMP_TO_EDGE, FILTER_NEAREST, PIXELFORMAT_R32U, PIXELFORMAT_RG32U, CULLFACE_NONE } from '../../platform/graphics/constants.js';
import { RenderTarget } from '../../platform/graphics/render-target.js';
import { drawQuadWithShader } from '../graphics/quad-render-utils.js';
import { BlendState } from '../../platform/graphics/blend-state.js';
import { DepthState } from '../../platform/graphics/depth-state.js';
import { ShaderUtils } from '../shader-lib/shader-utils.js';
import gsplatIntervalTextureGLSL from '../shader-lib/glsl/chunks/gsplat/frag/gsplatIntervalTexture.js';
import gsplatIntervalTextureWGSL from '../shader-lib/wgsl/chunks/gsplat/frag/gsplatIntervalTexture.js';

class GSplatIntervalTexture {
		constructor(device){
				this.texture = null;
				this.rt = null;
				this.intervalsDataTexture = null;
				this.shader = null;
				this.device = device;
		}
		destroy() {
				this.texture?.destroy();
				this.texture = null;
				this.rt?.destroy();
				this.rt = null;
				this.intervalsDataTexture?.destroy();
				this.intervalsDataTexture = null;
				this.shader = null;
		}
		getShader() {
				if (!this.shader) {
						this.shader = ShaderUtils.createShader(this.device, {
								uniqueName: 'GSplatIntervalsShader',
								attributes: {
										aPosition: SEMANTIC_POSITION
								},
								vertexChunk: 'quadVS',
								fragmentGLSL: gsplatIntervalTextureGLSL,
								fragmentWGSL: gsplatIntervalTextureWGSL,
								fragmentOutputTypes: [
										'uint'
								]
						});
				}
				return this.shader;
		}
		createTexture(name, format, width, height) {
				return new Texture(this.device, {
						name: name,
						width: width,
						height: height,
						format: format,
						cubemap: false,
						mipmaps: false,
						minFilter: FILTER_NEAREST,
						magFilter: FILTER_NEAREST,
						addressU: ADDRESS_CLAMP_TO_EDGE,
						addressV: ADDRESS_CLAMP_TO_EDGE
				});
		}
		update(intervals) {
				if (!intervals || intervals.length === 0) {
						return 0;
				}
				let totalSplats = 0;
				for(let i = 0; i < intervals.length; i += 2){
						const start = intervals[i];
						const end = intervals[i + 1];
						totalSplats += end - start;
				}
				const maxTextureSize = this.device.maxTextureSize;
				let textureWidth = Math.ceil(Math.sqrt(totalSplats));
				textureWidth = Math.min(textureWidth, maxTextureSize);
				const textureHeight = Math.ceil(totalSplats / textureWidth);
				if (!this.texture) {
						this.texture = this.createTexture('intervalsTexture', PIXELFORMAT_R32U, textureWidth, textureHeight);
				}
				if (!this.rt) {
						this.rt = new RenderTarget({
								colorBuffer: this.texture,
								depth: false
						});
				}
				if (this.rt.width !== textureWidth || this.rt.height !== textureHeight) {
						this.rt.resize(textureWidth, textureHeight);
				}
				const numIntervals = intervals.length / 2;
				const dataTextureSize = Math.ceil(Math.sqrt(numIntervals));
				if (!this.intervalsDataTexture) {
						this.intervalsDataTexture = this.createTexture('intervalsData', PIXELFORMAT_RG32U, dataTextureSize, dataTextureSize);
				}
				if (this.intervalsDataTexture.width !== dataTextureSize) {
						this.intervalsDataTexture.resize(dataTextureSize, dataTextureSize);
				}
				const intervalsData = this.intervalsDataTexture.lock();
				let runningSum = 0;
				for(let i = 0; i < numIntervals; i++){
						const start = intervals[i * 2];
						const end = intervals[i * 2 + 1];
						const intervalSize = end - start;
						runningSum += intervalSize;
						intervalsData[i * 2] = start;
						intervalsData[i * 2 + 1] = runningSum;
				}
				this.intervalsDataTexture.unlock();
				const scope = this.device.scope;
				scope.resolve('uIntervalsTexture').setValue(this.intervalsDataTexture);
				scope.resolve('uNumIntervals').setValue(numIntervals);
				scope.resolve('uTextureWidth').setValue(textureWidth);
				scope.resolve('uActiveSplats').setValue(totalSplats);
				this.device.setCullMode(CULLFACE_NONE);
				this.device.setBlendState(BlendState.NOBLEND);
				this.device.setDepthState(DepthState.NODEPTH);
				drawQuadWithShader(this.device, this.rt, this.getShader());
				return totalSplats;
		}
}

export { GSplatIntervalTexture };
