import { Uint32BufferAttribute, Uint16BufferAttribute } from 'three';

function arrayNeedsUint32( array ) {

	// assumes larger values usually on last

	for ( let i = array.length - 1; i >= 0; -- i ) {

		if ( array[ i ] >= 65535 ) return true; // account for PRIMITIVE_RESTART_FIXED_INDEX, #24565

	}

	return false;

}

function getWireframeVersion( geometry ) {

	return ( geometry.index !== null ) ? geometry.index.version : geometry.attributes.position.version;

}

function getWireframeIndex( geometry ) {

	const indices = [];

	const geometryIndex = geometry.index;
	const geometryPosition = geometry.attributes.position;

	if ( geometryIndex !== null ) {

		const array = geometryIndex.array;

		for ( let i = 0, l = array.length; i < l; i += 3 ) {

			const a = array[ i + 0 ];
			const b = array[ i + 1 ];
			const c = array[ i + 2 ];

			indices.push( a, b, b, c, c, a );

		}

	} else {

		const array = geometryPosition.array;

		for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) {

			const a = i + 0;
			const b = i + 1;
			const c = i + 2;

			indices.push( a, b, b, c, c, a );

		}

	}

	const attribute = new ( arrayNeedsUint32( indices ) ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 );
	attribute.version = getWireframeVersion( geometry );

	return attribute;

}

class WebGPUGeometries {

	constructor( attributes, properties, info ) {

		this.attributes = attributes;
		this.properties = properties;
		this.info = info;

		this.wireframes = new WeakMap();
		this.geometryFrame = new WeakMap();

	}

	has( renderObject ) {

		const geometry = renderObject.geometry;

		return this.properties.has( geometry ) && this.properties.get( geometry ).initialized === true;

	}

	update( renderObject ) {

		if ( this.has( renderObject ) === false ) this.initGeometry( renderObject );

		this.updateFrameAttributes( renderObject );

	}

	initGeometry( renderObject ) {

		const geometry = renderObject.geometry;
		const geometryProperties = this.properties.get( geometry );

		geometryProperties.initialized = true;

		const dispose = () => {

			this.info.memory.geometries --;

			const index = geometry.index;
			const geometryAttributes = geometry.attributes;

			if ( index !== null ) {

				this.attributes.remove( index );

			}

			for ( const name in geometryAttributes ) {

				this.attributes.remove( geometryAttributes[ name ] );

			}

			const wireframeAttribute = this.wireframes.get( geometry );

			if ( wireframeAttribute !== undefined ) {

				this.attributes.remove( wireframeAttribute );

			}

			geometry.removeEventListener( 'dispose', dispose );

		};

		this.info.memory.geometries ++;

		geometry.addEventListener( 'dispose', dispose );

	}

	updateFrameAttributes( renderObject ) {

		const frame = this.info.render.frame;
		const geometry = renderObject.geometry;

		if ( this.geometryFrame.get( geometry ) !== frame ) {

			this.updateAttributes( renderObject );

			this.geometryFrame.set( geometry, frame );

		}

	}

	updateAttributes( renderObject ) {

		const geometry = renderObject.geometry;
		const geometryAttributes = geometry.attributes;

		for ( const name in geometryAttributes ) {

			this.attributes.update( geometryAttributes[ name ] );

		}

		const index = this.getIndex( renderObject );

		if ( index !== null ) {

			this.attributes.update( index, true );

		}

	}

	getIndex( renderObject ) {

		const { geometry, material } = renderObject;

		let index = geometry.index;

		if ( material.wireframe === true ) {

			const wireframes = this.wireframes;

			let wireframeAttribute = wireframes.get( geometry );

			if ( wireframeAttribute === undefined ) {

				wireframeAttribute = getWireframeIndex( geometry );

				wireframes.set( geometry, wireframeAttribute );

			} else if ( wireframeAttribute.version !== getWireframeVersion( geometry ) ) {

				this.attributes.remove( wireframeAttribute );

				wireframeAttribute = getWireframeIndex( geometry );

				wireframes.set( geometry, wireframeAttribute );

			}

			index = wireframeAttribute;

		}

		return index;

	}

}

export default WebGPUGeometries;
