import { Vec2 } from '../../core/math/vec2.js';
import { BoundingBox } from '../../core/shape/bounding-box.js';
import { TYPE_UINT32, SEMANTIC_ATTR13, BUFFER_STATIC, ADDRESS_CLAMP_TO_EDGE, FILTER_NEAREST, SEMANTIC_POSITION } from '../../platform/graphics/constants.js';
import { Texture } from '../../platform/graphics/texture.js';
import { VertexFormat } from '../../platform/graphics/vertex-format.js';
import { VertexBuffer } from '../../platform/graphics/vertex-buffer.js';
import { Mesh } from '../mesh.js';
import { ShaderMaterial } from '../materials/shader-material.js';
import { QuadRender } from '../graphics/quad-render.js';
import { ShaderUtils } from '../shader-lib/shader-utils.js';
import glslGsplatCopyToWorkBufferPS from '../shader-lib/glsl/chunks/gsplat/frag/gsplatCopyToWorkbuffer.js';
import wgslGsplatCopyToWorkBufferPS from '../shader-lib/wgsl/chunks/gsplat/frag/gsplatCopyToWorkbuffer.js';

let id = 0;
const tempMap = new Map();
class WorkBufferRenderInfo {
		constructor(device, key, material){
				this.device = device;
				this.material = material;
				const clonedDefines = new Map(material.defines);
				const shader = ShaderUtils.createShader(this.device, {
						uniqueName: `SplatCopyToWorkBuffer:${key}`,
						attributes: {
								vertex_position: SEMANTIC_POSITION
						},
						vertexDefines: clonedDefines,
						fragmentDefines: clonedDefines,
						vertexChunk: 'fullscreenQuadVS',
						fragmentGLSL: glslGsplatCopyToWorkBufferPS,
						fragmentWGSL: wgslGsplatCopyToWorkBufferPS
				});
				this.quadRender = new QuadRender(shader);
		}
		destroy() {
				this.material?.destroy();
				this.quadRender?.destroy();
		}
}
class GSplatResourceBase {
		constructor(device, gsplatData){
				this.id = id++;
				this.workBufferRenderInfos = new Map();
				this.device = device;
				this.gsplatData = gsplatData;
				this.centers = new Float32Array(gsplatData.numSplats * 3);
				gsplatData.getCenters(this.centers);
				this.aabb = new BoundingBox();
				gsplatData.calcAabb(this.aabb);
				this.mesh = GSplatResourceBase.createMesh(device);
				this.instanceIndices = GSplatResourceBase.createInstanceIndices(device, gsplatData.numSplats);
				this.mesh.incRefCount();
				this.mesh.aabb.copy(this.aabb);
		}
		destroy() {
				this.mesh?.destroy();
				this.instanceIndices?.destroy();
				this.workBufferRenderInfos.forEach((info)=>info.destroy());
				this.workBufferRenderInfos.clear();
		}
		getWorkBufferRenderInfo(useIntervals, colorizeLod) {
				this.configureMaterialDefines(tempMap);
				if (useIntervals) tempMap.set('GSPLAT_LOD', '');
				if (colorizeLod) tempMap.set('GSPLAT_COLORIZE', '');
				const key = Array.from(tempMap.entries()).map(([k, v])=>`${k}=${v}`).join(';');
				let info = this.workBufferRenderInfos.get(key);
				if (!info) {
						const material = new ShaderMaterial();
						this.configureMaterial(material);
						tempMap.forEach((v, k)=>material.setDefine(k, v));
						info = new WorkBufferRenderInfo(this.device, key, material);
						this.workBufferRenderInfos.set(key, info);
				}
				tempMap.clear();
				return info;
		}
		static createMesh(device) {
				const splatInstanceSize = GSplatResourceBase.instanceSize;
				const meshPositions = new Float32Array(12 * splatInstanceSize);
				const meshIndices = new Uint32Array(6 * splatInstanceSize);
				for(let i = 0; i < splatInstanceSize; ++i){
						meshPositions.set([
								-1,
								-1,
								i,
								1,
								-1,
								i,
								1,
								1,
								i,
								-1,
								1,
								i
						], i * 12);
						const b = i * 4;
						meshIndices.set([
								0 + b,
								1 + b,
								2 + b,
								0 + b,
								2 + b,
								3 + b
						], i * 6);
				}
				const mesh = new Mesh(device);
				mesh.setPositions(meshPositions, 3);
				mesh.setIndices(meshIndices);
				mesh.update();
				return mesh;
		}
		static createInstanceIndices(device, splatCount) {
				const splatInstanceSize = GSplatResourceBase.instanceSize;
				const numSplats = Math.ceil(splatCount / splatInstanceSize) * splatInstanceSize;
				const numSplatInstances = numSplats / splatInstanceSize;
				const indexData = new Uint32Array(numSplatInstances);
				for(let i = 0; i < numSplatInstances; ++i){
						indexData[i] = i * splatInstanceSize;
				}
				const vertexFormat = new VertexFormat(device, [
						{
								semantic: SEMANTIC_ATTR13,
								components: 1,
								type: TYPE_UINT32,
								asInt: true
						}
				]);
				const instanceIndices = new VertexBuffer(device, vertexFormat, numSplatInstances, {
						usage: BUFFER_STATIC,
						data: indexData.buffer
				});
				return instanceIndices;
		}
		static get instanceSize() {
				return 128;
		}
		get numSplats() {
				return this.gsplatData.numSplats;
		}
		configureMaterial(material) {}
		configureMaterialDefines(defines) {}
		evalTextureSize(count) {
				return Vec2.ZERO;
		}
		createTexture(name, format, size, data) {
				return new Texture(this.device, {
						name: name,
						width: size.x,
						height: size.y,
						format: format,
						cubemap: false,
						mipmaps: false,
						minFilter: FILTER_NEAREST,
						magFilter: FILTER_NEAREST,
						addressU: ADDRESS_CLAMP_TO_EDGE,
						addressV: ADDRESS_CLAMP_TO_EDGE,
						...data ? {
								levels: [
										data
								]
						} : {}
				});
		}
		instantiate() {}
}

export { GSplatResourceBase };
