/**
 * @name CeL data.Convert_Pairs function
 * @fileoverview 本檔案包含了 data.Convert_Pairs 處理的 functions。
 * 
 * TODO: Should use Map()
 * 
 * @since 2022/2/10 8:43:7
 */

'use strict';
// 'use asm';

// --------------------------------------------------------------------------------------------

typeof CeL === 'function' && CeL.run({
	// module name
	name : 'data.Convert_Pairs',

	require : 'data.|data.code.compatibility.|data.native.',

	// 設定不匯出的子函式。
	no_extend : '*',

	// 為了方便格式化程式碼，因此將 module 函式主體另外抽出。
	code : module_code
});

function module_code(library_namespace) {

	// requiring

	// ---------------------------------------------------------------------//
	// see data.native.String_to_RegExp
	/**
	 * 主要目的：解析文字 source 成 Object，以及用來作 convert。
	 * 
	 * TODO:<br />
	 * 整合 application.OS.Windows.file.cacher
	 * 
	 * @example <code>

	 // example 1
	 var conversion_pair = new CeL.data.Convert_Pairs(CeL.get_file(path));
	 text = conversion_pair.convert(text);


	 // example 2
	 CeL.run([ 'data.file', 'application.OS.Windows.file' ]);
	 var cache_file = 'work.codes/get_work_information.cache.txt', cache_pair,
	 // target encoding
	 target_encoding = 'UTF-8';

	 cache_pair = new CeL.data.Convert_Pairs(null, {
		path : cache_file,
		encoding : target_encoding,
		remove_comments : true
	 });

	 cache_pair.add([ [ 'key 1', 'value 1' ] ]);

	 CeL.log(cache_pair.select('key 1'));

	 cache_pair.save_new();

	 * </code>
	 * 
	 * @param {Object|Array}source
	 * @param {Object}options
	 */
	function Convert_Pairs(source, options) {
		if (!is_Convert_Pairs(this)) {
			library_namespace
					.warn('Convert_Pairs: Please use (pair = new Convert_Pairs()) instead of (pair = Convert_Pairs())!');
			return new Convert_Pairs(source, options);
		}

		// 前置處理。
		options = library_namespace.setup_options(options);

		var _this = this;
		function copy_properties_from_options() {
			// 單一 instance 僅能設定一個 replace_flags。
			// Convert_Pairs.prototype.add() 不設定 this.flags。
			[ 'flags', 'may_remove_pair_Map' ].forEach(function(property) {
				if (options[property]) {
					_this[property] = options[property];
				}
			});
		}

		if (is_Convert_Pairs(source)) {
			// is_clone
			// library_namespace.is_Object(source) @ Firefox
			// assert: library_namespace.is_Object(source.pair);
			// assert: Array.isArray(source.keys);
			Object.assign(this, source);
			this.pair_Map = new Map(source.pair_Map);
			copy_properties_from_options();
		} else {
			copy_properties_from_options();
			// Warning: 手動設定 this.pair_Map 非常危險!
			// initialization.
			this.pair_Map = new Map;
			if (source)
				this.add(source, options);
		}

		if (options.path) {
			this.add_path(options);
		}
	}

	// if the value is instance of Convert_Pairs
	function is_Convert_Pairs(value) {
		return value && value.constructor === Convert_Pairs;
	}

	/**
	 * 排除/移除注解。 strip/remove javascript comments.
	 * CeL.data.Convert_Pairs.remove_comments(text)
	 * 
	 * @see http://vrana.github.io/JsShrink/
	 * @see http://trinithis.awardspace.com/commentStripper/stripper.html
	 * @see http://upshots.org/javascript/javascript-regexp-to-remove-comments
	 * @see http://marijnhaverbeke.nl//uglifyjs
	 */
	function remove_comments(text) {
		// 僅作最簡單之處理，未考量: "// .. /*", "// .. */", "// /* .. */",
		// 以及 RegExp, "", '' 中注解的情況!
		return String(text)
		// /* ... */
		.replace(/\/\*[\s\S]*?\*\//g, '')
		// // ...
		.replace(/\/\/[^\r\n]*/g, '');

		// text.replace(/<!--[\s\S]*?-->/g, '');

		// TODO: /^#/
	}

	library_namespace.set_method(Convert_Pairs, {
		remove_comments : remove_comments,
		is_Convert_Pairs : is_Convert_Pairs,

		KEY_REMOVE : typeof Symbol === 'function' ? Symbol('remove the key')
				: {
					KEY_REMOVE : true
				}
	});

	// ----------------------------------------------------------------------------------

	function get_pair_Map_of_key(key) {
		// assert: typeof key === 'string'
		var pair_Map_by_length = this.special_keys_Map
				&& this.pair_Map_by_length, pair_Map;
		if (pair_Map_by_length) {
			pair_Map = pair_Map_by_length[key.length];
			if (!pair_Map)
				return;
		} else {
			pair_Map = this.pair_Map;
		}
	}

	function Convert_Pairs__get_value(key) {
		// assert: typeof key === 'string'
		var pair_Map = get_pair_Map_of_key.call(this, key);
		return pair_Map && pair_Map.get(key);
	}

	function Convert_Pairs__add(source, options) {
		// 前置處理。
		options = library_namespace.setup_options(options);

		var pair_Map = this.pair_Map;

		if (library_namespace.is_Object(source)) {
			for ( var key in source) {
				if (value === Convert_Pairs.KEY_REMOVE)
					pair_Map['delete'](key, source[key]);
				else
					pair_Map.set(key, source[key]);
			}
			delete this.special_keys_Map;
			return this;
		}

		if (typeof source === 'string') {
			if (options.remove_comments)
				source = Convert_Pairs.remove_comments(source);
			// console.trace([ source ]);
			if (!source.trim())
				return;
			// 順便正規化。
			var separator = options.field_separator
			//
			|| this.field_separator;
			if (!separator) {
				// 偵測是 key=value,key=value，
				// 或 key \t value \n key \t value
				if (/[\r\n]/.test(source))
					separator = /[\r\n]+/;
				else if (separator = source.match(/[,;|]/))
					separator = separator[0];
				if (separator)
					library_namespace.debug('Use field separator: ['
							+ separator + ']', 2, 'Convert_Pairs.add');
			}
			if (separator)
				source = source.split(separator);
			else {
				library_namespace
						.warn('Convert_Pairs.add: Cannot determine the field separator! '
								+ source);
				source = [ source ];
			}
		}

		if (!Array.isArray(source) || source.length === 0) {
			return this;
		}

		// --------------------------------------------------------------------

		var length = source.length,
		// options: 僅納入 key 與 value 不同之 pair。
		no_the_same_key_value = options.no_the_same_key_value,
		// key / value 分隔符號。
		separator = options.separator || this.separator,
		//
		key_is_number = options.key_is_number,
		//
		value_is_number = options.value_is_number,
		//
		item_processor = typeof options.item_processor === 'function'
				&& options.item_processor,
		//
		dictionary_path = this.path;

		if (!separator && typeof source[0] === 'string') {
			// 遍歷 source 以偵測是 key=value,key=value，
			// 或 key \t value \n key \t value
			for (var i = 0; i < length; i++) {
				if (typeof source[i] === 'string'
						&& (separator = source[i].match(/[^\n]([\t=])[^\n]/))) {
					separator = separator[1];
					library_namespace.debug('Use assignment sign: '
							+ new RegExp(separator), 3, 'Convert_Pairs.add');
					break;
				}
			}
			if (!separator) {
				// console.trace(source);
				throw new Error(
						'Convert_Pairs.add: No assignment sign detected! 請手動指定！');
			}
		}

		library_namespace.debug('Add ' + source.length + ' pairs...', 3,
				'Convert_Pairs.add');
		source.forEach(function(item) {
			if (item_processor) {
				item = item_processor(/* {String} */item, options);
			}
			if (!item)
				return;

			if (false && typeof item === 'string' && !item.trim()) {
				console.log(item.charCodeAt(0));
				console.trace(JSON.stringify(item));
			}
			if (typeof item === 'string')
				item = item.split(separator);
			var key = item[0], value = item[1];
			if (!key) {
				library_namespace.warn('Convert_Pairs.add: 未設定 key: '
						+ item.join(separator));
				return;
			}
			if (!value) {
				var matched = key
						.match(library_namespace.PATTERN_RegExp_replacement);
				if (matched) {
					key = '/' + matched[1] + '/' + matched[3];
					value = matched[2];
				}
				if (!value) {
					library_namespace.warn('Convert_Pairs.add: 轉換時將刪除 '
							+ JSON.stringify(key));
				}
			}

			library_namespace.debug('adding [' + key + '] → ['
			// Cannot convert a Symbol value to a string
			+ String(value) + ']', source.length > 200 ? 3 : 2,
					'Convert_Pairs.add');
			if (key === value) {
				library_namespace.debug('key 與 value 相同，項目沒有改變：[' + key + ']'
				//
				+ (dictionary_path ? ' (' + dictionary_path + ')' : ''), 2,
						'Convert_Pairs.add');
				if (no_the_same_key_value) {
					return;
				}
				// 長度為 1 的沒有轉換必要。
				if (no_the_same_key_value !== false && key.length === 1) {
					// 後來的會覆蓋前面的。
					if (pair_Map.has(key))
						pair_Map['delete'](key);
					return;
				}
				// 可能是為了確保不被改變而設定。
			}

			if (value === Convert_Pairs.KEY_REMOVE) {
				library_namespace.debug('Remove [' + key + ']: '
						+ pair_Map[key], 0, 'Convert_Pairs.add');
				// if (pair_Map.has(key)) { }
				pair_Map['delete'](key);
				return;
			}

			if (key_is_number && !isNaN(key))
				key = +key;
			if (value_is_number && !isNaN(value))
				value = +value;

			if (pair_Map.has(key)) {
				if (value === pair_Map.get(key)) {
					library_namespace.info('Convert_Pairs.add: 重複設定相同的['
					//
					+ key + ']=[' + value + ']'
					//
					+ (dictionary_path ? ' (' + dictionary_path + ')' : ''));
					return;
				}
				// 後來的會覆蓋前面的。
				if (library_namespace.is_debug(2)) {
					library_namespace.warn(
					//
					'Convert_Pairs.add: Duplicated key [' + key
					//
					+ '], value will be changed: [' + pair_Map.get(key)
					//
					+ '] → [' + String(value) + ']');
				}
			}
			pair_Map.set(key, value);
		});

		delete this.special_keys_Map;
		return this;
	}

	function Convert_Pairs__remove(key_hash, options) {
		if (!key_hash)
			return this;

		if (typeof key_hash === 'string')
			key_hash = [ key_hash ];

		if (Array.isArray(key_hash)) {
			var tmp = key_hash;
			key_hash = Object.create(null);
			for (var i = 0; i < tmp.length; i++)
				key_hash[tmp[i]] = null;
		}

		var remove_matched_path = options && options.remove_matched_path;
		var pair_Map = this.pair_Map, path = this.path, changed;
		// console.trace([ path, key_hash ]);
		for ( var search_key in key_hash) {
			// key_hash[key]: ignore path
			if (key_hash[search_key] === path) {
				if (remove_matched_path)
					delete key_hash[search_key];
				continue;
			}

			var pattern = search_key.match(library_namespace.PATTERN_RegExp);
			if (pattern) {
				pattern = new RegExp(pattern[1], pattern[2] || options.flags);
				library_namespace.debug('Remove pattern: ' + pattern + ' of '
						+ path, 2, 'Convert_Pairs__remove');
				var keys_to_remove = [];
				pair_Map.forEach(function(value, key) {
					if (pattern.test(key) || pattern.test(value))
						keys_to_remove.push(key);
				});
				if (keys_to_remove.length > 0) {
					library_namespace.debug(path + '\tRemove '
							+ keys_to_remove.length + ' keys.', 2,
							'Convert_Pairs__remove');
					// console.trace(keys_to_remove);
					keys_to_remove.forEach(function(key) {
						pair_Map['delete'](key);
					});
					changed = true;
				}

			} else {
				changed = pair_Map['delete'](search_key);
			}
		}

		if (changed)
			delete this.special_keys_Map;
		return this;
	}

	// add the content of file path
	function Convert_Pairs__add_path(options) {
		// console.trace(options);
		// 前置處理。
		options = library_namespace.setup_options(options);

		var path = options.path;
		if (Array.isArray(path)) {
			if (path.length < 2) {
				path = path[0];
			} else {
				path.forEach(function(file_path) {
					var _options = library_namespace.new_options(options);
					if (library_namespace.is_Object(file_path)) {
						// e.g., for .remove_comments
						Object.assign(_options, file_path);
						file_path = file_path.file_path || file_path.path;
					}

					_options.path = file_path;
					this.add_path(_options);
				}, this);
				return this;
			}
		}

		// assert: typeof path === 'string'
		// `path` is file path
		if (!path || typeof options.file_filter === 'function'
				&& !options.file_filter(path)) {
			return this;
		}

		var source;
		try {
			// 注意:此方法不可跨 domain!
			source = library_namespace.get_file(path);
		} catch (e) {
			// TODO: handle exception
		}

		if (source) {
			this.path = path;
			// 載入 resources。
			this.add(source, options);
		} else {
			library_namespace
					.warn('Convert_Pairs.add_path: Cannot get contents of ['
							+ path + ']!');
		}
		return this;
	}

	function Convert_Pairs__save(path, encoding, save_new) {
		if (!library_namespace.write_file)
			throw new Error('Please include CeL.application.storage first!');

		if (path !== this.path) {
			path = this.path;
		} else if (!save_new && this.remove_comments) {
			library_namespace.warn('移除注解後再存檔，會失去原先的注解！請考慮設定 save_new flag。');
		}

		if (!encoding) {
			encoding = library_namespace.guess_encoding
					&& library_namespace.guess_encoding(path)
					|| library_namespace.open_format.TristateTrue;
		}
		library_namespace.debug([ '(' + encoding, ') [', path, ']' ], 2,
				'Convert_Pairs.save');

		var pair_Map = this.pair_Map;
		if (pair_Map.size > 0) {
			var line, data = [], separator = this.separator || '\t';
			pair_Map.forEach(function(pair) {
				var value = pair[1];
				if (Array.isArray(value))
					value = value.join(separator);
				data.push(pair[0] + separator + value);
			})

			library_namespace.debug([ save_new ? 'Appending ' : 'Writing ',
					data.length, ' data to (' + encoding, ') [', path, ']' ],
					2, 'Convert_Pairs.save');
			library_namespace.debug(data.join('<br />'), 3,
					'Convert_Pairs.save');
			library_namespace.write_file(path,
			//
			data.join(this.field_separator
					|| library_namespace.env.line_separator), encoding,
			//
			save_new ? library_namespace.IO_mode.ForAppending : undefined);
		}

		library_namespace.log([ data.length, ' new records saved. [', {
			// 重新紀錄.
			a : 'save again',
			href : '#',
			onclick : function() {
				this.save(path, encoding, save_new);
				return false;
			}.bind(this)
		}, ']' ]);

		return this;
	}

	function Convert_Pairs__save_new(path, encoding) {
		return this.save(path, encoding, true);
	}

	// re-generate pattern, this.get_sorted_keys()
	function Convert_Pairs__pattern(options) {
		// 前置處理。
		options = library_namespace.setup_options(options);

		var normal_keys = [], flags = options.flags || this.flags,
		// 若 key 為 RegExp 之 source 時，.length 不代表可能 match 之長度。
		// e.g., '([\d〇一二三四五六七八九])米'
		// 因此特殊 keys 必須放在 special_keys_Map。
		special_keys_Map = this.special_keys_Map = new Map,
		//
		pair_Map = this.pair_Map;
		pair_Map.forEach(function(value, key) {
			// 必須排除掉 "(﹁﹁)" → "(﹁﹁)"
			if (!library_namespace.PATTERN_RegExp.test(key)) {
				normal_keys.push(key);
				return;
			}

			try {
				// console.trace([ key.to_RegExp(flags), value ]);
				special_keys_Map.set(key, [ key.to_RegExp(flags), value ]);
			} catch (e) {
				library_namespace.error('Convert_Pairs__pattern: '
				// Error key?
				+ '[' + key + '] → ['
				// Cannot convert a Symbol value to a string
				+ String(value) + ']: ' + e.message);

				// normal_keys.push(key);
			}
		});

		normal_keys.sort(this.comparator);

		// reset
		delete this.pair_Map_by_length;
		delete this.convert_pattern;

		if (normal_keys.length === 0) {
			;

		} else if (options.generate_pair_Map_by_length) {
			var pair_Map_by_length = this.pair_Map_by_length = [];
			normal_keys.forEach(function(key) {
				var length = key.length;
				var map = pair_Map_by_length[length];
				if (!map)
					map = pair_Map_by_length[length] = new Map;
				map.set(key, pair_Map.get(key));
			});

		} else {
			try {
				this.convert_pattern = new RegExp(
						normal_keys.join('|') || '^$', flags);
			} catch (e) {
				// @IE，當 keys 太多太長時，
				// 若是使用 new RegExp(keys.join('|'), 'g') 的方法，
				// 可能出現 "記憶體不足" 之問題。
			}
		}
		// console.log(this.convert_pattern);
		// 2022/2/13: 17
		// console.trace('最長的轉換 key.length=' + pair_Map_by_length.length);

		if (options.get_normal_keys)
			return normal_keys;

		return this.convert_pattern;
	}

	function Convert_Pairs__for_each(operator, options) {
		this.pair_Map.forEach(function(value, key) {
			operator(key, value);
		});
		return this;
	}

	// select the first fitted
	function Convert_Pairs__select(selector, options) {
		if (typeof selector !== 'function') {
			var target = selector || options && options.target;
			if (!target)
				return;

			library_namespace.debug('target: ' + target + ', options: '
					+ options, 3);
			if (options === true) {
				return this.get_value(target);
			}

			if (library_namespace.is_RegExp(target)) {
				selector = function(key, value) {
					return target.test(key) && value;
				};
			} else {
				var replace_flags = this.flags;
				selector = function(key, value) {
					var pattern;
					try {
						pattern = typeof replace_flags === 'function'
						//
						? replace_flags(key)
						//
						: new RegExp(key, replace_flags);
					} catch (e) {
						// Error key?
						library_namespace.error('Convert_Pairs.select: key '
								+ (pattern || '[' + key + ']') + ': '
								+ e.message);
					}
					return pattern.test(target) && value;
				};
			}
		}

		// TODO: use `for (const key of this.pair_Map.keys())`
		for (var pair_Map = this.pair_Map, keys = Array.from(pair_Map.keys()), index = 0; index < keys.length; index++) {
			var key = keys[index], value = selector(key, pair_Map.get(key));
			if (value)
				return value;
		}
	}

	// @inner
	function generate_demarcation_points(text_list) {
		var index = 0, demarcation_points = [];
		text_list.forEach(function(text_slice) {
			demarcation_points.push(index += text_slice.length);
		});
		return demarcation_points;
	}

	// @inner
	function convert_using_pair_Map_by_length(text, options) {
		var pair_Map_by_length = this.pair_Map_by_length, max_key_length = pair_Map_by_length.length,
		// node.js v17.4.0 採用字串的方法 converted_text_slice += '' 與採用陣列的方法 .push()
		// 速度差不多。
		converted_text_list, converted_text_slice = '',
		// show_hitted
		show_matched = options && options.show_matched,
		// 分界點。
		demarcation_points;

		if (Array.isArray(text)) {
			demarcation_points = generate_demarcation_points(text);
			// console.log([ text, demarcation_points ]);
			converted_text_list = [];
			text = text.join('');
		} else {
			text = String(text);
		}

		// @see
		// https://github.com/tongwentang/tongwen-core/blob/master/src/converter/map/convert-phrase.ts
		for (var index = 0, length = text.length; index < length;) {
			// 本次要測試的文字。
			var text_to_convert = text.slice(index, Math.min(length, index
					+ max_key_length));

			var text_to_convert_length = text_to_convert.length;
			while (true) {
				var map = pair_Map_by_length[text_to_convert_length];
				if (map && map.has(text_to_convert)) {
					// Found.
					if (show_matched) {
						library_namespace.info(text_to_convert + '→'
								+ map.get(text_to_convert));
					}
					converted_text_slice += map.get(text_to_convert);
					break;
				}

				if (text_to_convert_length === 1) {
					// Nothing matched.
					converted_text_slice += text_to_convert;
					break;
				}

				// 長先短後 詞先字後
				text_to_convert = text_to_convert.slice(0,
				// 截短1字元再做下一輪測試。
				--text_to_convert_length);
			}

			// console.log(index + '→' + (index + text_to_convert_length));
			index += text_to_convert_length;
			if (!demarcation_points) {
				continue;
			}

			// 依照分界點分割 converted_text_slice。
			while (true) {
				// 先計算還不夠的長度。
				var _length = demarcation_points[converted_text_list.length]
						- index;
				if (!(_length <= 0)) {
					break;
				}
				// 已經累積足夠的 converted_text_slice。
				_length += converted_text_slice.length;
				converted_text_list
						.push(converted_text_slice.slice(0, _length));
				converted_text_slice = converted_text_slice.slice(_length);
			}
		}

		// console.trace(converted_text_list || converted_text_slice);
		if (converted_text_list) {
			// assert: converted_text_slice === ''
			return converted_text_list;
		}

		return converted_text_slice;
	}

	// @inner
	function split_text_by_demarcation_points(text_String, converted_text,
			demarcation_points) {
		// 分割 converted_text: 因為 converted_text 可能經過多重 this.special_keys_Map 轉換，
		// 在 this.special_keys_Map.forEach() 裡面處理 indexes 將會非常複雜。是故採用 CeL.LCS()。
		var diff_list = library_namespace.LCS(text_String, converted_text, {
			line : false,
			diff : true
		});
		var index_of_demarcation_points = 0, increased_index = 0;
		// console.trace(diff_list);
		// console.trace(demarcation_points);

		diff_list.forEach(function(diff_pair) {
			var from_index = diff_pair.index[0];
			var to_index = diff_pair.index[1];
			// console.trace(from_index, to_index, diff_pair.last_index);
			var increased_in_this_diff = to_index && from_index
			//
			? to_index[1] - from_index[1] - increased_index
			//
			: to_index ? to_index[1] - diff_pair.last_index[1]
					: -(from_index[1] - diff_pair.last_index[0]);
			// console.trace(increased_in_this_diff, from_index, to_index,
			// diff_pair.last_index);
			// 開始的索引差距應該跟上一個結尾的索引差距相同。
			// assert: increased_index === to_index[0] - from_index[0]
			var from_start = from_index ? from_index[0]
					: diff_pair.last_index[0] + 1;
			var from_end = from_index ? from_index[1] + 1
					: diff_pair.last_index[0] + 1;
			while (demarcation_points[index_of_demarcation_points]
			//
			< from_end) {
				if (increased_in_this_diff
				//
				&& demarcation_points[index_of_demarcation_points]
				//
				> from_start) {
					// e.g., a,a → bbb；不能決定到底是 b,bb 還是 bb,b。
					var old_index
					//
					= demarcation_points[index_of_demarcation_points];
					var _diff = old_index - from_start;
					// converted_text 切割的 index。
					var to_i = to_index[0] + _diff;
					library_namespace.info('Convert_Pairs__convert: 將 ['
					//
					+ text_String.slice(from_start, old_index) + ','
					//
					+ text_String.slice(old_index, from_end) + '] 分割成 ['
					//
					+ converted_text.slice(to_index[0], to_i) + ','
					//
					+ converted_text.slice(to_i, to_index[1] + 1) + ']');
				}
				demarcation_points[index_of_demarcation_points++]
				// 這樣會將本次增加的 (increased_in_this_diff) 全部排到最後一個。
				+= increased_index;
			}

			// 結尾的索引差距。
			increased_index += increased_in_this_diff;
		});

		// console.trace(increased_index, index_of_demarcation_points,
		// demarcation_points);
		if (increased_index) {
			while (index_of_demarcation_points < demarcation_points.length)
				demarcation_points[index_of_demarcation_points++] += increased_index;
		}
		// console.trace(demarcation_points);

		text_String = demarcation_points.map(function(i, index) {
			return converted_text.slice(
					index > 0 ? demarcation_points[index - 1] : 0, i);
		});

		return text_String;
	}

	// @inner
	function adapt_special_keys_Map(text_Array, options) {
		// show_hitted
		var show_matched = options && options.show_matched;

		var demarcation_points = generate_demarcation_points(text_Array);
		var text_String = text_Array.join('');
		var converted_text = text_String;
		this.special_keys_Map.forEach(function(value, key) {
			// var pattern = value[0], replace_to = value[1];
			if (show_matched && value[0].test(converted_text)) {
				library_namespace.info(value[0] + ': '
				//
				+ converted_text + '→'
						+ converted_text.replace(value[0], value[1]));
			}
			// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_string_as_a_parameter
			converted_text = converted_text.replace(value[0], value[1]);
			// 在這裡呼叫 split_text_by_demarcation_points() 可增加精準度，但大大降低效能。
		});

		// assert: demarcation_points.at(-1) === text_String.length
		if (text_String === converted_text) {
			return text_Array;
		}

		return split_text_by_demarcation_points(text_String, converted_text,
				demarcation_points);
	}

	// @see function LCS_length()
	// https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Errors/Invalid_array_length
	// 設定小一點，LCS() 的時候才不會浪費太多時間。
	var SOFT_SLICE_LIMIT = 1000;
	// HARD_SLICE_LIMIT < 65536 但必須預防有些轉換把長度拉長了。
	var HARD_SLICE_LIMIT = 65530;
	// console.assert(SOFT_SLICE_LIMIT < HARD_SLICE_LIMIT);
	// console.assert(HARD_SLICE_LIMIT ** 2 < 2 ** 32 - 1);

	var using_pair_Map_by_length = true;
	function Convert_Pairs__convert(text, options) {
		if (false && this.pair_Map) {
			library_namespace.debug(
			//
			'Convert ' + String(text).length + ' characters, using '
					+ this.pair_Map.size + ' pairs with replace_flags ['
					+ this.replace_flags + '].', 3,
			// Convert_Pairs.convert
			'Convert_Pairs__convert');
		}

		if (!this.special_keys_Map) {
			this.pattern({
				flags : this.replace_flags,
				generate_pair_Map_by_length : using_pair_Map_by_length
			});
			if (this.may_remove_pair_Map) {
				library_namespace.debug('在開始轉換之後就不會再修改辭典檔，因此可移除 .pair_Map。', 1,
						'Convert_Pairs__convert');
				delete this.pair_Map;
			}
		}
		// console.trace(this.convert_pattern);
		// console.trace(this.special_keys_Map);

		// show_hitted
		var show_matched = options && options.show_matched;

		// 長先短後 詞先字後
		if (this.pair_Map_by_length) {
			// console.trace(text);
			text = convert_using_pair_Map_by_length.call(this, text, options);

		} else if (this.convert_pattern) {
			text = String(text).replace(this.convert_pattern, function(token) {
				// library_namespace.info(token + '→' +
				// pair_Map.get(token));
				return pair_Map.get(token);
			});
		}

		// ----------------------------------------------------------

		// console.trace([ text, this.special_keys_Map ]);

		if (!Array.isArray(text)
		// Nothing to do.
		|| this.special_keys_Map.size === 0) {
			// assert: typeof text === 'string'
			this.special_keys_Map.forEach(function(value, key) {
				// var pattern = value[0], replace_to = value[1];
				if (show_matched && value[0].test(text)) {
					library_namespace.info(value[0] + ': ' + text + '→'
							+ text.replace(value[0], value[1]));
				}
				text = text.replace(value[0], value[1]);
			});
			return text;
		}

		// assert: Array.isArray(text)
		// console.trace([ text, this.special_keys_Map ]);

		var converted_text = [];
		// 避免 RangeError: Invalid typed array length:
		// @see function LCS_length()
		for (var index = 0; index < text.length;) {
			var latest_index = index, this_slice_length = 0;
			// 限制長度在這個之內。
			while (index < text.length && this_slice_length < SOFT_SLICE_LIMIT)
				this_slice_length += text[index++].length;
			while (index < text.length) {
				this_slice_length += text[index].length;
				if (this_slice_length >= HARD_SLICE_LIMIT) {
					// 真的太長還是得強制截斷。警告: 這可能造成沒有辦法匹配的錯誤情況。
					break;
				}
				// 延伸到句子結尾。
				if (/(?:[。？！…」]|\/>)[\s\n]*$/.test(text[index]))
					break;
				index++;
			}
			if (false) {
				console.log(text.slice(index - 20, index));
				console.trace(latest_index + '-' + index + '/' + text.length
						+ ': ' + this_slice_length);
			}
			converted_text.append(adapt_special_keys_Map.call(this, text.slice(
					latest_index, index), options));
		}
		// console.assert(converted_text.length === text.length);
		return converted_text;
	}

	// reverse conversion, 改成 value → key
	function Convert_Pairs__reverse(options) {
		// 前置處理。
		options = library_namespace.new_options(options);
		options.ignore_null_value = true;

		this.pair_Map = this.pair_Map.reverse_key_value(options);

		delete this.special_keys_Map;
		return this;
	}

	function Convert_Pairs__clone(options) {
		return new Convert_Pairs(this, options);
	}

	function Convert_Pairs__to_Object(source) {
		var object = Object.create(null);
		this.pair_Map.forEach(function(value, key) {
			object[key] = value;
		});
		return object;
	}

	function Convert_Pairs__comparator(key_1, key_2) {
		// 排序：長的 key 排前面。 long → short
		var diff = key_2.length - key_1.length;
		return diff !== 0 ? diff
		// assert: key_1 !== key_2
		: key_1 < key_2 ? -1 : 1;
	}

	// ---------------------------------------------------------------------//
	// export 導出.

	library_namespace.set_method(Convert_Pairs.prototype, {
		get_value : Convert_Pairs__get_value,
		add : Convert_Pairs__add,
		remove : Convert_Pairs__remove,
		add_path : Convert_Pairs__add_path,
		save : Convert_Pairs__save,
		save_new : Convert_Pairs__save_new,
		pattern : Convert_Pairs__pattern,
		// for each pair
		for_each : Convert_Pairs__for_each,
		select : Convert_Pairs__select,
		// convert from key to value.
		convert : Convert_Pairs__convert,
		reverse : Convert_Pairs__reverse,
		clone : Convert_Pairs__clone,
		to_Object : Convert_Pairs__to_Object,

		comparator : Convert_Pairs__comparator,

		/**
		 * {String} key-value 分隔符號.
		 */
		// separator : '\t',
		/**
		 * {String} 欄位分隔符號.
		 */
		// field_separator : /[\r\n]+/,
		/** default RegExp replace flags: global match, or 'ig' */
		flags : 'g'
	});

	return Convert_Pairs;
}
