/*!
 * Copyright (c) 2025-present, Vanilagy and contributors
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

import { parseOpusTocByte } from '../codec-data';
import { assert, ilog, toDataView } from '../misc';

export const OGGS = 0x5367674f; // 'OggS'

const OGG_CRC_POLYNOMIAL = 0x04c11db7;
const OGG_CRC_TABLE = new Uint32Array(256);
for (let n = 0; n < 256; n++) {
	let crc = n << 24;

	for (let k = 0; k < 8; k++) {
		crc = (crc & 0x80000000)
			? ((crc << 1) ^ OGG_CRC_POLYNOMIAL)
			: (crc << 1);
	}

	OGG_CRC_TABLE[n] = (crc >>> 0) & 0xffffffff;
}

export const computeOggPageCrc = (bytes: Uint8Array) => {
	const view = toDataView(bytes);

	const originalChecksum = view.getUint32(22, true);
	view.setUint32(22, 0, true); // Zero out checksum field

	let crc = 0;
	for (let i = 0; i < bytes.length; i++) {
		const byte = bytes[i]!;
		crc = ((crc << 8) ^ OGG_CRC_TABLE[(crc >>> 24) ^ byte]!) >>> 0;
	}

	view.setUint32(22, originalChecksum, true); // Restore checksum field

	return crc;
};

export type OggCodecInfo = {
	codec: 'vorbis' | 'opus' | null;
	vorbisInfo: {
		blocksizes: number[];
		modeBlockflags: number[];
	} | null;
	opusInfo: {
		preSkip: number;
	} | null;
};

export const extractSampleMetadata = (
	data: Uint8Array,
	codecInfo: OggCodecInfo,
	vorbisLastBlocksize: number | null,
) => {
	let durationInSamples = 0;
	let currentBlocksize: number | null = null;

	if (data.length > 0) {
		// To know sample duration, we'll need to peak inside the packet
		if (codecInfo.codec === 'vorbis') {
			assert(codecInfo.vorbisInfo);

			const vorbisModeCount = codecInfo.vorbisInfo.modeBlockflags.length;
			const bitCount = ilog(vorbisModeCount - 1);
			const modeMask = ((1 << bitCount) - 1) << 1;
			const modeNumber = (data[0]! & modeMask) >> 1;

			if (modeNumber >= codecInfo.vorbisInfo.modeBlockflags.length) {
				throw new Error('Invalid mode number.');
			}

			// In Vorbis, packet duration also depends on the blocksize of the previous packet
			let prevBlocksize = vorbisLastBlocksize;

			const blockflag = codecInfo.vorbisInfo.modeBlockflags[modeNumber]!;
			currentBlocksize = codecInfo.vorbisInfo.blocksizes[blockflag]!;

			if (blockflag === 1) {
				const prevMask = (modeMask | 0x1) + 1;
				const flag = data[0]! & prevMask ? 1 : 0;
				prevBlocksize = codecInfo.vorbisInfo.blocksizes[flag]!;
			}

			durationInSamples = prevBlocksize !== null
				? (prevBlocksize + currentBlocksize) >> 2
				: 0; // The first sample outputs no audio data and therefore has a duration of 0
		} else if (codecInfo.codec === 'opus') {
			const toc = parseOpusTocByte(data);
			durationInSamples = toc.durationInSamples;
		}
	}

	return {
		durationInSamples,
		vorbisBlockSize: currentBlocksize,
	};
};

export const buildOggMimeType = (info: {
	codecStrings: string[];
}) => {
	let string = 'audio/ogg';

	if (info.codecStrings) {
		const uniqueCodecMimeTypes = [...new Set(info.codecStrings)];
		string += `; codecs="${uniqueCodecMimeTypes.join(', ')}"`;
	}

	return string;
};
