/**
 *	Author: JCloudYu
 *	Create: 2020/06/28
**/
export const TrimId = (()=>{
	"use strict";
	
	// See http://www.isthe.com/chongo/tech/comp/fnv/#FNV-param for the definition of these parameters;
	const FNV_PRIME_HIGH = 0x0100, FNV_PRIME_LOW = 0x0193;	// 16777619 0x01000193
	const OFFSET_BASIS = new Uint8Array([0xC5, 0x9D, 0x1C, 0x81]);	// 2166136261 [0x81, 0x1C, 0x9D, 0xC5]
	const BASE32_MAP = "0123456789abcdefghijklmnopqrstuv".split('');
	const ENV = {
		SEQ:Math.floor(Math.random() * 0xFFFFFFFF),
		PID:0, PPID:0, MACHINE_ID:new Uint8Array(0)
	};
	
	if ( typeof Buffer !== "undefined" && typeof process !== undefined ) {
		ENV.MACHINE_ID = fnv1a32(UTF8Encode(require('os').hostname()));
		ENV.PID = process.pid;
		ENV.PPID = process.ppid;
	}
	else {
		let hostname = '';
		if ( typeof window !== undefined ) {
			hostname = window.location.host;
		}
		else {
			const HOSTNAME_CANDIDATES = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWZYZ_-";
		
			let count = 30;
			while(count-- > 0) hostname += HOSTNAME_CANDIDATES[(Math.random() * HOSTNAME_CANDIDATES.length)|0]
		}

		ENV.MACHINE_ID = fnv1a32(UTF8Encode(hostname));
		ENV.PID = (Math.random() * 65535)|0;
		ENV.PPID = (Math.random() * 65535)|0;
	}



	return function GenTrimId() {
		const time	= Date.now();
		const time_lower = time%0xFFFFFFFF;
		const inc	= (ENV.SEQ=(ENV.SEQ+1) % 0xFFFFFFFF);
		const buff	= new Uint8Array(14);
		const view	= new DataView(buff.buffer);
		
		view.setUint32(0, time_lower, false);				// [0-3] epoch time upper
		buff.set(ENV.MACHINE_ID, 4);					// [4-7] machine id
		view.setUint16(8, ENV.PID,  false);				// [8-9] pid
		view.setUint32(10, inc,	 false);					// [10-13] seq
		
		return Base32Encode(buff);
	};


	


	
	function Base32Encode(bytes:Uint8Array):string {
		if ( bytes.length < 1 ) return '';
		
		
		// Run complete bundles
		let encoded = '';
		let begin, loop = Math.floor(bytes.length/5);
		for (let run=0; run<loop; run++) {
			begin = run * 5;
			encoded += BASE32_MAP[  bytes[begin]           >> 3];								// 0
			encoded += BASE32_MAP[ (bytes[begin  ] & 0x07) << 2 | (bytes[begin+1] >> 6)];	// 1
			encoded += BASE32_MAP[ (bytes[begin+1] & 0x3E) >> 1];								// 2
			encoded += BASE32_MAP[ (bytes[begin+1] & 0x01) << 4 | (bytes[begin+2] >> 4)];	// 3
			encoded += BASE32_MAP[ (bytes[begin+2] & 0x0F) << 1 | (bytes[begin+3] >> 7)];	// 4
			encoded += BASE32_MAP[ (bytes[begin+3] & 0x7C) >> 2];								// 5
			encoded += BASE32_MAP[ (bytes[begin+3] & 0x03) << 3 | (bytes[begin+4] >> 5)];	// 6
			encoded += BASE32_MAP[  bytes[begin+4] & 0x1F];										// 7
		}
		
		// Run remains
		let remain = bytes.length % 5;
		if ( remain === 0 ) { return encoded; }
		
		
		begin = loop*5;
		if ( remain === 1 ) {
			encoded += BASE32_MAP[  bytes[begin]           >> 3];								// 0
			encoded += BASE32_MAP[ (bytes[begin  ] & 0x07) << 2];								// 1
		}
		else
		if ( remain === 2 ) {
			encoded += BASE32_MAP[  bytes[begin]           >> 3];								// 0
			encoded += BASE32_MAP[ (bytes[begin  ] & 0x07) << 2 | (bytes[begin+1] >> 6)];	// 1
			encoded += BASE32_MAP[ (bytes[begin+1] & 0x3E) >> 1];								// 2
			encoded += BASE32_MAP[ (bytes[begin+1] & 0x01) << 4];								// 3
		}
		else
		if ( remain === 3 ) {
			encoded += BASE32_MAP[  bytes[begin]           >> 3];								// 0
			encoded += BASE32_MAP[ (bytes[begin  ] & 0x07) << 2 | (bytes[begin+1] >> 6)];	// 1
			encoded += BASE32_MAP[ (bytes[begin+1] & 0x3E) >> 1];								// 2
			encoded += BASE32_MAP[ (bytes[begin+1] & 0x01) << 4 | (bytes[begin+2] >> 4)];	// 3
			encoded += BASE32_MAP[ (bytes[begin+2] & 0x0F) << 1];								// 4
		}
		else
		if ( remain === 4 ) {
			encoded += BASE32_MAP[  bytes[begin]           >> 3];								// 0
			encoded += BASE32_MAP[ (bytes[begin  ] & 0x07) << 2 | (bytes[begin+1] >> 6)];	// 1
			encoded += BASE32_MAP[ (bytes[begin+1] & 0x3E) >> 1];								// 2
			encoded += BASE32_MAP[ (bytes[begin+1] & 0x01) << 4 | (bytes[begin+2] >> 4)];	// 3
			encoded += BASE32_MAP[ (bytes[begin+2] & 0x0F) << 1 | (bytes[begin+3] >> 7)];	// 4
			encoded += BASE32_MAP[ (bytes[begin+3] & 0x7C) >> 2];								// 5
			encoded += BASE32_MAP[ (bytes[begin+3] & 0x03) << 3];								// 6
		}
		
		return encoded;
	}
	function UTF8Encode(str:string):Uint8Array {
		if ( typeof str !== "string" ) {
			throw new TypeError( "Given input argument must be a js string!" );
		}
	
		let codePoints = [];
		let i=0;
		while( i < str.length ) {
			let codePoint = str.codePointAt(i);
			if ( codePoint === undefined ) throw new RangeError("Given string cannot be encoded into utf8!");
			
			// 1-byte sequence
			if( (codePoint & 0xffffff80) === 0 ) {
				codePoints.push(codePoint);
			}
			// 2-byte sequence
			else if( (codePoint & 0xfffff800) === 0 ) {
				codePoints.push(
					0xc0 | (0x1f & (codePoint >> 6)),
					0x80 | (0x3f & codePoint)
				);
			}
			// 3-byte sequence
			else if( (codePoint & 0xffff0000) === 0 ) {
				codePoints.push(
					0xe0 | (0x0f & (codePoint >> 12)),
					0x80 | (0x3f & (codePoint >> 6)),
					0x80 | (0x3f & codePoint)
				);
			}
			// 4-byte sequence
			else if( (codePoint & 0xffe00000) === 0 ) {
				codePoints.push(
					0xf0 | (0x07 & (codePoint >> 18)),
					0x80 | (0x3f & (codePoint >> 12)),
					0x80 | (0x3f & (codePoint >> 6)),
					0x80 | (0x3f & codePoint)
				);
			}
			
			i += (codePoint>0xFFFF) ? 2 : 1;
		}
		return new Uint8Array(codePoints);
	}
	function fnv1a32(octets:Uint8Array):Uint8Array {
		const U8RESULT		= OFFSET_BASIS.slice(0);
		const U32RESULT		= new Uint32Array(U8RESULT.buffer);
		const RESULT_PROC	= new Uint16Array(U8RESULT.buffer);
		for( let i = 0; i < octets.length; i += 1 ) {
			U32RESULT[0] = U32RESULT[0] ^ octets[i];
			
			let hash_low = RESULT_PROC[0], hash_high = RESULT_PROC[1];
			
			RESULT_PROC[0] = hash_low * FNV_PRIME_LOW;
			RESULT_PROC[1] = hash_low * FNV_PRIME_HIGH + hash_high * FNV_PRIME_LOW + (RESULT_PROC[0]>>>16);
		}
		return U8RESULT;
	}
})();
