/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the Source EULA. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

export function toUint8ArrayBuffer(str: string): ArrayBuffer {

	if (typeof TextEncoder !== 'undefined') {
		return new TextEncoder().encode(str).buffer;
	}

	let i: number, len: number, length = 0, charCode = 0, trailCharCode = 0, codepoint = 0;

	// First pass, for the size
	for (i = 0, len = str.length; i < len; i++) {
		charCode = str.charCodeAt(i);

		// Surrogate pair
		if (charCode >= 0xD800 && charCode < 0xDC00) {
			trailCharCode = str.charCodeAt(++i);

			if (!(trailCharCode >= 0xDC00 && trailCharCode < 0xE000)) {
				throw new Error('Invalid char code');
			}

			// Code point can be obtained by subtracting 0xD800 and 0xDC00 from both char codes respectively
			// and joining the 10 least significant bits from each, finally adding 0x10000.
			codepoint = ((((charCode - 0xD800) & 0x3FF) << 10) | ((trailCharCode - 0xDC00) & 0x3FF)) + 0x10000;

		} else {
			codepoint = charCode;
		}

		length += byteSizeInUTF8(codepoint);
	}

	let result = new ArrayBuffer(length);
	let view = new Uint8Array(result);
	let pos = 0;

	// Second pass, for the data
	for (i = 0, len = str.length; i < len; i++) {
		charCode = str.charCodeAt(i);

		if (charCode >= 0xD800 && charCode < 0xDC00) {
			trailCharCode = str.charCodeAt(++i);
			codepoint = ((((charCode - 0xD800) & 0x3FF) << 10) | ((trailCharCode - 0xDC00) & 0x3FF)) + 0x10000;
		} else {
			codepoint = charCode;
		}

		pos += writeUTF8(codepoint, view, pos);
	}

	return result;
}

function byteSizeInUTF8(codePoint: number): number {
	codePoint = codePoint >>> 0;

	if (codePoint < 0x80) {
		return 1;
	} else if (codePoint < 0x800) {
		return 2;
	} else if (codePoint < 0x10000) {
		return 3;
	} else if (codePoint < 0x200000) {
		return 4;
	} else if (codePoint < 0x4000000) {
		return 5;
	} else if (codePoint < 0x80000000) {
		return 6;
	} else {
		throw new Error('Code point 0x' + toHexString(codePoint) + ' not encodable in UTF8.');
	}
}

function writeUTF8(codePoint: number, buffer: Uint8Array, pos: number): number {

	// How many bits needed for codePoint
	let byteSize = byteSizeInUTF8(codePoint);

	// 0xxxxxxx
	if (byteSize === 1) {
		buffer[pos] = codePoint;
		return 1;
	}

	// 110xxxxx 10xxxxxx
	// 1110xxxx 10xxxxxx 10xxxxxx
	// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
	// 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
	// 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

	// first byte
	buffer[pos] = ((0xFC << (6 - byteSize)) | (codePoint >>> (6 * (byteSize - 1)))) & 0xFF;

	// successive bytes
	for (let i = 1; i < byteSize; i++) {
		buffer[pos + i] = (0x80 | (0x3F & (codePoint >>> (6 * (byteSize - i - 1))))) & 0xFF;
	}

	return byteSize;
}

function leftPad(value: string, length: number, char: string = '0'): string {
	return new Array(length - value.length + 1).join(char) + value;
}

function toHexString(value: number, bitsize: number = 32): string {
	return leftPad((value >>> 0).toString(16), bitsize / 4);
}
