/**
 * @name CeL file system functions
 * @fileoverview 本檔案包含了 file system functions。
 * @since 2013/1/5 9:38:34
 * @see <a href="http://en.wikipedia.org/wiki/Filesystem" accessdate="2013/1/5
 *      9:44">File system</a>
 */

'use strict';

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

// 不採用 if 陳述式，可以避免 Eclipse JSDoc 與 format 多縮排一層。
typeof CeL === 'function' && CeL.run({
	// module name
	name : 'data.file',

	// includes() @ data.code.compatibility.
	require : 'data.code.compatibility.|application.OS.Windows.new_COM'
	//
	+ '|data.code.thread.Serial_execute',

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

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

function module_code(library_namespace) {

	// requiring.
	var new_COM = this.r('new_COM'), Serial_execute = this.r('Serial_execute');

	// ---------------------------------------------------------------------//
	// 基本宣告與定義。

	var
	// cache.
	path_separator = library_namespace.env.path_separator,
	/**
	 * FileSystemObject
	 * 
	 * @inner
	 * @ignore
	 * @see <a
	 *      href="http://msdn.microsoft.com/en-us/library/z9ty6h50(v=VS.84).aspx"
	 *      accessdate="2010/1/9 8:10">FileSystemObject Object</a>, Scripting
	 *      Run-Time Reference/FileSystemObject
	 *      http://msdn.microsoft.com/en-US/library/hww8txat%28v=VS.84%29.aspx
	 */
	FSO = new_COM("Scripting.FileSystemObject");

	// 可 test FSO.

	var
	// const flag enumeration: 用於指示 type.
	// assert: (!!type) MUST true!
	FILE = 1, FOLDER = 2,

	// const flag enumeration: file/folder callback index.
	FILE_HANDLER_INDEX = 0, FOLDER_HANDLER_INDEX = 1,

	// const: sub files.
	// 必須是不會被用於目錄名之值。
	FILES = '',
	// TODO: file/directory status/infomation, even contents.
	// 必須是不會被用於目錄名之值。
	DATA = '.',

	// 預設最多處理之 folder 層數。
	// directory depth limit.
	default_depth = 64,

	// const flag enumeration: default property data.
	fso_property = {
		file : {
			Attributes : '',
			DateCreated : '',
			DateLastAccessed : '',
			DateLastModified : '',
			Drive : '',
			Name : '',
			ParentFolder : '',
			Path : '',
			ShortName : '',
			ShortPath : '',
			Size : '',
			Type : ''
		},
		folder : {
			Attributes : '',
			DateCreated : '',
			DateLastAccessed : '',
			DateLastModified : '',
			Drive : '',
			Name : '',
			ParentFolder : '',
			Path : '',
			ShortName : '',
			ShortPath : '',
			Size : '',
			Type : '',
			Files : '',
			IsRootFolder : '',
			SubFolders : ''
		},
		driver : {
			AvailableSpace : '',
			DriveLetter : '',
			DriveType : '',
			FileSystem : '',
			FreeSpace : '',
			IsReady : '',
			Path : '',
			RootFolder : '',
			SerialNumber : '',
			ShareName : '',
			TotalSize : '',
			VolumeName : ''
		}
	};

	// a network drive.
	// http://msdn.microsoft.com/en-us/library/ys4ctaz0(v=vs.84).aspx
	// NETWORK_DRIVE = 3,

	/**
	 * 取得指定 path 之檔名/資料夾名稱。
	 * 
	 * @param {String}path
	 *            指定之目標路徑。
	 * @returns {String} 檔名/資料夾名稱。
	 * @inner
	 */
	function name_of_path(path) {
		var match = typeof path === 'string' && path.match(/[^\\\/]+/);
		return match && match[0] || path || '';
	}

	/**
	 * 傳回新的資料夾結構。
	 * 
	 * @returns 新的資料夾結構。
	 * @inner
	 */
	function new_folder() {
		var folder = Object.create(null);
		// 檔案, sub-files.
		folder[FILES] = Object.create(null);
		// directory status/infomation.
		folder[DATA] = Object.create(null);
		return folder;
	}

	// ---------------------------------------------------------------------//
	// filter 處理。

	/**
	 * options 所使用到的 filter name。
	 */
	var regular_filter_name = {
		// path filter.
		// 通常我們輸入的只會指定 path filter。
		filter : 0,
		// file name filter. 篩選檔案.
		// WARNING: 若有設定，只要檔名不符合，即使 folder name 符合亦一樣會被剔除！
		file_filter : 1,
		// folder name filter. 篩選資料夾.
		folder_filter : 2
	};

	/**
	 * 判別是否為可接受之字串篩選器。<br />
	 * filter should has NO global flag.
	 * 
	 * @param {undefined|String|RegExp|Function}filter
	 *            字串篩選器。
	 * 
	 * @returns {Boolean} 為可接受之字串篩選器。
	 */
	function is_regular_filter(filter) {
		return !filter || typeof filter === 'string'
		// RegExp
		|| library_namespace.is_RegExp(filter)
		// function
		|| typeof filter === 'function';
	}

	/**
	 * 檢測 options 的 filter。
	 * 
	 * @param {Object}options
	 *            optional flag. e.g., filter.
	 */
	function check_filter_of_options(options) {
		for ( var filter_name in regular_filter_name)
			if ((filter_name in options)
					&& !is_regular_filter(options[filter_name]))
				delete options[filter_name];
	}

	/** {Number}未發現之index。 const: 基本上與程式碼設計合一，僅表示名義，不可更改。(=== -1) */
	var NOT_FOUND = ''.indexOf('_');

	/**
	 * 判斷指定字串是否合乎篩選。
	 * 
	 * @param {undefined|String|RegExp|Function}filter
	 *            字串篩選器。
	 * @param {String}string
	 *            欲測試之指定字串。string to test.
	 * @param [argument]
	 *            當篩選器為 function 時之附加引數。
	 * 
	 * @returns {Boolean} 為可接受之字串篩選器。
	 */
	function match_filter(filter, string, argument) {
		return !filter
		//
		|| (typeof filter === 'string'
		// String
		? string.includes(filter)
		// RegExp
		: filter.test ? filter.test(string)
		// function
		: filter(string, argument));
	}

	// ---------------------------------------------------------------------//
	// 檔案系統模擬結構。

	/**
	 * 建立模擬檔案系統結構 (file system structure) 之 Class。取得檔案列表。<br />
	 * 注意: 在此設定的 callback，不具有防呆滯功能。<br />
	 * TODO: fs.readdir or fs.readdirSync @ node.js<br />
	 * TODO: follow link
	 * 
	 * @example <code>
	//	列出指定目錄下所有壓縮檔。
	CeL.run('data.file', function() {
		var folder = new CeL.file_system_structure('D:\\a', { file_filter : /\.(zip|rar|7z|exe)$/i });
		folder.each(function(fso, info) {
			CeL[info.is_file ? 'log' : 'info']([ info.index, '/', info.length, '[', fso.Path, ']' ]); }, {
				// filter : 'f',
				max_count : 5,
				'final' : function() { CeL.log([ this.count.filtered_file, ' done']); }
			}
		);
	});
	</code>
	 * 
	 * @param {String|Array}path
	 *            指定之目標路徑。<br />
	 *            使用相對路徑，如 '..' 開頭時，須用 get_file_path() 調整過。
	 * @param {Object}[options]
	 *            optional flag. e.g., filter.
	 * 
	 * @constructor
	 * 
	 * @see <a
	 *      href="http://msdn.microsoft.com/library/en-US/script56/html/0fa93e5b-b657-408d-9dd3-a43846037a0e.asp">FileSystemObject</a>
	 * 
	 * @since 2013/1/6 18:57:16 堪用。
	 */
	function file_system_structure(path, options) {
		if (this === file_system_structure)
			throw 'Please use "new file_system_structure()"'
					+ ' instead of "file_system_structure()"!';

		// private properties.
		this.structure = new_folder();
		this.count = Object.create(null);
		this.path_list = [];

		this.add(path, options);
	}

	/**
	 * 解析/取得模擬結構中，指定 path 所在位置。
	 * 
	 * @param {String}path
	 *            指定之目標路徑。
	 * @param {Number}create_type
	 *            以 FILE or FOLDER 創建此標的。
	 * 
	 * @returns {Object} 檔案系統模擬結構中，指定 path 所在位置。
	 * @returns undefined error occurred.
	 */
	function resolve_path(path, create_type) {
		var base = this.structure;

		if (path && typeof path === 'string') {
			var name, i = 0, list = library_namespace.simplify_path(path)
					.replace(/[\\\/]+$/, '');
			if (name = list.match(/^\\\\[^\\]+/)) {
				// a network drive.
				name = name[0];
				list = list.slice(name.length).split(/[\\]+/);
				list[0] = name;
			} else
				list = list.split(/[\\\/]+/);

			for (; i < list.length; i++) {
				name = list[i];
				if (name in base)
					base = base[name];
				else if (!create_type)
					return undefined;

				else if (create_type === FOLDER
				// 不是最後一個。
				|| i + 1 < list.length)
					base = base[name] = new_folder();
				else
					base[FILES][name] = null;
			}
		}

		return base;
	}

	/**
	 * 將 fso 所有 data_fields 中的項目 extend 到 data 中。
	 * 
	 * @inner
	 * @private
	 * 
	 * @param {file_system_object}fso
	 *            file system object.
	 * @param {Object}data_fields
	 *            欲 extend 之項目。
	 * @param {檔案系統模擬結構}data
	 *            所在位置。
	 * 
	 * @returns {Number} extend 之項目數。
	 */
	function fill_data(fso, data_fields, data) {
		var name,
		// {Number} extend 之項目數。
		// count = 0,
		item;
		for (name in data_fields)
			try {
				// Get the infomation/status of fso.
				if ((item = fso[name]) !== undefined) {
					data[name]
					//
					= typeof data_fields[name] === 'function'
					//
					? data_fields[name](item) : item;
					// count++;
				}
			} catch (e) {
				// 在取得 RootFolder 的 .DateCreated,
				// .DateLastAccessed, .DateLastModified 時，會
				// throw。
				if (library_namespace.is_debug(3)) {
					library_namespace.warn('fill_data: 擷取資訊 [' + name
							+ '] 時發生錯誤！');
					library_namespace.error(e);
				}
			}
		// return count;
		return data;
	}

	/**
	 * 將指定 path 加入模擬結構。<br />
	 * 注意: base path 本身不受 filter 限制！
	 * 
	 * @param {String|Array}path
	 *            指定之目標路徑。<br />
	 *            使用相對路徑，如 '..' 開頭時，須用 get_file_path() 調整過。
	 * @param {Object}[options]
	 *            optional flag. e.g., filter.
	 */
	function add_path(path, options) {

		library_namespace.debug('初始化+正規化。', 2, 'add_path');
		// 前置處理。
		if (!library_namespace.is_Object(options))
			options = Object.create(null);
		if (isNaN(options.depth) || options.depth < 0
		// || options.depth > default_depth
		)
			options.depth = default_depth;

		check_filter_of_options(options);

		var callback_Array;
		if ('callback' in options) {
			callback_Array = options.callback;
			if (typeof callback_Array === 'function')
				options.callback = callback_Array = [ callback_Array,
						callback_Array ];
			else if (!Array.isArray(callback_Array)
					|| callback_Array.length === 0) {
				delete options.callback;
				callback_Array = undefined;
			}
		}

		// for Array [path].
		if (Array.isArray(path)) {
			path.forEach(function(p) {
				this.add(p, options);
			}, this);
			return;
		}

		var base = this.structure;
		if (!path) {
			library_namespace.debug(
			// 省略 path 會當作所有 Drivers。
			'取得各個 driver code。', 2, 'add_path');
			for (var driver, drivers = new Enumerator(FSO.Drives);
			//
			!drivers.atEnd(); drivers.moveNext()) {
				driver = drivers.item();
				// http://msdn.microsoft.com/en-us/library/ts2t8ybh(v=vs.84).aspx
				if (driver.IsReady)
					base[driver.Path] = new_folder();
				else
					library_namespace.warn('add_path: Drive [' + driver.Path
							+ '] 尚未就緒！');
			}

			return;
		}

		var fso,
		// 類型
		type;
		// 轉換輸入之 path 成 FSO object。
		try {
			if (typeof path === 'string') {
				// 注意: 輸入 "C:" 會得到 C: 的工作目錄。
				if (FSO.FolderExists(path)) {
					fso = FSO.GetFolder(path);
					type = FOLDER;
				} else if (FSO.FileExists(path)) {
					fso = FSO.GetFile(path);
					type = FILE;
				}

			} else if (typeof path === 'object' && path.Path) {
				fso = isNaN(path.DriveType) ? path : FSO.GetFolder(path.Path);
				type = fso.SubFolders ? FOLDER : FILE;
			}
		} catch (e) {
			library_namespace.error(e);
		}

		if (typeof fso !== 'object' || !(path = fso.Path)) {
			library_namespace.warn('add_path: 無法判別 path [' + path
					+ ']！指定的文件不存在？');
			return;
		}

		var list = this.path_list;
		for (var i = 0; i < list.length; i++)
			if (list[i].startsWith(path)) {
				library_namespace.debug('已處理過 path [' + path + ']。', 2,
						'add_path');
				return;
			}

		library_namespace.debug(
		//
		'Adding [' + path + ']', 2, 'add_path');
		list.push(path);

		if (base = callback_Array && callback_Array[
		//
		type === FOLDER ? FOLDER_HANDLER_INDEX : FILE_HANDLER_INDEX])
			try {
				base(fso, {
					depth : 0,
					is_file : type !== FOLDER
				});
			} catch (e) {
				library_namespace.error(e);
			}

		base = this.get(path, type);

		var filter = options.filter,
		//
		file_filter = options.file_filter,
		//
		folder_filter = options.folder_filter,
		//
		folder_first = !!options.folder_first,

		//
		count = this.count,
		// 注意: assert(undefined|0===0)
		total_size = count.size | 0,
		//
		total_file_count = count.file | 0,
		//
		total_folder_count = count.folder | 0,
		//
		filtered_total_file_count = count.filtered_file | 0,
		//
		filtered_total_folder_count = count.filtered_folder | 0,

		//
		file_data_fields = library_namespace.is_Object(options.data)
		//
		? options.data : options.data === true ? fso_property.file : false,
		//
		folder_data_fields = library_namespace.is_Object(options.folder_data) ? options.folder_data
				//
				: options.folder_data === true || options.data === true
				//
				? fso_property.folder : file_data_fields,

		// 巡覽/遍歷子目錄與所包含的檔案。
		traverse = function(fso, base, depth) {
			var item, collection, name, callback,
			//
			folder_data = base[DATA],
			// 有無加入 file count 功能，在 JScript 10.0.16438 差別不到 4%。
			// 為求方便，不如皆加入。
			size = 0,
			//
			file_count = 0, folder_count = 0,
			//
			filtered_file_count = 0, filtered_folder_count = 0,

			// list files of folder. 所包含的檔案.
			each_file = function() {
				library_namespace.debug('巡覽 [' + fso.Path + '] 之 sub-files。',
						3, 'add_path');
				try {
					for (collection = new Enumerator(fso.Files);
					//
					!collection.atEnd(); collection.moveNext()) {
						item = collection.item();
						file_count++;
						size += item.Size;

						if (match_filter(filter, item.Path, depth)
								&& match_filter(file_filter, name = item.Name,
										depth)) {
							++filtered_file_count;
							library_namespace.debug(
							//
							'Adding sub-file [' + filtered_file_count + '/'
									+ file_count + '] [' + item.Path + ']', 4,
									'add_path');
							base[FILES][name] = file_data_fields
							//
							? fill_data(item, file_data_fields, Object
									.create(null)) : null;
							// 預防 callback 動到 item，排在最後才處理。
							if (callback = callback_Array
							//
							&& callback_Array[FILE_HANDLER_INDEX])
								try {
									callback(item, {
										depth : depth,
										is_file : true
									});
								} catch (e) {
									library_namespace.error(e);
								}
						}
					}
				} catch (e) {
					// TODO: handle exception
				}
				total_size += size;
				total_file_count += file_count;
				filtered_total_file_count += filtered_file_count;
			},

			// 處理子目錄。
			each_folder = function() {
				library_namespace.debug('巡覽 [' + fso.Path + '] 之 sub-folders。',
						3, 'add_path');
				// 執行途中可能更動/刪除到此項目。try: 以防 item 已經被刪除。
				try {
					// 因為檔案可能因改名等改變順序，因此用 .moveNext()
					// 的方法可能有些重複，有些漏掉未處理。
					for (collection = new Enumerator(fso.SubFolders);
					//
					!collection.atEnd(); collection.moveNext()) {
						item = collection.item();
						folder_count++;
						if (match_filter(filter, item.Path, depth)
						//
						&& match_filter(folder_filter, name = item.Name, depth)) {
							filtered_folder_count++;
							library_namespace.debug(
							//
							'Adding sub-folder [' + filtered_folder_count + '/'
									+ folder_count + '] [' + item.Path + ']',
									4, 'add_path');
							name = base[name] = new_folder();

							if (callback = callback_Array
							//
							&& callback_Array[FOLDER_HANDLER_INDEX])
								try {
									callback(item, {
										depth : depth,
										is_file : false
									});
								} catch (e) {
									library_namespace.error(e);
								}

							if (depth < options.depth)
								traverse(item, name, depth);
						}
					}
				} catch (e) {
					// TODO: handle exception
				}
				// 可加上次一層: 會連這次一層之檔案都加上去。
				total_folder_count += folder_count;
				filtered_total_folder_count += filtered_folder_count;
			};

			if (folder_data_fields)
				fill_data(fso, folder_data_fields, folder_data);

			// 自身已經處理完，接下來 sub-files/sub-folders 之 depth 皆 +1。
			depth++;

			if (folder_first) {
				each_folder();
				each_file();
			} else {
				each_file();
				each_folder();
			}

			library_namespace.debug('巡覽 [' + fso.Path + ']: ' + file_count
					+ '/' + total_file_count + ' files, ' + size + '/'
					+ total_size + ' bytes, ' + folder_count + '/'
					+ total_folder_count + ' folders。', 2, 'add_path');

			folder_data.count = {
				size : size,

				file : file_count,
				folder : folder_count,

				filtered_file : filtered_file_count,
				filtered_folder : filtered_folder_count
			};
		};

		if (type === FOLDER) {
			if (options.depth > 0) {
				library_namespace.debug('開始巡覽 [' + path + ']。', 2, 'add_path');
				traverse(fso, base, 0);
			}

			count.size = total_size;
			count.file = total_file_count;
			// +1: base path 本身。
			count.folder = total_folder_count + 1;
			count.filtered_file = filtered_total_file_count;
			count.filtered_folder = filtered_total_folder_count + 1;

		} else {
			count.size = total_size + fso.Size;
			// +1: base path 本身。
			count.file = total_file_count + 1;
			count.folder = total_folder_count;
			// +1: base path 本身。
			count.filtered_file = filtered_total_file_count + 1;
			count.filtered_folder = filtered_total_folder_count;
		}
	}

	/**
	 * 重新整理/同步模擬結構。
	 * 
	 * @param {String|Array}path
	 *            指定之目標路徑。
	 * @param {Object}[options]
	 *            optional flag. e.g., filter.
	 */
	function refresh_structure(path, options) {
		// TODO: 勿 reset。
		this.structure = new_folder();

		this.path_list.forEach(function(p) {
			this.add(p, options);
		}, this);
	}

	/**
	 * 當呼叫 JSON.stringify() 時的前置處理。
	 */
	function structure_to_JSON(key) {
		// hierarchy:
		// {
		// FILES:[],
		// DATA:{},
		// 資料夾名稱(sub directory name):{}
		// };
		var structure = Object.create(null),
		//
		traverse = function(folder, base) {
			if (folder[FILES].length)
				base[FILES] = folder[FILES];

			for ( var name in folder)
				if (name !== FILES && name !== DATA)
					traverse(folder[name], base[name] = Object.create(null));
		};

		traverse(this.structure, structure);

		return structure;
	}

	// ---------------------------------------------------------------------//
	// 巡覽/遍歷檔案系統模擬結構之功能。

	/**
	 * 處理執行 callback 發生錯誤時的函數。
	 * 
	 * @inner
	 * @private
	 * 
	 * @since 2013/1/7 22:38:47。
	 */
	function callback_error_handler(controller, error_Object, options, stack) {
		var message = [ '執行 callback 時發生錯誤！您可' ],
		//
		skip_error = options.skip_error;
		if (skip_error)
			message.push({
				a : '停止執行',
				href : '#',
				onclick : function() {
					controller.stop();
				}
			}, '，或者', {
				// 忽略錯誤
				a : '不再提醒錯誤',
				href : '#',
				onclick : function() {
					delete options.skip_error;
				}
			});
		else
			message.push({
				a : '重試',
				href : '#',
				onclick : function() {
					stack.index--;
					controller.start();
				}
			}, '、', {
				a : '跳過並繼續執行',
				href : '#',
				onclick : function() {
					controller.start();
				}
			}, '，或者', {
				a : '忽略所有錯誤',
				href : '#',
				title : '★若忽略所有錯誤，之後發生錯誤將不會停止，而會直接忽略。',
				onclick : function() {
					options.skip_error = true;
					controller.start();
				}
			});
		message.push('。');
		CeL.warn(message);

		if (error_Object)
			library_namespace.error(error_Object);

		if (!skip_error) {
			controller.stop();
			return true;
		}
	}

	/**
	 * 巡覽/遍歷檔案系統模擬結構時，實際處理的函數。
	 * 
	 * @param {Array}LIFO_stack
	 *            處理堆疊。
	 * @param {file_system_structure}_this
	 *            file_system_structure instance.
	 * @param {Array}callback_Array
	 *            callback_Array[]
	 * @param {Object}options
	 *            optional flag. e.g., filter.
	 * 
	 * @inner
	 * @private
	 * 
	 * @since 2013/1/6 18:57:16 堪用。
	 */
	function travel_handler(LIFO_stack, _this,
	//
	callback_Array, options) {

		// 每個 folder 會跑一次 travel_handler。

		// 處理順序:
		// 處理/執行 folder 本身
		// → expand sub-files
		// → 處理/執行 sub-files (若檔案過多會跳出至下一循環處理)
		// → expand sub-folders
		// → 下一循環 from sub-folder[0]。

		var stack, base_space, base_path,
		//
		path, name, depth, path_exists;

		if (LIFO_stack.length > 0) {
			var callback, index;
			stack = LIFO_stack.at(-1);
			// 若有設定 stack.path，必以 path_separator 作結。
			base_path = stack.path || '';

			if (!stack.is_file)
				while (stack.index < stack.length) {
					name = stack[stack.index++];
					if (FSO.FolderExists(path = base_path
					//
					+ name
					// 若有設定 stack.path，必以 path_separator 作結。
					+ path_separator)) {
						base_path = path;
						path_exists = true;
						base_space = stack.base[name];
						library_namespace.debug('處理/執行 folder [' + path
								+ '] 本身。', 2, 'travel_handler');
						// 判別是否在標的區域內。
						// depth: 正處理之 folder 本身的深度。
						if (isNaN(depth = stack.depth))
							if (stack.length > 1) {
								stack.depth = depth = 0;
							} else
								for (var i = 0,
								//
								path_list = _this.path_list;
								//
								i < path_list.length; i++)
									if (path.startsWith(path_list[i])) {
										stack.depth = depth = 0;
										break;
									}

						if (!isNaN(depth)) {
							// 無論有無 match filter，皆應計數。
							index = options.single_count
							//
							? options.index++ : options.folder_index++;

							if ((callback
							//
							= callback_Array[FOLDER_HANDLER_INDEX])
									&& match_filter(options.filter, path, depth)
									&& match_filter(options.folder_filter,
											name, depth))
								try {
									/**
									 * 處理/執行資料夾本身。 用 folder.Size==0 可判別是否為 empty
									 * folder。
									 * 
									 * @see <a
									 *      href="http://msdn.microsoft.com/en-us/library/1c87day3(v=vs.84).aspx"
									 *      accessdate="2013/1/5 12:43">Folder
									 *      Object</a>
									 */
									callback.call(this, FSO.GetFolder(path), {
										is_file : false,
										depth : depth,
										data : base_space[DATA],
										index : index,
										length : options.folder_count
									});
								} catch (e) {
									if (callback_error_handler(this, e,
											options, stack))
										return;
								}
						}

						// expand sub-files.
						// TODO: check fso.SubFolders
						if (callback_Array[FILE_HANDLER_INDEX]
								&& depth < options.depth) {
							stack = [];
							for (name in
							//
							(stack.base = base_space[FILES]))
								stack.push(name);

							if (stack.length > 0) {
								if (options.sort)
									if (typeof options.sort
									//
									=== 'function')
										stack.sort(options.sort);
									else
										stack.sort();

								library_namespace.debug('開始處理 [' + base_path
										+ '] 之' + stack.length + ' sub-files ['
										+ stack + '].', 2, 'travel_handler');
								stack.index = 0;
								stack.path = path;
								stack.depth = depth + 1;
								stack.is_file = true;
								LIFO_stack.push(stack);
							}
						}
						// 預防有 sub-folder，還是先 break;
						break;

					} else {
						if (path.startsWith('\\\\')) {
							// a network drive.
							base_path = path;
							path_exists = true;
						} else
							library_namespace.warn([ 'travel_handler: ',
									'無法 access folder [', path, ']！',
									'或許是操作期間，檔案有所更動。您可能需要 refresh？' ]);
					}
				}

			// depth: 正處理之 files 的深度。
			depth = stack.depth;
			callback = callback_Array[FILE_HANDLER_INDEX];
			if (stack.is_file) {
				library_namespace.debug('處理/執行 folder [' + base_path
						+ '] 的 sub-files。', 2, 'travel_handler');
				// main loop of file.
				for (var max_count = options.max_count,
				//
				filter = options.filter,
				//
				base = stack.base, pass_data = {
					is_file : true,
					depth : depth,
					// data : stack.base[DATA],
					// index : options.index,
					length : options.count
				};;)
					if (stack.index < stack.length) {
						name = stack[stack.index++];
						if (match_filter(options.filter, path = base_path
								+ name, depth)
								&& match_filter(filter, name, depth)) {
							/**
							 * 處理/執行 sub-files。
							 * 
							 * @see <a
							 *      href="http://msdn.microsoft.com/en-us/library/1ft05taf(v=vs.84).aspx"
							 *      accessdate="2013/1/5 12:43">File Object</a>
							 */
							if (FSO.FileExists(path))
								try {
									// sub-file 存在，則 parent
									// folder 亦存在。
									path_exists = true;
									pass_data.index = options.index++;
									pass_data.data = base[name];
									callback.call(this, FSO.GetFile(path),
											pass_data);
									if (--max_count === 0
									//
									&& stack.index < stack.length)
										return;
								} catch (e) {
									if (callback_error_handler(this, e,
											options, stack))
										return;
								}
							else
								library_namespace.warn([
										'travel_handler: 檔案 [', path,
										'] 不存在或無法 access！', '或許是操作期間，檔案有所更動。',
										'您可能需要 refresh？' ]);
						}
						// 無論有無 match filter，皆應計數。
						pass_data.index++;

					} else {
						// 去掉 sub-files 之stack。
						LIFO_stack.pop();
						options.index = pass_data.index;
						break;
					}
			}

			library_namespace.debug('已處理過 folder [' + base_path
					+ '] 本身與 sub-files。expand sub-folders.', 2,
					'travel_handler');
			stack = LIFO_stack.at(-1);
			// depth: 正處理之 folder 本身的深度。
			depth = stack.depth;
			if (!base_space) {
				base_space = stack.base[stack[stack.index - 1]];
				library_namespace.debug(
						'可能因為 file list 長度超過 options.max_count，已經被截斷過，因此需要重設 base_space ['
								+ stack[stack.index - 1] + '] → ' + base_space
								+ '。', 2, 'travel_handler');
			}
		} else {
			base_path = '';
			base_space = _this.structure;
		}

		if (isNaN(depth) || depth < options.depth) {
			if (LIFO_stack.length === 0
			// 確認所在的 folder 存在。
			// 若不存在，也毋須 expand 了。
			|| path_exists
			// 亦可以下列檢測 base_path 的方式判別。
			// || base_path !== LIFO_stack[LIFO_stack.length -
			// 1].path
			) {
				library_namespace.debug('expand sub-folders.', 3,
						'travel_handler');
				for (name in ((stack = []).base = base_space))
					if (name !== FILES && name !== DATA)
						stack.push(name);

				if (stack.length > 0) {
					if (options.sort)
						if (typeof options.sort === 'function')
							stack.sort(options.sort);
						else
							stack.sort();

					// sub-folders / sub-directory.
					library_namespace.debug([ '開始處理 [', base_path, '] 之 ',
							stack.length, ' 個子資料夾 [<span style="color:#25a;">',
							stack.join('<b style="color:#47e;">|</b>'),
							'</span>].' ], 2, 'travel_handler');
					stack.index = 0;
					stack.path = base_path;
					if (!isNaN(depth))
						stack.depth = depth + 1;
					LIFO_stack.push(stack);
					return;
				}
			}

			if (!base_path) {
				// 應該只有 structure 為空時會用到。
				library_namespace.warn(
				//		
				'travel_handler: structure 為空？');
				return this.finish();
			}
		}

		// 檢查本 stack 是否已處理完畢。
		while ((stack = LIFO_stack.at(-1))
		//
		.index === stack.length) {
			library_namespace.debug('Move up. stack.length = '
					+ LIFO_stack.length, 2, 'travel_handler');
			LIFO_stack.pop();
			if (LIFO_stack.length === 0)
				return this.finish();
		}

	}

	/**
	 * travel structure.<br />
	 * 巡覽/遍歷檔案系統模擬結構的 public 公用函數。
	 * 
	 * TODO:<br />
	 * 不區分大小寫。ignore case<br />
	 * 與 dir/a/s 相較，network drive 的速度過慢。
	 * 
	 * @param {Function|Array}callback
	 *            file system handle function / Array [file handler, folder
	 *            handler].<br />
	 *            可以安排僅對folder或是file作用之function。<br />
	 *            handle function 應有的長度: 2<br />
	 *            callback(fso, info = {is_file, depth, data : fso data, index,
	 *            length}); index = 0 to (length - 1)
	 * @param {Object}[options]
	 *            optional flag. e.g., filter.<br />
	 *            options 之內容會被改動！
	 * 
	 * @returns {Serial_execute} controller
	 * @returns undefined error occurred.
	 * 
	 * @since 2013/1/6 18:57:16 堪用。
	 */
	function for_each_FSO(callback, options) {

		var path_length = this.path_list.length;
		if (path_length === 0) {
			if (library_namespace.is_debug())
				library_namespace.warn(
				//		
				'for_each_FSO: 尚未設定可供巡覽之 path。');
			return undefined;
		}

		library_namespace.debug('初始化+正規化。', 2, 'for_each_FSO');
		if (typeof callback === 'function')
			callback = [ callback, callback ];
		else if (!Array.isArray(callback) || callback.length === 0)
			return;

		// 前置處理。
		if (!library_namespace.is_Object(options))
			options = Object.create(null);

		// 計數 + 進度。
		options.index = 0;
		// 至此 options.count 代表 file count.
		options.count = this.count.filtered_file;
		options.folder_count = this.count.filtered_folder;
		if (options.single_count
		//
		= callback[FOLDER_HANDLER_INDEX]
		//
		=== callback[FILE_HANDLER_INDEX])
			options.folder_count
			//
			= (options.count += options.folder_count);
		else
			options.folder_index = 0;

		check_filter_of_options(options);

		if (typeof options.sort !== 'function'
				&& !(options.sort = !!options.sort))
			delete options.sort;

		// 限定 traverse 深度幾層。深入/不深入下層子目錄及檔案。
		if (isNaN(options.depth) || (options.depth |= 0) < 0)
			options.depth = default_depth;

		if (isNaN(options.max_count) || (options.max_count |= 0) < 1
				|| options.max_count > 1e5)
			// 預設一次 thread 最多處理之檔案個數。
			options.max_count = 800;

		options.argument = [ [], this, callback, options ];

		if (typeof options.final === 'function')
			options.final = options.final.bind(this);

		library_namespace.debug('開始巡覽 ' + path_length + ' paths。', 2,
				'for_each_FSO');
		return new Serial_execute(travel_handler, options);
	}

	// ---------------------------------------------------------------------//
	// public interface of file_system_structure.

	Object.assign(file_system_structure.prototype, {
		get : resolve_path,
		add : add_path,
		each : for_each_FSO,
		refresh : refresh_structure,
		list : function(options) {
			var list = [];
			// TODO: 可用萬用字元。
			this.each(function(fso, info) {
				if (info.is_file)
					list.push(fso.Path);
			}, options);
			return list;
		},
		toJSON : structure_to_JSON
	});

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

	// CeL.run('data.file');
	// var file_handler = new CeL.file([ 'path', 'path' ]);
	// file_handler.merge('to_path').add('path').forEach(function(){});
	function Files(path_list, options) {
		// private property:
		// this.data[path] = data of file = {
		// encoding : encoding cache. NOT absolutely correct.
		// other data.
		// }
		this.data = {};
		// private property:
		// this.order = ['path'];
		if (options) {
			if (typeof options.comparator === 'function')
				// e.g., function(a, b){return a-b;};
				this.comparator = options.comparator;
		}

		this.add(path_list, options);
	}

	function Files_add_object(path, data, order, filter, attributes) {
		var object, count = 0;
		// 注意: 輸入 "C:" 會得到 C: 的工作目錄。
		if (FSO.FolderExists(path)) {
			library_namespace.debug('Add folder [' + path + ']!', 2);
			var fso = FSO.GetFolder(path), item, collection;
			for (collection = new Enumerator(fso.Files);
			//
			!collection.atEnd(); collection.moveNext()) {
				item = collection.item();
				path = item.Path;
				if (!match_filter(filter, path))
					continue;
				data[path] = object = {
					size : item.Size
				};
				if (attributes)
					Object.assign(object, attributes);
				if (order)
					order.push(path);
				count++;
			}

		} else if (FSO.FileExists(path)) {
			library_namespace.debug('Add file [' + path + ']!', 2);
			if (match_filter(filter, path)) {
				data[path] = object = {};
				if (attributes)
					Object.assign(object, attributes);
				if (order)
					order.push(path);
				count++;
			}

		} else
			library_namespace.warn('Files_add_object: 無法辨識 path [' + path
					+ ']!');

		library_namespace.debug('Get ' + count + ' files.', 2);
	}

	function Files_add(path_list, options) {
		var data = this.data,
		//
		order = Array.isArray(this.order) && this.order,
		//
		filter = is_regular_filter(options.filter) && options.filter;

		if (typeof path_list === 'string')
			Files_add_object(path_list, data, order, filter);

		else if (Array.isArray(path_list))
			path_list.forEach(function(path) {
				Files_add_object(path, data, order, filter);
			});

		else if (library_namespace.is_Object(path_list))
			for ( var path in path_list)
				Files_add_object(path, data, order, filter, path_list[path]);

		else {
			library_namespace.warn('Files: Unknown path list [' + path_list
					+ ']!');
			order = false;
		}

		if (order && this.comparator)
			this.sort(
			// default: this.comparator
			);

		return this;
	}

	// create and return this 之 list。
	// 請勿直接使用 this.order，而應該使用 this.list()；
	// 因為 this.order 可能尚未準備好。
	// 不想改到 this 的，得自己 .slice()。
	function Files_list(rebuilt) {
		if (rebuilt || !Array.isArray(this.order)) {
			var order = [];
			// 重新創建 order。
			for ( var path in this.data)
				order.push(path);
			this.order = order;
		}

		return this.order;
	}

	function Files_sort(comparator, options) {
		// 重製 order。
		var order = this.list(),
		//
		comparator = comparator || this.comparator;

		if (options && options.no_set_order)
			order = order.slice();

		if (typeof use_function === 'function')
			order.sort(use_function);
		else if (library_namespace.is_RegExp(use_function))
			order.sort(function(key_1, key_2) {
				if ((key_1 = key_1.match(use_function))
				//
				&& (key_2 = key_2.match(use_function)))
					return key_1[1] - key_2[1];
			});
		else
			order.sort();

		library_namespace.debug(order.join('<br />'), 2);

		if (options) {
			if (!options.no_set_order) {
				this.order = order;
				if (comparator === 'function' && !options.no_set_sort)
					this.comparator = comparator;
			}

			if (options.get_list)
				return order;
		}

		return this;
	}

	var get_file_extension, read_file, write_file, guess_encoding;

	function setup_Files(function_body, function_name) {
		if (!function_name)
			function_name = library_namespace.get_function_name(function_body);
		setup_Files.conversion[function_name] = function_body;

		return function() {
			setup_Files.setup();
			return function_body.apply(this, arguments);
		};
	}
	setup_Files.conversion = {};
	setup_Files.setup = function() {
		library_namespace.debug('Trying setup Files.', 1, 'setup_Files.setup');
		library_namespace.is_included([ 'application.storage.file',
				'application.OS.Windows.file', 'application.locale.encoding' ],
				true);

		get_file_extension = library_namespace.
		//
		application.storage.file.get_file_extension;
		var file = library_namespace.application.OS.Windows.file;
		read_file = file.read_file;
		write_file = file.write_file;
		guess_encoding = library_namespace.
		//
		application.locale.encoding.guess_encoding;

		Object.assign(Files.prototype, setup_Files.conversion);
		library_namespace.debug('Setup Files done.', 1, 'setup_Files.setup');
	};

	function detect_encoding(this_Files, path, force) {
		var data = this_Files.data.path;
		if (!data)
			return;
		if (force || !data.encoding)
			// cache encoding
			data.encoding = guess_encoding(path);
		library_namespace.debug('(' + data.encoding + ') [' + path + ']');
		return data.encoding;
	}

	function decide_target(path, options) {
		var target = typeof options.target === 'function'
		// default: overwrite / save to original file.
		? options.target(path) : path;

		if (typeof options.target_file === 'function') {
			// get file name.
			target = target.match(/^(.*?)([^\\\/]+)$/);
			target = target[1] + options.target_file(target[2]);
		}

		if (target !== path) {
			library_namespace.debug('check extension 一致性。', 2);
			var has_error,
			//
			ext1 = get_file_extension(path),
			//
			ext2 = get_file_extension(target);
			if (ext1 !== ext2) {
				has_error = 1;
				library_namespace.error('副檔名不同! [' + ext1 + ']→[' + ext2 + ']');
			}

			library_namespace.debug('check target_file 檔案存在與否。', 2);
			if (FSO.FileExists(target)) {
				library_namespace.debug('check target_file 檔案大小是否差異太大。', 2);
				var ratio = FSO.GetFile(target).Size / FSO.GetFile(path).Size;
				if (ratio > 8) {
					has_error = 2;
					library_namespace.error('目標檔案 [' + target_file
							+ '] 已存在，且檔案大小差異太大！');
				} else {
					library_namespace.warn('目標檔案 [' + target_file + '] 已存在！'
							+ (has_error ? '' : '將覆寫之。'));
				}
			}

			if (has_error)
				return;
		}

		library_namespace.debug('['
				+ path
				+ ']'
				+ (path === target ? ': original = decided' : '<br />→ ['
						+ target + ']'), 2);
		return target;
	}

	/**
	 * @example <code>
		// Files_encode('target encoding')
		// if set save_to, then backup_to will be ignored.
		options = {
			from : 'encoding',
			save_to : function(path) {
				return 'save to';
			},
			backup_to : function(path) {
				return 'save to';
			}
		}
	 </code>
	 */
	function Files_encode(target_encoding, options) {
		if (!options)
			options = Object.create(null);

		var modify = typeof options.modify === 'function' && options.modify,
		//
		filter = is_regular_filter(options.filter) && options.filter,
		//
		handle_file = function(path) {
			if (!match_filter(filter, path))
				return;
			library_namespace.debug('處理 [' + path + ']', 2, 'Files_encode');
			var source_encoding = options.source_encoding
					|| detect_encoding(this, path) || this.encoding,
			//
			source_text, converted_text,
			//
			target_file = decide_target(path, options);
			if (!target_file)
				return;

			source_text = read_file(path, source_encoding);
			converted_text = modify ? modify(text) : text;

			if (typeof converted_text === 'string'
			//
			&& (source_text !== converted_text
			//
			|| source_enc && target_encoding
			//
			&& source_enc !== target_encoding)) {
				// TODO: !!workaround!!
				write_file(target_file, converted_text,
				// TODO: truncate file
				target_encoding);
			} else if (target_file !== path) {
				// copy file.
				library_namespace.debug(path + ' → ' + target_file);
				FSO.CopyFile(path, target_file);
			}

		};

		if (target_encoding || modify)
			this.list().forEach(handle_file, this);
		else
			library_namespace.warn(
			//
			'Files_encode: Target encoding does not specified.');

		return this;
	}

	function Files_replace(RegExp_from, to) {
		return this.encode(null, {
			modify : function(text) {
				return text.replace(RegExp_from, to);
			}
		});
	}

	function Files_merge(target, options) {
		if (!target)
			// TODO: auto detect.
			throw new Error('Files_merge: No target specified!');

		target = decide_target(target, options);
		if (!target)
			return;

		if (!options)
			options = Object.create(null);
		else if (typeof options === 'string')
			options = {
				encoding : options
			};

		var encoding = options.encoding,
		//
		modify = typeof options.modify === 'function'
		//
		&& options.modify,
		//
		filter = is_regular_filter(options.filter) && options.filter,
		//
		text_Array = [],
		//
		add_text = function(item, path) {
			item = options[item];
			if (!item)
				return;

			if (typeof item === 'string')
				text_Array.push(item);
			else if (typeof item === 'function')
				text_Array.push(item(path));
			else if (Array.isArray(item))
				Array.prototype.push.apply(
				//
				text_Array, item);
			else
				library_namespace.debug('Unknown text: [' + item + ']', 3);
		},
		//
		handle_file = function(path) {
			if (!match_filter(filter, path))
				return;
			library_namespace.debug('處理 [' + path + ']', 2, 'Files_merge');
			var encoding = options.source_encoding
			//
			|| detect_encoding(this, path),
			//
			text = read_file(path, encoding);
			library_namespace.debug('(' + encoding + ') [' + path + ']: '
					+ text.length + ' characters.', 3);

			if (modify) {
				text = modify(text, path);
				library_namespace.debug(
				//
				'→ ' + text.length + ' characters.', 3);
				if (text.length === 0)
					library_namespace.warn('[' + path + ']: 經 modify 後，無內容回傳！');
			}

			add_text('subhead', path);
			text_Array.push(text);
			// sub-footer
			add_text('subfooter', path);
		};

		if (!encoding)
			encoding = FSO.FileExists(target) && detect_encoding(this, target)
					|| this.encoding;
		if (library_namespace.is_debug(3))
			library_namespace.debug('Merge ' + this.list().length
			//
			+ ' files to (' + encoding + ') [' + target + '].');

		add_text('head', target);
		this.list().forEach(handle_file, this);
		add_text('footer', target);

		text_Array = text_Array.join('');
		if (typeof options.modify_all === 'function')
			text_Array = options.modify_all(text_Array);

		library_namespace.debug('Merge ' + this.order.length
				+ ' files, writing ' + text_Array.length + ' characters to ('
				+ encoding + ') [' + target + '].');
		write_file(target, text_Array, encoding);

		(this.data = {})[target] = {};

		delete this.order;

		return this;
	}

	/**
	 * batch operation
	 * 
	 * @param {Function}callback
	 *            callback(path, data)
	 * @param {Function}comparator
	 * @returns {@Files}
	 */
	function Files_forEach(callback, comparator) {
		var data = this.data,
		//
		order = comparator ? this.sort(comparator, {
			no_set_order : true,
			get_list : true
		}) : this.list();
		order.forEach(function(path) {
			library_namespace.debug('處理 [' + path + ']', 2, 'Files_forEach');
			callback(path, data[path]);
		});

		return this;
	}

	/**
	 * get HTML contents.<br />
	 * get contents between "body" tag.
	 * 
	 * @param {String}HTML
	 *            HTML text
	 * @returns {String}contents
	 * @since 2014/7/19 23:9:41
	 */
	function HTML_contents(HTML, use_RegExp) {
		if (use_RegExp)
			return HTML.replace(/[\s\n]*<\/body>(.+|\n)*$/i, '')
			// default 不用 RegExp，因為 RegExp 太慢。
			.replace(/^(.+|\n)*<body>[\s\n]*/i, '');

		var foot = HTML.lastIndexOf('</body>'),
		//
		head = HTML.indexOf('<body');

		// 去尾。
		if (false && foot === NOT_FOUND)
			foot = HTML.lastIndexOf('</BODY>');
		if (foot === NOT_FOUND) {
			foot = HTML.lastIndexOf('</div>');
			if (foot !== NOT_FOUND && !/^[\s\n]*$/.test(HTML.slice(foot)))
				foot = NOT_FOUND;
		}

		// 去頭。
		if (false && head === NOT_FOUND)
			head = HTML.indexOf('<BODY');
		if (head === NOT_FOUND) {
			head = HTML.indexOf('<div');
			if (head !== NOT_FOUND && !/^[\s\n]*$/.test(HTML.slice(0, head)))
				head = NOT_FOUND;
		}
		head = HTML.indexOf('>', head) + 1;

		// 切割。
		if (foot === NOT_FOUND) {
			if (head !== NOT_FOUND)
				HTML = HTML.slice(head);
		} else
			HTML = HTML.slice(head, foot);

		return HTML;
	}

	// ---------------------------------------------------------------------//
	// public interface of Files.

	Object.assign(Files.prototype, {
		add : Files_add,
		list : Files_list,
		sort : Files_sort,

		// convert encoding
		encode : setup_Files(Files_encode, 'encode'),
		merge : setup_Files(Files_merge, 'merge'),

		replace : setup_Files(Files_replace, 'replace'),

		forEach : Files_forEach,
		encoding : 'UTF-8'
	});

	// ---------------------------------------------------------------------//
	// export.

	return Object.assign(Files, {
		HTML_contents : HTML_contents,
		file_system_structure : file_system_structure
	});

}
