import { BufferReader } from './buffer-reader.js';
import { KTX2_ID } from './constants-internal.js';
import { KHR_DF_SAMPLE_DATATYPE_SIGNED, type Supercompression, type VKFormat } from './constants.js';
import type { KTX2BasicFormatSample, KTX2Container, KTX2DataFormatDescriptorBasicFormat } from './container.js';
import { createDefaultContainer } from './create-default-container.js';
import { decodeText } from './util.js';

/**
 * Parses a KTX 2.0 file, returning an unpacked {@link KTX2Container} instance with all associated
 * data. The container's mip levels and other binary data are pointers into the original file, not
 * copies, so the original file should not be overwritten after reading.
 *
 * @param data Bytes of KTX 2.0 file, as Uint8Array or Buffer.
 */
export function read(data: Uint8Array): KTX2Container {
	///////////////////////////////////////////////////
	// KTX 2.0 Identifier.
	///////////////////////////////////////////////////

	const id = new Uint8Array(data.buffer, data.byteOffset, KTX2_ID.length);
	if (
		id[0] !== KTX2_ID[0] || // '´'
		id[1] !== KTX2_ID[1] || // 'K'
		id[2] !== KTX2_ID[2] || // 'T'
		id[3] !== KTX2_ID[3] || // 'X'
		id[4] !== KTX2_ID[4] || // ' '
		id[5] !== KTX2_ID[5] || // '2'
		id[6] !== KTX2_ID[6] || // '0'
		id[7] !== KTX2_ID[7] || // 'ª'
		id[8] !== KTX2_ID[8] || // '\r'
		id[9] !== KTX2_ID[9] || // '\n'
		id[10] !== KTX2_ID[10] || // '\x1A'
		id[11] !== KTX2_ID[11] // '\n'
	) {
		throw new Error('Missing KTX 2.0 identifier.');
	}

	const container = createDefaultContainer();

	///////////////////////////////////////////////////
	// Header.
	///////////////////////////////////////////////////

	const headerByteLength = 17 * Uint32Array.BYTES_PER_ELEMENT;
	const headerReader = new BufferReader(data, KTX2_ID.length, headerByteLength, true);

	container.vkFormat = headerReader._nextUint32() as VKFormat;
	container.typeSize = headerReader._nextUint32();
	container.pixelWidth = headerReader._nextUint32();
	container.pixelHeight = headerReader._nextUint32();
	container.pixelDepth = headerReader._nextUint32();
	container.layerCount = headerReader._nextUint32();
	container.faceCount = headerReader._nextUint32();
	container.levelCount = headerReader._nextUint32();

	container.supercompressionScheme = headerReader._nextUint32() as Supercompression;

	const dfdByteOffset = headerReader._nextUint32();
	const dfdByteLength = headerReader._nextUint32();
	const kvdByteOffset = headerReader._nextUint32();
	const kvdByteLength = headerReader._nextUint32();
	const sgdByteOffset = headerReader._nextUint64();
	const sgdByteLength = headerReader._nextUint64();

	///////////////////////////////////////////////////
	// Level Index.
	///////////////////////////////////////////////////

	const levelByteLength = Math.max(container.levelCount, 1) * 3 * 8;
	const levelReader = new BufferReader(data, KTX2_ID.length + headerByteLength, levelByteLength, true);

	for (let i = 0, il = Math.max(container.levelCount, 1); i < il; i++) {
		container.levels.push({
			levelData: new Uint8Array(data.buffer, data.byteOffset + levelReader._nextUint64(), levelReader._nextUint64()),
			uncompressedByteLength: levelReader._nextUint64(),
		});
	}

	///////////////////////////////////////////////////
	// Data Format Descriptor (DFD).
	///////////////////////////////////////////////////

	const dfdReader = new BufferReader(data, dfdByteOffset, dfdByteLength, true);

	dfdReader._skip(4); // totalSize
	const vendorId = dfdReader._nextUint16();
	const descriptorType = dfdReader._nextUint16();
	const versionNumber = dfdReader._nextUint16();
	const descriptorBlockSize = dfdReader._nextUint16();
	const colorModel = dfdReader._nextUint8();
	const colorPrimaries = dfdReader._nextUint8();
	const transferFunction = dfdReader._nextUint8();
	const flags = dfdReader._nextUint8();

	const texelBlockDimension = [
		dfdReader._nextUint8(),
		dfdReader._nextUint8(),
		dfdReader._nextUint8(),
		dfdReader._nextUint8(),
	] as KTX2DataFormatDescriptorBasicFormat['texelBlockDimension'];

	const bytesPlane = [
		dfdReader._nextUint8(),
		dfdReader._nextUint8(),
		dfdReader._nextUint8(),
		dfdReader._nextUint8(),
		dfdReader._nextUint8(),
		dfdReader._nextUint8(),
		dfdReader._nextUint8(),
		dfdReader._nextUint8(),
	] as KTX2DataFormatDescriptorBasicFormat['bytesPlane'];

	const samples = [] as KTX2BasicFormatSample[];

	const dfd: KTX2DataFormatDescriptorBasicFormat = {
		vendorId,
		descriptorType,
		versionNumber,
		colorModel,
		colorPrimaries,
		transferFunction,
		flags,
		texelBlockDimension,
		bytesPlane,
		samples,
	};

	const sampleStart = 6;
	const sampleWords = 4;
	const numSamples = (descriptorBlockSize / 4 - sampleStart) / sampleWords;

	for (let i = 0; i < numSamples; i++) {
		const sample = {
			bitOffset: dfdReader._nextUint16(),
			bitLength: dfdReader._nextUint8(),
			channelType: dfdReader._nextUint8(),
			samplePosition: [dfdReader._nextUint8(), dfdReader._nextUint8(), dfdReader._nextUint8(), dfdReader._nextUint8()],
			sampleLower: Number.NEGATIVE_INFINITY,
			sampleUpper: Number.POSITIVE_INFINITY,
		};

		if (sample.channelType & KHR_DF_SAMPLE_DATATYPE_SIGNED) {
			sample.sampleLower = dfdReader._nextInt32();
			sample.sampleUpper = dfdReader._nextInt32();
		} else {
			sample.sampleLower = dfdReader._nextUint32();
			sample.sampleUpper = dfdReader._nextUint32();
		}

		dfd.samples[i] = sample;
	}

	container.dataFormatDescriptor.length = 0;
	container.dataFormatDescriptor.push(dfd);

	///////////////////////////////////////////////////
	// Key/Value Data (KVD).
	///////////////////////////////////////////////////

	const kvdReader = new BufferReader(data, kvdByteOffset, kvdByteLength, true);

	while (kvdReader._offset < kvdByteLength) {
		const keyValueByteLength = kvdReader._nextUint32();
		const keyData = kvdReader._scan(keyValueByteLength);
		const key = decodeText(keyData);

		container.keyValue[key] = kvdReader._nextUint8Array(keyValueByteLength - keyData.byteLength - 1);

		if (key.match(/^ktx/i)) {
			const text = decodeText(container.keyValue[key] as Uint8Array);
			container.keyValue[key] = text.substring(0, text.lastIndexOf('\x00'));
		}

		const kvPadding = keyValueByteLength % 4 ? 4 - (keyValueByteLength % 4) : 0; // align(4)
		// 4-byte alignment.
		kvdReader._skip(kvPadding);
	}

	///////////////////////////////////////////////////
	// Supercompression Global Data (SGD).
	///////////////////////////////////////////////////

	if (sgdByteLength <= 0) return container;

	const sgdReader = new BufferReader(data, sgdByteOffset, sgdByteLength, true);

	const endpointCount = sgdReader._nextUint16();
	const selectorCount = sgdReader._nextUint16();
	const endpointsByteLength = sgdReader._nextUint32();
	const selectorsByteLength = sgdReader._nextUint32();
	const tablesByteLength = sgdReader._nextUint32();
	const extendedByteLength = sgdReader._nextUint32();

	const imageDescs = [];
	for (let i = 0, il = Math.max(container.levelCount, 1); i < il; i++) {
		imageDescs.push({
			imageFlags: sgdReader._nextUint32(),
			rgbSliceByteOffset: sgdReader._nextUint32(),
			rgbSliceByteLength: sgdReader._nextUint32(),
			alphaSliceByteOffset: sgdReader._nextUint32(),
			alphaSliceByteLength: sgdReader._nextUint32(),
		});
	}

	const endpointsByteOffset = sgdByteOffset + sgdReader._offset;
	const selectorsByteOffset = endpointsByteOffset + endpointsByteLength;
	const tablesByteOffset = selectorsByteOffset + selectorsByteLength;
	const extendedByteOffset = tablesByteOffset + tablesByteLength;

	const endpointsData = new Uint8Array(data.buffer, data.byteOffset + endpointsByteOffset, endpointsByteLength);
	const selectorsData = new Uint8Array(data.buffer, data.byteOffset + selectorsByteOffset, selectorsByteLength);
	const tablesData = new Uint8Array(data.buffer, data.byteOffset + tablesByteOffset, tablesByteLength);
	const extendedData = new Uint8Array(data.buffer, data.byteOffset + extendedByteOffset, extendedByteLength);

	container.globalData = {
		endpointCount,
		selectorCount,
		imageDescs,
		endpointsData,
		selectorsData,
		tablesData,
		extendedData,
	};

	return container;
}
