text: 'use strict';
  
  const fs = require('fs');
  const path = require('path');
  const zlib = require('zlib');
  const jsesc = require('jsesc');
  const mkdirp = require('mkdirp');
  const regenerate = require('regenerate');
  
  const gzipInline = function(data) {
  	if (data instanceof Map) {
  		return `new Map(${ gzipInline([...data]) })`;
  	}
  	const json = jsesc(data, { 'json': true });
  	const gzipBuffer = zlib.gzipSync(json);
  	return `JSON.parse(require('zlib').gunzipSync(${ jsesc(gzipBuffer) }))`;
  };
  
  const range = function(start, stop) {
  	// inclusive, e.g. `range(1, 3)` → `[1, 2, 3]`
  	const result = [];
  	for (; start <= stop; result.push(start++));
  	return result;
  };
  
  const object = {};
  const hasOwnProperty = object.hasOwnProperty;
  const hasKey = function(object, key) {
  	return hasOwnProperty.call(object, key);
  };
  
  const append = function(object, key, value) {
  	if (hasKey(object, key)) {
  		object[key].push(value);
  	} else {
  		object[key] = [value];
  	}
  };
  
  const writeFiles = function(options) {
  	const version = options.version;
  	const map = options.map;
  	if (map == null) {
  		return;
  	}
  	const dirMap = {};
  	const auxMap = {};
  	Object.keys(map).forEach(function(item) {
  		const codePoints = map[item];
  		const type = typeof options.type == 'function'
  			? options.type(item)
  			: options.type;
  		const isCaseFolding = type == 'Case_Folding';
  		const isBidiClass = type == 'Bidi_Class';
  		if (isBidiClass) {
  			item = item.replace(/^Bidi_/, '');
  		}
  		const dir = path.resolve(
  			__dirname, '..',
  			'output', 'unicode-' + version, type, item
  		);
  		if (
  			type == 'Bidi_Class' ||
  			type == 'Bidi_Mirroring_Glyph' ||
  			type == 'Bidi_Paired_Bracket_Type' ||
  			type == 'Names' ||
  			(
  				type == 'General_Category' &&
  				// Use the most specific category names, i.e. those whose aliases match
  				// `^[A-Z][a-z]$`. Ignore the others.
  				!/^(?:Other|Letter|Cased_Letter|Mark|Number|Punctuation|Symbol|Separator)$/.test(item)
  			)
  		) {
  			if (!auxMap[type]) {
  				auxMap[type] = [];
  			}
  			codePoints.forEach(function(codePoint) {
  				console.assert(!auxMap[type][codePoint]);
  				auxMap[type][codePoint] = item;
  			});
  		}
  		if (type == 'Bidi_Mirroring_Glyph' || type == 'Names') {
  			return;
  		}
  		append(dirMap, type, item);
  		// Create the target directory if it doesn’t exist yet.
  		mkdirp.sync(dir);
  
  		// Sequence properties are special.
  		if (type == 'Sequence_Property') {
  			const sequences = codePoints;
  			const output = `module.exports=${ gzipInline(map[item]) }`;
  			fs.writeFileSync(
  				path.resolve(dir, 'index.js'),
  				output
  			);
  			return;
  		}
  
  		// Save the data to a file
  		fs.writeFileSync(
  			path.resolve(dir, 'code-points.js'),
  			'module.exports=' + (
  				codePoints.length > 999 ? gzipInline : jsesc
  			)(codePoints)
  		);
  		if (!isCaseFolding) {
  			fs.writeFileSync(
  				path.resolve(dir, 'regex.js'),
  				'module.exports=/' + regenerate(codePoints).toString() + '/'
  			);
  		}
  
  		const symbols = isCaseFolding ?
  			(() => {
  				const result = new Map();
  				for (let [from, to] of codePoints) {
  					from = String.fromCodePoint(from);
  					if (Array.isArray(to)) {
  						to = String.fromCodePoint.apply(null, to);
  					} else {
  						to = String.fromCodePoint(to);
  					}
  					result.set(from, to);
  				}
  				return result;
  			})() :
  			codePoints.map((codePoint) => String.fromCodePoint(codePoint));
  		fs.writeFileSync(
  			path.resolve(dir, 'symbols.js'),
  			'module.exports=' + (
  				!isCaseFolding && symbols.length > 999 ? gzipInline : jsesc
  			)(symbols)
  		);
  	});
  	Object.keys(auxMap).forEach(function(type) {
  		const dir = path.resolve(
  			__dirname, '..',
  			'output', 'unicode-' + version, type
  		);
  		if (!hasKey(dirMap, type)) {
  			dirMap[type] = [];
  		}
  		mkdirp.sync(dir);
  		let output = '';
  		if (/^(?:Bidi_Class|Bidi_Mirroring_Glyph|bidi-brackets|Names)$/.test(type)) {
  			const map = new Map();
  			Object.keys(auxMap[type]).forEach(function(key) {
  				const codePoint = Number(key);
  				const value = auxMap[type][key];
  				map.set(codePoint, value);
  			});
  			if ('Bidi_Mirroring_Glyph' == type) { // `Bidi_Mirroring_Glyph/index.js`
  				// Note: `Bidi_Mirroring_Glyph` doesn’t have repeated strings; don’t gzip.
  				output = `module.exports=${ jsesc(map) }`;
  			} else { // `Bidi_Class/index.js` or `bidi-brackets/index.js` or `Names/index.js`
  				output = `module.exports=${ gzipInline(map) }`;
  			}
  		} else { // `categories/index.js`
  			const array = auxMap[type];
  			output = `var x=${ gzipInline(array) };module.exports=new Map(x.entries())`;
  		}
  		fs.writeFileSync(
  			path.resolve(dir, 'index.js'),
  			output
  		);
  	});
  	return dirMap;
  };
  
  const extend = function(destination, source) {
  	for (var key in source) {
  		if (hasKey(source, key)) {
  			if (!hasKey(destination, key)) {
  				destination[key] = [];
  			}
  			source[key].forEach(function(item) {
  				append(destination, key, item);
  			});
  		}
  	}
  };
  
  const readDataFile = function(version, type) {
  	const sourceFile = path.resolve(
  		__dirname, '..',
  		'data', version + '-' + type + '.txt'
  	);
  	if (!fs.existsSync(sourceFile)) {
  		return;
  	}
  	const source = fs.readFileSync(sourceFile, 'utf-8');
  	return source;
  };
  
  module.exports = {
  	'range': range,
  	'append': append,
  	'extend': extend,
  	'readDataFile': readDataFile,
  	'writeFiles': writeFiles
  };
  