var fs = require('fs');
var regexpu = require('regexpu');

// Generate a map of all codes that appear in emojis
var emojiCodesMap = {};
(function () {
	var emojiTestData = fs.readFileSync('./emoji-test.txt').toString('utf8');
	var emojiTestEntries = emojiTestData.split('\n');

	for (var i = 0, len = emojiTestEntries.length; i < len; i++) {
		var entry = emojiTestEntries[i];

		// strip comments
		entry = entry.replace(/#.*/, '');
		entry = entry.trim();

		// strip status
		entry = entry.replace(/;.*/, '');
		entry = entry.trim();

		if (entry.indexOf(' ') > 0) {
			// ignore combined
			continue;
		}

		if (entry.length === 0) {
			// ignore empty lines
			continue;
		}

		emojiCodesMap[parseInt(entry, 16)] = true;
	}
})();

function toUnicodeCode(n) {
	return n.toString(16).toUpperCase();
}

// Read entire UnicodeData.txt and find contiguous ranges of emojis
var emojiCodes = [];
var BREAK_SIGNAL = -1;
(function () {
	var data = fs.readFileSync('./UnicodeData.txt').toString('utf8');
	var entries = data.split('\n');

	var prevWasEmoji = false;
	for (var i = 0, len = entries.length; i < len; i++) {
		var entry = entries[i];
		var props = entry.split(';');

		var code = parseInt(props[0], 16);
		var _isEmoji = isEmoji(code, emojiCodesMap[code]);

		if (_isEmoji) {
			emojiCodes.push(code);
			prevWasEmoji = true;
		} else if (prevWasEmoji) {
			prevWasEmoji = false;
			emojiCodes.push(BREAK_SIGNAL);
		}
	}
	fs.writeFileSync('emoji-codes.txt', emojiCodes.filter(code => code !== BREAK_SIGNAL).map(toUnicodeCode).join('\n'));
})();

var emojiCodeRanges = [];
(function () {
	var startCode = emojiCodes[0];
	var prevCode = emojiCodes[0];
	for (var i = 1; i < emojiCodes.length; i++) {
		var code = emojiCodes[i];
		if (code === BREAK_SIGNAL) {
			continue;
		}
		if (prevCode + 1 !== code) {
			emojiCodeRanges.push({
				from: startCode,
				to: prevCode
			});
			startCode = code;
		}
		prevCode = code;
	}
	emojiCodeRanges.push({
		from: startCode,
		to: prevCode
	});
})();

fs.writeFileSync('emoji-code-ranges.txt', emojiCodeRanges.map(function (entry) {
	var from = toUnicodeCode(entry.from);
	var to = toUnicodeCode(entry.to);
	if (from === to) {
		return from;
	}
	return from + '-' + to;
}).join('\n'));


var fuzzy = [], fuzzyLength = 0, breakSignal = false;
fuzzy[fuzzyLength++] = {
	from: emojiCodes[0],
	to: emojiCodes[0]
};
for (var i = 1; i < emojiCodes.length; i++) {
	var code = emojiCodes[i];

	if (code === BREAK_SIGNAL) {
		breakSignal = true;
		continue;
	}

	if (breakSignal) {
		fuzzy[fuzzyLength++] = {
			from: code,
			to: code
		};
	} else {
		fuzzy[fuzzyLength - 1].to = code;
	}

	breakSignal = false;
}

fs.writeFileSync('emoji-code-ranges-fuzzy.txt', fuzzy.map(function (entry) {
	var from = toUnicodeCode(entry.from);
	var to = toUnicodeCode(entry.to);
	if (from === to) {
		return from;
	}
	return from + '-' + to;
}).join('\n'));

var isEmojiFuzzy = fuzzy.map(function(entry) {
	var from = '\\u{'+toUnicodeCode(entry.from)+'}';
	var to = '\\u{'+toUnicodeCode(entry.to)+'}';
	if (from === to) {
		return from;
	}
	return from + '-' + to;
}).join('');

var isEmojiVeryImprecise = fuzzy.map(function(entry) {
	if (entry.from + 1 === entry.to) {
		return `(x === ${entry.from}) || (x === ${entry.to})`;
	}
	if (entry.from === entry.to) {
		return `(x === ${entry.from})`;
	}
	return `(x >= ${entry.from} && x <= ${entry.to})`;
}).join(' || ');

console.log('very imprecise test :: (x >= 0x1F1E6 && x <= 0x1F1FF) || ' + isEmojiVeryImprecise);

// Flags use two code points, but they usually start with 1F1E6 - 1F1FF
// REGIONAL INDICATOR SYMBOL LETTER A -> REGIONAL INDICATOR SYMBOL LETTER Z
isEmojiFuzzy += '\\u{1F1E6}-\\u{1F1FF}';


// TODO:
// use U+FE0F as a hint
// http://emojipedia.org/variation-selector-16/
// Variation Selector-16
// An invisible codepoint which specifies that the preceding character should be displayed with emoji presentation. Only required if the preceding character defaults to text presentation.
var regex = '[' + isEmojiFuzzy + ']';

console.log('UNICODE CODE-POINTS RANGES:');
console.log(regex);

var r = regexpu.rewritePattern(regex, 'u');
console.log('------');
console.log(r);
console.log('------');

// GOTTEN FROM ABOVE RESULT
var containsEmoji = /(?:[\u231A\u231B\u23F0\u23F3\u2600-\u27BF\u2B50\u2B55]|\uD83C[\uDDE6-\uDDFF\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F\uDE80-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD00-\uDDFF\uDE70-\uDED6])/;

// SANITY CHECK the regex
(function () {
	var emojiTestData = fs.readFileSync('./emoji-test.txt').toString('utf8');
	var emojiTestEntries = emojiTestData.split('\n');

	var MISSING = [];

	for (var i = 0, len = emojiTestEntries.length; i < len; i++) {
		var original = emojiTestEntries[i];
		var entry = emojiTestEntries[i];

		// strip comments
		entry = entry.replace(/#.*/, '');
		entry = entry.trim();

		// strip status
		entry = entry.replace(/;.*/, '');
		entry = entry.trim();

		if (entry.length === 0) {
			// ignore empty lines
			continue;
		}

		var codes = entry.split(' ').map(function(code) {
			return parseInt(code, 16);
		});

		var str = 'some ';
		for (var j = 0; j < codes.length; j++) {
			str += String.fromCodePoint(codes[j]);
		}
		str += ' text';

		if (containsEmoji.test(str)) {
			// regex captures this input
			continue;
		}

		MISSING.push(original);
	//	MISSING.push('TEST STRING: <<' + str + '>>');

		// console.log(str);

		// String.fromCodePoint()

		// console.log(entry);

		// if (entry.indexOf(' ') > 0) {
		// 	// ignore combined
		// 	continue;
		// }

		// if (entry.length === 0) {
		// 	// ignore empty lines
		// 	continue;
		// }

		// emojiCodesMap[parseInt(entry, 16)] = true;
	}

	fs.writeFileSync('emoji-missed.txt', 'THE FOLLOWING ARE NOT CAPTURED BY THE REGEX: \n' + MISSING.join('\n'));

	// console.log(MISSING);
})();


function isEmoji(code, isInEmojiTest) {
	var block = getCodeBlock(code);

	// Exclude some codes in emoji-test.txt that don't look like emojis.

	if (block === 'Latin-1 Supplement') {
		// e.g. 00A9 © copyright
		return false;
	}
	if (block === 'General Punctuation') {
		// e.g. 203C ‼️ double exclamation mark
		return false;
	}
	if (block === 'Letterlike Symbols') {
		// e.g. 2122 ️️™️ trade mark
		return false;
	}
	if (block === 'Arrows') {
		// e.g. 2194 ↔ left-right arrow
		return false;
	}
	if (block === 'Miscellaneous Technical') {
		// U+2300 -> U+23FE
		if (code === 0x231A) { // ⌚ watch
			return true;
		}
		if (code === 0x231B) { // ⌛ hourglass
			return true;
		}
		if (code === 0x23F0) { // ⏰ alarm clock
			return true;
		}
		if (code === 0x23F3) { // ⏳ hourglass with flowing sand
			return true;
		}
		return false;
	}

	if (block === 'Enclosed Alphanumerics') {
		// e.g. 24C2 Ⓜ circled M
		return false;
	}
	if (block === 'Geometric Shapes') {
		// e.g. 25AA ▪ black small square
		return false;
	}
	if (block === 'Supplemental Arrows-B') {
		// e.g. 2934 ⤴ right arrow curving up
		return false;
	}
	if (block === 'Miscellaneous Symbols and Arrows') {
		if (code === 0x2B50) { // ⭐ white medium star
			return true;
		}
		if (code === 0x2B55) { // ⭕ heavy large circle
			return true;
		}
		return false;
	}
	if (block === 'CJK Symbols and Punctuation') {
		// e.g. 303D 〽 part alternation mark
		return false;
	}
	if (block === 'Enclosed CJK Letters and Months') {
		// e.g. 3297 ㊗ Japanese “congratulations” button
		return false;
	}
	if (block === 'Mahjong Tiles') {
		// e.g. 1F004 🀄 mahjong red dragon
		return false;
	}
	if (block === 'Playing Cards') {
		// e.g. 1F0CF 🃏 joker
		return false;
	}
	if (block === 'Enclosed Alphanumeric Supplement') {
		// e.g. 1F170 🅰 A button (blood type)
		return false;
	}
	if (block === 'Enclosed Ideographic Supplement') {
		// e.g. 1F201 🈁 Japanese “here” button
		return false;
	}



	// include some blocks that look like emojis
	if (block === 'Miscellaneous Symbols') {
		return true;
	}
	if (block === 'Dingbats') {
		return true;
	}
	if (block === 'Miscellaneous Symbols and Pictographs') {
		return true;
	}
	if (block === 'Emoticons') {
		return true;
	}
	if (block === 'Transport and Map Symbols') {
		return true;
	}
	if (block === 'Supplemental Symbols and Pictographs') {
		return true;
	}

	return isInEmojiTest;
}

// http://www.fileformat.info/info/unicode/block/index.htm
// replace ^([^\t]+)\tU\+([0-9A-Z]+)\tU\+([0-9A-Z]+)
// with if (code >= 0x$2 && code <= 0x$3) return '$1';
function getCodeBlock(code) {
	if (code >= 0x0000 && code <= 0x007F) return 'Basic Latin';
	if (code >= 0x0080 && code <= 0x00FF) return 'Latin-1 Supplement';
	if (code >= 0x0100 && code <= 0x017F) return 'Latin Extended-A';
	if (code >= 0x0180 && code <= 0x024F) return 'Latin Extended-B';
	if (code >= 0x0250 && code <= 0x02AF) return 'IPA Extensions';
	if (code >= 0x02B0 && code <= 0x02FF) return 'Spacing Modifier Letters';
	if (code >= 0x0300 && code <= 0x036F) return 'Combining Diacritical Marks';
	if (code >= 0x0370 && code <= 0x03FF) return 'Greek and Coptic';
	if (code >= 0x0400 && code <= 0x04FF) return 'Cyrillic';
	if (code >= 0x0500 && code <= 0x052F) return 'Cyrillic Supplement';
	if (code >= 0x0530 && code <= 0x058F) return 'Armenian';
	if (code >= 0x0590 && code <= 0x05FF) return 'Hebrew';
	if (code >= 0x0600 && code <= 0x06FF) return 'Arabic';
	if (code >= 0x0700 && code <= 0x074F) return 'Syriac';
	if (code >= 0x0750 && code <= 0x077F) return 'Arabic Supplement';
	if (code >= 0x0780 && code <= 0x07BF) return 'Thaana';
	if (code >= 0x07C0 && code <= 0x07FF) return 'NKo';
	if (code >= 0x0800 && code <= 0x083F) return 'Samaritan';
	if (code >= 0x0840 && code <= 0x085F) return 'Mandaic';
	if (code >= 0x08A0 && code <= 0x08FF) return 'Arabic Extended-A';
	if (code >= 0x0900 && code <= 0x097F) return 'Devanagari';
	if (code >= 0x0980 && code <= 0x09FF) return 'Bengali';
	if (code >= 0x0A00 && code <= 0x0A7F) return 'Gurmukhi';
	if (code >= 0x0A80 && code <= 0x0AFF) return 'Gujarati';
	if (code >= 0x0B00 && code <= 0x0B7F) return 'Oriya';
	if (code >= 0x0B80 && code <= 0x0BFF) return 'Tamil';
	if (code >= 0x0C00 && code <= 0x0C7F) return 'Telugu';
	if (code >= 0x0C80 && code <= 0x0CFF) return 'Kannada';
	if (code >= 0x0D00 && code <= 0x0D7F) return 'Malayalam';
	if (code >= 0x0D80 && code <= 0x0DFF) return 'Sinhala';
	if (code >= 0x0E00 && code <= 0x0E7F) return 'Thai';
	if (code >= 0x0E80 && code <= 0x0EFF) return 'Lao';
	if (code >= 0x0F00 && code <= 0x0FFF) return 'Tibetan';
	if (code >= 0x1000 && code <= 0x109F) return 'Myanmar';
	if (code >= 0x10A0 && code <= 0x10FF) return 'Georgian';
	if (code >= 0x1100 && code <= 0x11FF) return 'Hangul Jamo';
	if (code >= 0x1200 && code <= 0x137F) return 'Ethiopic';
	if (code >= 0x1380 && code <= 0x139F) return 'Ethiopic Supplement';
	if (code >= 0x13A0 && code <= 0x13FF) return 'Cherokee';
	if (code >= 0x1400 && code <= 0x167F) return 'Unified Canadian Aboriginal Syllabics';
	if (code >= 0x1680 && code <= 0x169F) return 'Ogham';
	if (code >= 0x16A0 && code <= 0x16FF) return 'Runic';
	if (code >= 0x1700 && code <= 0x171F) return 'Tagalog';
	if (code >= 0x1720 && code <= 0x173F) return 'Hanunoo';
	if (code >= 0x1740 && code <= 0x175F) return 'Buhid';
	if (code >= 0x1760 && code <= 0x177F) return 'Tagbanwa';
	if (code >= 0x1780 && code <= 0x17FF) return 'Khmer';
	if (code >= 0x1800 && code <= 0x18AF) return 'Mongolian';
	if (code >= 0x18B0 && code <= 0x18FF) return 'Unified Canadian Aboriginal Syllabics Extended';
	if (code >= 0x1900 && code <= 0x194F) return 'Limbu';
	if (code >= 0x1950 && code <= 0x197F) return 'Tai Le';
	if (code >= 0x1980 && code <= 0x19DF) return 'New Tai Lue';
	if (code >= 0x19E0 && code <= 0x19FF) return 'Khmer Symbols';
	if (code >= 0x1A00 && code <= 0x1A1F) return 'Buginese';
	if (code >= 0x1A20 && code <= 0x1AAF) return 'Tai Tham';
	if (code >= 0x1AB0 && code <= 0x1AFF) return 'Combining Diacritical Marks Extended';
	if (code >= 0x1B00 && code <= 0x1B7F) return 'Balinese';
	if (code >= 0x1B80 && code <= 0x1BBF) return 'Sundanese';
	if (code >= 0x1BC0 && code <= 0x1BFF) return 'Batak';
	if (code >= 0x1C00 && code <= 0x1C4F) return 'Lepcha';
	if (code >= 0x1C50 && code <= 0x1C7F) return 'Ol Chiki';
	if (code >= 0x1C80 && code <= 0x1C8F) return 'Cyrillic Extended-C';
	if (code >= 0x1CC0 && code <= 0x1CCF) return 'Sundanese Supplement';
	if (code >= 0x1CD0 && code <= 0x1CFF) return 'Vedic Extensions';
	if (code >= 0x1D00 && code <= 0x1D7F) return 'Phonetic Extensions';
	if (code >= 0x1D80 && code <= 0x1DBF) return 'Phonetic Extensions Supplement';
	if (code >= 0x1DC0 && code <= 0x1DFF) return 'Combining Diacritical Marks Supplement';
	if (code >= 0x1E00 && code <= 0x1EFF) return 'Latin Extended Additional';
	if (code >= 0x1F00 && code <= 0x1FFF) return 'Greek Extended';
	if (code >= 0x2000 && code <= 0x206F) return 'General Punctuation';
	if (code >= 0x2070 && code <= 0x209F) return 'Superscripts and Subscripts';
	if (code >= 0x20A0 && code <= 0x20CF) return 'Currency Symbols';
	if (code >= 0x20D0 && code <= 0x20FF) return 'Combining Diacritical Marks for Symbols';
	if (code >= 0x2100 && code <= 0x214F) return 'Letterlike Symbols';
	if (code >= 0x2150 && code <= 0x218F) return 'Number Forms';
	if (code >= 0x2190 && code <= 0x21FF) return 'Arrows';
	if (code >= 0x2200 && code <= 0x22FF) return 'Mathematical Operators';
	if (code >= 0x2300 && code <= 0x23FF) return 'Miscellaneous Technical';
	if (code >= 0x2400 && code <= 0x243F) return 'Control Pictures';
	if (code >= 0x2440 && code <= 0x245F) return 'Optical Character Recognition';
	if (code >= 0x2460 && code <= 0x24FF) return 'Enclosed Alphanumerics';
	if (code >= 0x2500 && code <= 0x257F) return 'Box Drawing';
	if (code >= 0x2580 && code <= 0x259F) return 'Block Elements';
	if (code >= 0x25A0 && code <= 0x25FF) return 'Geometric Shapes';
	if (code >= 0x2600 && code <= 0x26FF) return 'Miscellaneous Symbols';
	if (code >= 0x2700 && code <= 0x27BF) return 'Dingbats';
	if (code >= 0x27C0 && code <= 0x27EF) return 'Miscellaneous Mathematical Symbols-A';
	if (code >= 0x27F0 && code <= 0x27FF) return 'Supplemental Arrows-A';
	if (code >= 0x2800 && code <= 0x28FF) return 'Braille Patterns';
	if (code >= 0x2900 && code <= 0x297F) return 'Supplemental Arrows-B';
	if (code >= 0x2980 && code <= 0x29FF) return 'Miscellaneous Mathematical Symbols-B';
	if (code >= 0x2A00 && code <= 0x2AFF) return 'Supplemental Mathematical Operators';
	if (code >= 0x2B00 && code <= 0x2BFF) return 'Miscellaneous Symbols and Arrows';
	if (code >= 0x2C00 && code <= 0x2C5F) return 'Glagolitic';
	if (code >= 0x2C60 && code <= 0x2C7F) return 'Latin Extended-C';
	if (code >= 0x2C80 && code <= 0x2CFF) return 'Coptic';
	if (code >= 0x2D00 && code <= 0x2D2F) return 'Georgian Supplement';
	if (code >= 0x2D30 && code <= 0x2D7F) return 'Tifinagh';
	if (code >= 0x2D80 && code <= 0x2DDF) return 'Ethiopic Extended';
	if (code >= 0x2DE0 && code <= 0x2DFF) return 'Cyrillic Extended-A';
	if (code >= 0x2E00 && code <= 0x2E7F) return 'Supplemental Punctuation';
	if (code >= 0x2E80 && code <= 0x2EFF) return 'CJK Radicals Supplement';
	if (code >= 0x2F00 && code <= 0x2FDF) return 'Kangxi Radicals';
	if (code >= 0x2FF0 && code <= 0x2FFF) return 'Ideographic Description Characters';
	if (code >= 0x3000 && code <= 0x303F) return 'CJK Symbols and Punctuation';
	if (code >= 0x3040 && code <= 0x309F) return 'Hiragana';
	if (code >= 0x30A0 && code <= 0x30FF) return 'Katakana';
	if (code >= 0x3100 && code <= 0x312F) return 'Bopomofo';
	if (code >= 0x3130 && code <= 0x318F) return 'Hangul Compatibility Jamo';
	if (code >= 0x3190 && code <= 0x319F) return 'Kanbun';
	if (code >= 0x31A0 && code <= 0x31BF) return 'Bopomofo Extended';
	if (code >= 0x31C0 && code <= 0x31EF) return 'CJK Strokes';
	if (code >= 0x31F0 && code <= 0x31FF) return 'Katakana Phonetic Extensions';
	if (code >= 0x3200 && code <= 0x32FF) return 'Enclosed CJK Letters and Months';
	if (code >= 0x3300 && code <= 0x33FF) return 'CJK Compatibility';
	if (code >= 0x3400 && code <= 0x4DBF) return 'CJK Unified Ideographs Extension A';
	if (code >= 0x4DC0 && code <= 0x4DFF) return 'Yijing Hexagram Symbols';
	if (code >= 0x4E00 && code <= 0x9FFF) return 'CJK Unified Ideographs';
	if (code >= 0xA000 && code <= 0xA48F) return 'Yi Syllables';
	if (code >= 0xA490 && code <= 0xA4CF) return 'Yi Radicals';
	if (code >= 0xA4D0 && code <= 0xA4FF) return 'Lisu';
	if (code >= 0xA500 && code <= 0xA63F) return 'Vai';
	if (code >= 0xA640 && code <= 0xA69F) return 'Cyrillic Extended-B';
	if (code >= 0xA6A0 && code <= 0xA6FF) return 'Bamum';
	if (code >= 0xA700 && code <= 0xA71F) return 'Modifier Tone Letters';
	if (code >= 0xA720 && code <= 0xA7FF) return 'Latin Extended-D';
	if (code >= 0xA800 && code <= 0xA82F) return 'Syloti Nagri';
	if (code >= 0xA830 && code <= 0xA83F) return 'Common Indic Number Forms';
	if (code >= 0xA840 && code <= 0xA87F) return 'Phags-pa';
	if (code >= 0xA880 && code <= 0xA8DF) return 'Saurashtra';
	if (code >= 0xA8E0 && code <= 0xA8FF) return 'Devanagari Extended';
	if (code >= 0xA900 && code <= 0xA92F) return 'Kayah Li';
	if (code >= 0xA930 && code <= 0xA95F) return 'Rejang';
	if (code >= 0xA960 && code <= 0xA97F) return 'Hangul Jamo Extended-A';
	if (code >= 0xA980 && code <= 0xA9DF) return 'Javanese';
	if (code >= 0xA9E0 && code <= 0xA9FF) return 'Myanmar Extended-B';
	if (code >= 0xAA00 && code <= 0xAA5F) return 'Cham';
	if (code >= 0xAA60 && code <= 0xAA7F) return 'Myanmar Extended-A';
	if (code >= 0xAA80 && code <= 0xAADF) return 'Tai Viet';
	if (code >= 0xAAE0 && code <= 0xAAFF) return 'Meetei Mayek Extensions';
	if (code >= 0xAB00 && code <= 0xAB2F) return 'Ethiopic Extended-A';
	if (code >= 0xAB30 && code <= 0xAB6F) return 'Latin Extended-E';
	if (code >= 0xAB70 && code <= 0xABBF) return 'Cherokee Supplement';
	if (code >= 0xABC0 && code <= 0xABFF) return 'Meetei Mayek';
	if (code >= 0xAC00 && code <= 0xD7AF) return 'Hangul Syllables';
	if (code >= 0xD7B0 && code <= 0xD7FF) return 'Hangul Jamo Extended-B';
	if (code >= 0xD800 && code <= 0xDB7F) return 'High Surrogates';
	if (code >= 0xDB80 && code <= 0xDBFF) return 'High Private Use Surrogates';
	if (code >= 0xDC00 && code <= 0xDFFF) return 'Low Surrogates';
	if (code >= 0xE000 && code <= 0xF8FF) return 'Private Use Area';
	if (code >= 0xF900 && code <= 0xFAFF) return 'CJK Compatibility Ideographs';
	if (code >= 0xFB00 && code <= 0xFB4F) return 'Alphabetic Presentation Forms';
	if (code >= 0xFB50 && code <= 0xFDFF) return 'Arabic Presentation Forms-A';
	if (code >= 0xFE00 && code <= 0xFE0F) return 'Variation Selectors';
	if (code >= 0xFE10 && code <= 0xFE1F) return 'Vertical Forms';
	if (code >= 0xFE20 && code <= 0xFE2F) return 'Combining Half Marks';
	if (code >= 0xFE30 && code <= 0xFE4F) return 'CJK Compatibility Forms';
	if (code >= 0xFE50 && code <= 0xFE6F) return 'Small Form Variants';
	if (code >= 0xFE70 && code <= 0xFEFF) return 'Arabic Presentation Forms-B';
	if (code >= 0xFF00 && code <= 0xFFEF) return 'Halfwidth and Fullwidth Forms';
	if (code >= 0xFFF0 && code <= 0xFFFF) return 'Specials';
	if (code >= 0x10000 && code <= 0x1007F) return 'Linear B Syllabary';
	if (code >= 0x10080 && code <= 0x100FF) return 'Linear B Ideograms';
	if (code >= 0x10100 && code <= 0x1013F) return 'Aegean Numbers';
	if (code >= 0x10140 && code <= 0x1018F) return 'Ancient Greek Numbers';
	if (code >= 0x10190 && code <= 0x101CF) return 'Ancient Symbols';
	if (code >= 0x101D0 && code <= 0x101FF) return 'Phaistos Disc';
	if (code >= 0x10280 && code <= 0x1029F) return 'Lycian';
	if (code >= 0x102A0 && code <= 0x102DF) return 'Carian';
	if (code >= 0x102E0 && code <= 0x102FF) return 'Coptic Epact Numbers';
	if (code >= 0x10300 && code <= 0x1032F) return 'Old Italic';
	if (code >= 0x10330 && code <= 0x1034F) return 'Gothic';
	if (code >= 0x10350 && code <= 0x1037F) return 'Old Permic';
	if (code >= 0x10380 && code <= 0x1039F) return 'Ugaritic';
	if (code >= 0x103A0 && code <= 0x103DF) return 'Old Persian';
	if (code >= 0x10400 && code <= 0x1044F) return 'Deseret';
	if (code >= 0x10450 && code <= 0x1047F) return 'Shavian';
	if (code >= 0x10480 && code <= 0x104AF) return 'Osmanya';
	if (code >= 0x104B0 && code <= 0x104FF) return 'Osage';
	if (code >= 0x10500 && code <= 0x1052F) return 'Elbasan';
	if (code >= 0x10530 && code <= 0x1056F) return 'Caucasian Albanian';
	if (code >= 0x10600 && code <= 0x1077F) return 'Linear A';
	if (code >= 0x10800 && code <= 0x1083F) return 'Cypriot Syllabary';
	if (code >= 0x10840 && code <= 0x1085F) return 'Imperial Aramaic';
	if (code >= 0x10860 && code <= 0x1087F) return 'Palmyrene';
	if (code >= 0x10880 && code <= 0x108AF) return 'Nabataean';
	if (code >= 0x108E0 && code <= 0x108FF) return 'Hatran';
	if (code >= 0x10900 && code <= 0x1091F) return 'Phoenician';
	if (code >= 0x10920 && code <= 0x1093F) return 'Lydian';
	if (code >= 0x10980 && code <= 0x1099F) return 'Meroitic Hieroglyphs';
	if (code >= 0x109A0 && code <= 0x109FF) return 'Meroitic Cursive';
	if (code >= 0x10A00 && code <= 0x10A5F) return 'Kharoshthi';
	if (code >= 0x10A60 && code <= 0x10A7F) return 'Old South Arabian';
	if (code >= 0x10A80 && code <= 0x10A9F) return 'Old North Arabian';
	if (code >= 0x10AC0 && code <= 0x10AFF) return 'Manichaean';
	if (code >= 0x10B00 && code <= 0x10B3F) return 'Avestan';
	if (code >= 0x10B40 && code <= 0x10B5F) return 'Inscriptional Parthian';
	if (code >= 0x10B60 && code <= 0x10B7F) return 'Inscriptional Pahlavi';
	if (code >= 0x10B80 && code <= 0x10BAF) return 'Psalter Pahlavi';
	if (code >= 0x10C00 && code <= 0x10C4F) return 'Old Turkic';
	if (code >= 0x10C80 && code <= 0x10CFF) return 'Old Hungarian';
	if (code >= 0x10E60 && code <= 0x10E7F) return 'Rumi Numeral Symbols';
	if (code >= 0x11000 && code <= 0x1107F) return 'Brahmi';
	if (code >= 0x11080 && code <= 0x110CF) return 'Kaithi';
	if (code >= 0x110D0 && code <= 0x110FF) return 'Sora Sompeng';
	if (code >= 0x11100 && code <= 0x1114F) return 'Chakma';
	if (code >= 0x11150 && code <= 0x1117F) return 'Mahajani';
	if (code >= 0x11180 && code <= 0x111DF) return 'Sharada';
	if (code >= 0x111E0 && code <= 0x111FF) return 'Sinhala Archaic Numbers';
	if (code >= 0x11200 && code <= 0x1124F) return 'Khojki';
	if (code >= 0x11280 && code <= 0x112AF) return 'Multani';
	if (code >= 0x112B0 && code <= 0x112FF) return 'Khudawadi';
	if (code >= 0x11300 && code <= 0x1137F) return 'Grantha';
	if (code >= 0x11400 && code <= 0x1147F) return 'Newa';
	if (code >= 0x11480 && code <= 0x114DF) return 'Tirhuta';
	if (code >= 0x11580 && code <= 0x115FF) return 'Siddham';
	if (code >= 0x11600 && code <= 0x1165F) return 'Modi';
	if (code >= 0x11660 && code <= 0x1167F) return 'Mongolian Supplement';
	if (code >= 0x11680 && code <= 0x116CF) return 'Takri';
	if (code >= 0x11700 && code <= 0x1173F) return 'Ahom';
	if (code >= 0x118A0 && code <= 0x118FF) return 'Warang Citi';
	if (code >= 0x11AC0 && code <= 0x11AFF) return 'Pau Cin Hau';
	if (code >= 0x11C00 && code <= 0x11C6F) return 'Bhaiksuki';
	if (code >= 0x11C70 && code <= 0x11CBF) return 'Marchen';
	if (code >= 0x12000 && code <= 0x123FF) return 'Cuneiform';
	if (code >= 0x12400 && code <= 0x1247F) return 'Cuneiform Numbers and Punctuation';
	if (code >= 0x12480 && code <= 0x1254F) return 'Early Dynastic Cuneiform';
	if (code >= 0x13000 && code <= 0x1342F) return 'Egyptian Hieroglyphs';
	if (code >= 0x14400 && code <= 0x1467F) return 'Anatolian Hieroglyphs';
	if (code >= 0x16800 && code <= 0x16A3F) return 'Bamum Supplement';
	if (code >= 0x16A40 && code <= 0x16A6F) return 'Mro';
	if (code >= 0x16AD0 && code <= 0x16AFF) return 'Bassa Vah';
	if (code >= 0x16B00 && code <= 0x16B8F) return 'Pahawh Hmong';
	if (code >= 0x16F00 && code <= 0x16F9F) return 'Miao';
	if (code >= 0x16FE0 && code <= 0x16FFF) return 'Ideographic Symbols and Punctuation';
	if (code >= 0x17000 && code <= 0x187FF) return 'Tangut';
	if (code >= 0x18800 && code <= 0x18AFF) return 'Tangut Components';
	if (code >= 0x1B000 && code <= 0x1B0FF) return 'Kana Supplement';
	if (code >= 0x1BC00 && code <= 0x1BC9F) return 'Duployan';
	if (code >= 0x1BCA0 && code <= 0x1BCAF) return 'Shorthand Format Controls';
	if (code >= 0x1D000 && code <= 0x1D0FF) return 'Byzantine Musical Symbols';
	if (code >= 0x1D100 && code <= 0x1D1FF) return 'Musical Symbols';
	if (code >= 0x1D200 && code <= 0x1D24F) return 'Ancient Greek Musical Notation';
	if (code >= 0x1D300 && code <= 0x1D35F) return 'Tai Xuan Jing Symbols';
	if (code >= 0x1D360 && code <= 0x1D37F) return 'Counting Rod Numerals';
	if (code >= 0x1D400 && code <= 0x1D7FF) return 'Mathematical Alphanumeric Symbols';
	if (code >= 0x1D800 && code <= 0x1DAAF) return 'Sutton SignWriting';
	if (code >= 0x1E000 && code <= 0x1E02F) return 'Glagolitic Supplement';
	if (code >= 0x1E800 && code <= 0x1E8DF) return 'Mende Kikakui';
	if (code >= 0x1E900 && code <= 0x1E95F) return 'Adlam';
	if (code >= 0x1EE00 && code <= 0x1EEFF) return 'Arabic Mathematical Alphabetic Symbols';
	if (code >= 0x1F000 && code <= 0x1F02F) return 'Mahjong Tiles';
	if (code >= 0x1F030 && code <= 0x1F09F) return 'Domino Tiles';
	if (code >= 0x1F0A0 && code <= 0x1F0FF) return 'Playing Cards';
	if (code >= 0x1F100 && code <= 0x1F1FF) return 'Enclosed Alphanumeric Supplement';
	if (code >= 0x1F200 && code <= 0x1F2FF) return 'Enclosed Ideographic Supplement';
	if (code >= 0x1F300 && code <= 0x1F5FF) return 'Miscellaneous Symbols and Pictographs';
	if (code >= 0x1F600 && code <= 0x1F64F) return 'Emoticons';
	if (code >= 0x1F650 && code <= 0x1F67F) return 'Ornamental Dingbats';
	if (code >= 0x1F680 && code <= 0x1F6FF) return 'Transport and Map Symbols';
	if (code >= 0x1F700 && code <= 0x1F77F) return 'Alchemical Symbols';
	if (code >= 0x1F780 && code <= 0x1F7FF) return 'Geometric Shapes Extended';
	if (code >= 0x1F800 && code <= 0x1F8FF) return 'Supplemental Arrows-C';
	if (code >= 0x1F900 && code <= 0x1F9FF) return 'Supplemental Symbols and Pictographs';
	if (code >= 0x20000 && code <= 0x2A6DF) return 'CJK Unified Ideographs Extension B';
	if (code >= 0x2A700 && code <= 0x2B73F) return 'CJK Unified Ideographs Extension C';
	if (code >= 0x2B740 && code <= 0x2B81F) return 'CJK Unified Ideographs Extension D';
	if (code >= 0x2B820 && code <= 0x2CEAF) return 'CJK Unified Ideographs Extension E';
	if (code >= 0x2F800 && code <= 0x2FA1F) return 'CJK Compatibility Ideographs Supplement';
	if (code >= 0xE0000 && code <= 0xE007F) return 'Tags';
	if (code >= 0xE0100 && code <= 0xE01EF) return 'Variation Selectors Supplement';
	if (code >= 0xF0000 && code <= 0xFFFFF) return 'Supplementary Private Use Area-A';
	if (code >= 0x100000 && code <= 0x10FFFF) return 'Supplementary Private Use Area-B';
}
