/**
 * @name	CeL log function
 * @fileoverview
 * 本檔案包含了記錄用 functions。
 * 
 * @since	2009/11/17
 * @see
 * <a href="http://getfirebug.com/lite.html" accessdate="2010/1/1 14:54">Firebug Lite</a>,
 * <a href="http://www.mozilla.org/projects/venkman/" accessdate="2010/1/1 16:43">Venkman JavaScript Debugger project page</a>
 */

//	http://blogs.msdn.com/b/webdevtools/archive/2007/03/02/jscript-intellisense-in-orcas.aspx
///	<reference path="../../ce.js" />
/**
 * <code>
 TODO:
 https://developers.google.com/web/tools/chrome-devtools/console/console-write#styling_console_output_with_css
 console.log("%c", 將 CSS 樣式規則應用到第二個參數指定的輸出字符串)


 emergency/urgent situation alert
 會盡量以網頁上方/頂部黄色的導航條/警告條展示
 「不再顯示」功能
 .format()
 將 div format 成 log panel。
 分群, http://developer.yahoo.com/yui/examples/uploader/uploader-simple-button.html
 </code>
 */

/**
 * <code>
 to include:
 include code_for_including
 <div id="debug_panel"></div>
 var SL = new Debug.log('debug_panel'), sl = function() { SL.log.apply(SL, arguments); }, error = function() { SL.error.apply(SL, arguments); }, warn = function() { SL.warn.apply(SL, arguments); };

 http://www.comsharp.com/GetKnowledge/zh-CN/TeamBlogTimothyPage_K742.aspx

 if possible, use Firebug Lite instead.
 http://benalman.com/projects/javascript-debug-console-log/
 </code>
 */

'use strict';

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

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

	// Object.is()
	require : 'data.code.compatibility.',

	// 設定不匯出的子函式。
	no_extend : 'this,do_log,extend',

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

	finish : finish
});

function module_code(library_namespace) {

	// WScript.Echo(this);

	var

	// class private -----------------------------------

	// class name, 需要用到這個都不是好方法。
	// cn = 'Debug.log',

	/**
	 * private storage pool
	 * 
	 * @ignore
	 */
	p = [],
	//
	has_performance_now,
	//
	log_data = function(message, options) {
		// 由於 .set_method() 會用到 .debug()，
		// 若在 log 的 core 中用上 .set_method() 會循環呼叫，造成 stack overflow。
		// ** NG: library_namespace.set_method(this, options);
		if (library_namespace.is_Object(options))
			Object.assign(this, options);

		this.date = new Date();
		if (has_performance_now)
			this.time = performance.now();
		this.message = message;
		return this;
	},

	/**
	 * default write/show log function
	 * 
	 * @ignore
	 * @param {string}id
	 *            element id
	 */
	write_log = function(id) {
		// console.log(id);
		var o, m, c, _p = p[id], _t = _p.instance,
		/**
		 * buffer
		 * 
		 * @inner
		 * @ignore
		 */
		b = _p.buf, B = _p.board, F = _p.do_function, level;

		if (_p.clean)
			_t.clear(), _p.clean = 0;

		if (!B && !F)
			return;

		while (b.length) {
			// 預防 multi-threading 時重複顯示。
			m = b.shift();

			if (F)
				F(m);

			// IE8: 'constructor' 是 null 或不是一個物件
			try {
				c = m.constructor;
				if (false)
					alert((m.constructor === log_data) + '\n' + m.constructor
							+ '\n' + m);
			} catch (e) {
			}
			if (c === log_data) {
				if (!isNaN(m.level) && m.level < library_namespace.set_debug())
					continue;
				c = m.level in _t.className_set ? m.level : 0;
				o = m.add_class;
				// 添加各種標記。
				m = [ _t.message_prefix(c), _t.show_time(m.date, m.time),
						m.message ];
				c = _t.className_set[c];
				if (o)
					c += ' ' + o;

			} else {
				// add default style set
				if (c = _t.message_prefix('log'))
					m = [ c, m ];
				c = _t.className_set.log || 0;
			}
			_p.lbuf.push(m);

			if (B
			// && typeof document === 'object'
			) {
				o = _p.instance.log_tag;
				if (o) {
					o = document.createElement(o);
					if (c)
						o.className = c;

					new_node(m, o);

				} else {
					o = document.createTextNode(m);
				}
				// TODO: pause
				B.appendChild(o);
				while (B.childNodes.length > _p.max_logs) {
					B.removeChild(B.firstChild);
				}
			}
		}

		if (false) {
			if (_t.auto_hide)
				B.style.display = B.innerHTML ? 'block' : 'none';
		}
		// TODO: 有時無法捲到最新。
		if (B && _t.auto_scroll)
			B.scrollTop = B.scrollHeight - B.clientHeight;
	},

	/**
	 * save log.
	 * 
	 * @ignore
	 * @param m
	 *            message
	 * @param {string}
	 *            id element id
	 * @param force
	 *            force to clean the message area
	 */
	do_save_log = function(m, id, force) {
		// console.log(m);
		var _p = p[id], _t = _p.instance,
		// log file handler
		f = _p.logF, s = _t.save_log;
		if (!s || typeof s === 'function' && !s(m, l))
			return;

		if (m)
			_p.sbuf
					.push(m = (_t.save_date && typeof gDate === 'function' ? _t.save_line_separator
							+ gDate() + _t.save_line_separator
							: '')
							+ m);

		if (force || _t.flush || _p.sbufL > _t.save_limit)
			try {
				if (f
						|| _t.log_file
						&& (f = _p.logF = fso.OpenTextFile(_t.log_file,
						/* ForAppending */8, /* create */true,
								_t.log_encoding)))
					f.Write(_p.sbuf.join(_t.save_line_separator)),
							_p.sbuf = [], _p.sbufL = 0, _t.error_message = 0;
			} catch (e) {
				// error(e);
				_t.error_message = e;
			}
		else if (m)
			_p.sbufL += m.length;
	},

	using_DOM_new_node = false,
	// 使 log 能用到 new_node 的功能。
	// @see function_placeholder() @ module.js
	new_node = function(o, layer) {
		// console.log(o);
		if (library_namespace.is_Function(library_namespace.new_node)) {
			// alert('開始利用 library 之 new_node。');
			using_DOM_new_node = true;
			return (new_node = library_namespace.new_node)(o, layer);
		}

		var list = [];

		// workaround: 簡易版 new_node().
		(function add(o) {
			var node, tag, child;
			if (Array.isArray(o))
				for (node = 0; node < o.length; node++)
					add(o[node]);
			else if (library_namespace.is_Object(o)) {
				if (o.$) {
					tag = o.$;
					list.push('<' + tag);
					delete o.$;
				}
				for (node in o) {
					if (tag)
						list.push(' ' + node + '="'
								+ ('' + o[node]).replace(/"/g, '&quot;') + '"');
					else {
						tag = node;
						list.push('<' + tag);
						child = o[node] || null;
					}
				}
				if (child === null)
					list.push(' />');
				else {
					list.push('>');
					add(child);
					list.push('</' + tag + '>');
				}
			} else
				list.push(o);
		})(o);

		layer.innerHTML = list.join('');

		return using_DOM_new_node;
	},

	show_date = function(date) {
		var h = date.getHours(), m = date.getMinutes(), s = date.getSeconds(), ms = date
				.getMilliseconds();
		return (h || m || s ? (h || m ? (h ? h + ':' : '') + m + ':' : '') + s
				: '')
				+ '.' + (ms > 99 ? '' : ms > 9 ? '0' : '00') + ms;
	},

	has_caller,

	// instance constructor ---------------------------
	// (document object)
	/**
	 * <code>

	_ = this


	TODO:
	set class in each input
	input array
	show file path & directory functional	可從 FSO operation.hta 移植。

	count
	c.f.: GLog

	dependency:

	</code>
	 */
	/**
	 * initial a log tool's instance/object
	 * 
	 * @class log function
	 * @_see usage: <a href="#.extend">_module_.extend</a>
	 * @since 2008/8/20 23:9:48
	 * @requires gDate(),line_separator,fso
	 * 
	 * @constructor
	 * @_name _module_
	 * @param {String|object
	 *            HTMLElement} obj log target: message area element or id
	 * @param {Object}
	 *            [className_set] class name set
	 */
	_// JSDT:_tmp;_module_
	= function(obj, className_set) {
		// Initial instance object. You can set it yourself.
		/**
		 * log 時 warning/error message 之 className
		 * 
		 * @_name _module_.prototype.className_set
		 */
		this.className_set = className_set || {
			/**
			 * @_description 當呼叫 {@link _module_.prototype.log} 時使用的 className,
			 *               DEFAULT className.
			 * @_name _module_.prototype.className_set.log
			 */
			log : 'debug_log',
			/**
			 * @_description 當呼叫 {@link _module_.prototype.warn} 時使用的 className
			 * @_name _module_.prototype.className_set.warn
			 */
			warn : 'debug_warn',
			/**
			 * @_description 當呼叫 {@link _module_.prototype.error} 時使用的 className
			 * @_name _module_.prototype.className_set.error
			 */
			error : 'debug_error',
			/**
			 * @_description 當顯示時間時使用的 className
			 * @_name _module_.prototype.className_set.time
			 */
			time : 'debug_time',
			/**
			 * @_description 當呼叫 {@link _module_.prototype.set_board} 時設定 log
			 *               panel 使用的 className
			 * @_name _module_.prototype.className_set.panel
			 */
			panel : 'debug_panel'
		};
		this.class_hide = {};

		var prefix = {
			/**
			 * @_description 當呼叫 {@link _module_.prototype.log} 時使用的 prefix,
			 *               DEFAULT prefix.
			 * @_name _module_.prototype.message_prefix.log
			 */
			log : '',
			/**
			 * @_description 當呼叫 {@link _module_.prototype.warn} 時使用的 prefix
			 * @_name _module_.prototype.message_prefix.warn
			 */
			warn : '',
			/**
			 * @_description 表示當呼叫 {@link _module_.prototype.error}, 是錯誤 error
			 *               message 時使用的 prefix
			 * @_name _module_.prototype.message_prefix.error
			 */
			error : '<em>!! Error !!</em> '
		};
		/**
		 * log 時 warning/error message 之 prefix。
		 * 
		 * @_name _module_.prototype.message_prefix
		 */
		this.message_prefix = function(level) {
			return level in prefix ? prefix[level] : '';
		};

		this.id = p.length;
		p.push({
			instance : this,
			/** write buffer */
			buf : [],
			/** save buffer when we need to save the messages */
			sbuf : [],
			/** length of save buffer */
			sbufL : 0,
			/** now logged buffer */
			lbuf : []
		});
		this.set_board(obj);
	};

	try {
		has_performance_now = performance.now() > 0;
	} catch (e) {
	}

	try {
		has_caller = function(a) {
			'use strict';
			return arguments.callee.caller !== undefined;
		};
		has_caller = (function() {
			return has_caller();
		})();
	} catch (e) {
		has_caller = false;
	}

	// class public interface ---------------------------

	_// JSDT:_module_
	.
	/**
	 * do the log action
	 * 
	 * @_memberOf _module_
	 * @private
	 */
	do_log = function(id) {
		/**
		 * <code>

		這段應該只在 module namespace 重複定義時才會發生

		var I = p[id];
		if (!I) {
			alert('.do_log: not exist: [' + id + ']');
			return;
		}
		I = I.instance;
		</code>
		 */

		var I = p[id].instance;
		if (I.do_log)
			I.do_log();
	};

	_// JSDT:_module_
	.
	/**
	 * 對各種不同 error object 作應對，獲得可理解的 error message。
	 * 
	 * @param e
	 *            error object
	 * @param line_separator
	 *            line separator
	 * @param caller
	 *            function caller
	 * @_memberOf _module_
	 * @see http://msdn.microsoft.com/en-us/library/ms976144.aspx The facility
	 *      code establishes who originated the error. For example, all internal
	 *      script engine errors generated by the JScript engine have a facility
	 *      code of "A".
	 *      http://msdn.microsoft.com/en-us/library/ms690088(VS.85).aspx
	 * @see http://msdn.microsoft.com/en-us/library/t9zk6eay.aspx
	 *      http://msdn.microsoft.com/en-us/library/microsoft.jscript.errorobject.aspx
	 *      Specifies the name of the type of the error. Possible values include
	 *      Error, EvalError, RangeError, ReferenceError, SyntaxError,
	 *      TypeError, and URIError.
	 */
	get_error_message = function get_error_message(e, line_separator, caller) {
		if (!line_separator)
			line_separator = _.prototype.save_line_separator;

		if (!caller || typeof caller !== 'string') {
			if (typeof caller !== 'function' && has_caller)
				caller = get_error_message.caller;

			if (caller === null)
				caller = 'from the top level';
			else if (typeof caller === 'function')
				caller = '@'
						+ (library_namespace.get_function_name(caller) || caller);
			else
				caller = '@' + library_namespace.Class;
		}

		// from popErr()
		// type
		var T = library_namespace.is_type(e),
		// message
		m = T === 'Error' ? 'Error '
				+ caller
				+ ': '
				/**
				 * <code>
					http://msdn.microsoft.com/en-us/library/cc231198(PROT.10).aspx
					<a href="http://msdn.microsoft.com/en-us/library/ms819773.aspx">Winerror.h</a>: error code definitions for the Win32 API functions
					(e.number & 0xFFFF): See 錯誤代碼 /錯誤提示碼 <a href="http://msdn.microsoft.com/en-us/library/ms681381%28VS.85%29.aspx">System Error Codes</a>
					http://social.msdn.microsoft.com/Search/zh-TW/?Query=%22System+Error+Codes%22+740&AddEnglish=1
					http://msdn.microsoft.com/en-us/library/aa394559(VS.85).aspx
					net helpmsg (e.number & 0xFFFF)
				</code>
				 */
				+ (e.number & 0xFFFF)
				+ (e.name ? ' [' + e.name + '] ' : ' ')
				+ '(facility code '
				+ (e.number >> 16 & 0x1FFF)
				+ '): '
				+ line_separator
				+ (e.message || '').replace(/\r?\n/g, '<br />')
				// .message 為主，.description 是舊的。
				+ (!e.description || e.description === e.message ? ''
						: line_separator
								+ line_separator
								+ ('' + e.description).replace(/\r?\n/g,
										'<br />'))

		: T === 'DOMException' ?
		// http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-17189187
		'[' + T + '] ' + e.code + ': ' + e.message
		//
		: !e || T === 'string' ? e
		//
		: '[' + T + '] ' + (e.message || e);

		if (library_namespace.is_debug(2) && typeof e === 'object' && e)
			for (T in e)
				try {
					// Firefox has (new Error).stack
					// http://eriwen.com/javascript/js-stack-trace/
					m += '<br /> <span class="debug_debug">'
							+ T
							+ '</span>: '
							+ (typeof e[T] === 'string' && T === 'stack' ? e[T]
									.replace(/[\r\n]+$/, '')
									.replace(/(@)([a-z\-]+:\/\/.+)(:)(\d+)$/gm,
											'$1<a href="view-source:$2#$4" target="_blank">$2</a>$3$4')
									.replace(/\n/g, '<br />- ')
									: typeof e[T] === 'string'
											&& T === 'fileName' ? '<a href="view-source:'
											+ e[T]
											+ '" target="_blank">'
											+ e[T] + '</a>'
											: e[T]);
				} catch (e) {
					// TODO: handle exception
				}

		// m += ' (' + arguments.callee.caller + ')';
		return m;
	};

	_// JSDT:_module_
	.
	/**
	 * get node description
	 * 
	 * @param node
	 *            HTML node
	 * @_memberOf _module_
	 */
	node_description = function(node, flag) {
		// console.log(node);
		if (typeof node === 'string')
			node = document.getElementById(node);
		if (!node)
			return;

		var description = '';

		if (node.id)
			description += '#' + node.id;

		if (node.className)
			description += '.' + node.className;

		if (node.tagName)
			description = '&lt;' + node.tagName + description + '&gt;';

		if (!description && node.innerHTML) {
			description = node.innerHTML;
			if (description.length > 40)
				description = description.slice(0, 40);
			description = description.replace(/</g, '&lt;');
		}

		// TODO: 對 Range object 之類的處理
		// http://help.dottoro.com/ljxsqnoi.php
		return description || '(null description node: '
				+ library_namespace.is_type(node) + ')';
	};

	_// JSDT:_module_
	.default_log_target = function(message_data) {
		var level = typeof message_data === 'object' && message_data.level, logger;
		if (logger = level && library_namespace.debug_console[level])
			logger(message_data.message);
		else
			console.log(level ? '[' + level + '] ' + message_data.message
					: message_data);
	};

	_// JSDT:_module_
	.
	/**
	 * get new extend instance
	 * 
	 * @param {String|object
	 *            HTMLElement} [obj] message area element or id
	 * @return {Array} [ instance of this module, log function, warning
	 *         function, error function ]
	 * @_example<code>

	// status logger
	var SL = new _module_('log'), sl = SL[1], warn = SL[2], error = SL[3];
	sl(msg);
	sl(msg, clear);

	// general log
	function_set = new _module_.extend('panel', {});
	// 1.
	function_set = new CeL.code.log.extend('panel', {});
	logger = function_set[1];
	// 2.
	log_only = (new CeL.code.log.extend('panel', {}))[1];

	</code>
	 * @_memberOf _module_
	 * @since 2009/8/24 20:15:31
	 */
	extend = function(obj, className_set) {
		if (false) {
			CeL.Log = new CeL.code.log(
					function(m) {
						var F = typeof JSalert === 'function' ? JSalert
								: typeof alert === 'function' ? alert
										: WScript.Echo;
						F(typeof m === 'object' ? '[' + m.level + '] '
								+ m.message : m);
					});
		}

		/**
		 * new instance
		 * 
		 * @_type _module_
		 * @inner
		 * @ignore
		 */
		var log_controller = new _// JSDT:_module_
		(obj || _.default_log_target, className_set);

		// TODO: do not use arguments
		return [ log_controller, function() {
			// console.log(arguments);
			log_controller.log.apply(log_controller, arguments);
		}, function() {
			log_controller.warn.apply(log_controller, arguments);
		}, function() {
			log_controller.error.apply(log_controller, arguments);
		} ];

	};

	/**
	 * <code>
	_.option_open=function(p){

	};

	_.option_file=function(p){
	};

	_.option_folder=function(p){
	};
	</code>
	 */

	// class constructor ---------------------------
	_// JSDT:_module_
	.prototype = {

		// instance public interface -------------------

		/**
		 * 當執行寫檔案或任何錯誤發生時之錯誤訊息。<br />
		 * while error occurred.. should read only
		 * 
		 * @_name _module_.prototype.error_message
		 */
		error_message : '',

		/**
		 * 超過這長度才 save。<=0 表示 autoflash，非數字則不紀錄。
		 * 
		 * @_name _module_.prototype.save_limit
		 * @type Number
		 */
		save_limit : 4000,

		/**
		 * 在 log 結束時執行，相當於 VB 中 DoEvent() 或 。
		 * 
		 * @_name _module_.prototype.do_event
		 */
		do_event : library_namespace.DoNoting || null,

		/**
		 * log 時使用之 tagName, 可用 div / span 等。若不設定會用 document.createTextNode
		 * 
		 * @_name _module_.prototype.log_tag
		 */
		log_tag : 'div',

		/**
		 * boolean or function(message, log level) return save or not
		 * 
		 * @_name _module_.prototype.save_log
		 * @type Boolean
		 */
		save_log : false,
		/**
		 * save log to this file path
		 * 
		 * @_name _module_.prototype.log_file
		 * @type Boolean
		 */
		log_file : false,
		/**
		 * auto save log. 若未設定，記得在 onunload 時 .save()
		 * 
		 * @_name _module_.prototype.flush
		 * @type Boolean
		 */
		flush : false,
		/**
		 * 在 save log 時 add date
		 * 
		 * @_name _module_.prototype.save_date
		 * @type Boolean
		 */
		save_date : true,
		/**
		 * 在 save log 時的換行
		 * 
		 * @_name _module_.prototype.save_line_separator
		 * @type string
		 */
		save_line_separator : library_namespace.env.line_separator || '\r\n',
		/**
		 * 在 save log 時的 encoding
		 * 
		 * @_name _module_.prototype.log_encoding
		 */
		log_encoding : -1,// -1: TristateTrue

		/**
		 * 自動捲動
		 * 
		 * @_name _module_.prototype.auto_scroll
		 * @type Boolean
		 */
		auto_scroll : true,
		/**
		 * 沒有內容時自動隱藏
		 * 
		 * @deprecated TODO
		 * @_name _module_.prototype.auto_hide
		 * @type Boolean
		 */
		auto_hide : false,

		/**
		 * 等待多久才顯示 log。若為 0 則直接顯示。<br />
		 * e.g., 即時顯示，不延遲顯示： CeL.Log.interval = 0;<br />
		 * (WScript 沒有 setTimeout)
		 * 
		 * @_name _module_.prototype.interval
		 */
		interval : typeof setTimeout === 'undefined' ? 0 : 1,

		/**
		 * log function (no delay)
		 * 
		 * @_name _module_.prototype.do_log
		 */
		do_log : function(level) {
			if (false)
				if (p[this.id].th)
					clearTimeout(p[this.id].th);

			// reset timeout handler
			p[this.id].th = 0;

			// TODO: 提升效率.
			if ('controller' in this)
				this.set_controller();

			write_log(this.id);
		},

		/**
		 * class instance 預設作 log 之 function
		 * 
		 * @param {String}
		 *            message message
		 * @param {Boolean}clean
		 *            clean message area
		 * @param {Object}options
		 *            選擇性項目. { level : log level, 記錄複雜度. }
		 * @return
		 * @_name _module_.prototype.log
		 */
		log : function(message, clean, options) {
			// console.log(message);
			var t = this, _p = p[t.id], level, force_save;

			if (library_namespace.is_Object(options)) {
				level = options.level;
				force_save = options.save;
			} else if (options) {
				force_save = level = options;
				(options = {}).level = level;
			}

			/**
			 * <code>
					var message_head = (arguments.callee.caller + '')
							.match(/function\do_save_log([^\(]+)/);
					if (message_head)
						message_head = message_head[1] + ' ';
			</code>
			 */
			do_save_log(message, t.id, force_save);

			// window.status = message;
			if (options) {
				message = new log_data(message, options);
			}

			if (clean) {
				// clean log next time
				_p.clean = 1, _p.buf = [ message ];
			} else {
				_p.buf.push(message);
			}

			if (!(t.interval > 0))
				t.do_log();
			else if (!_p.th)
				// no window.setTimeout @ node.js
				if (typeof setTimeout === 'undefined')
					t.interval = 0, t.do_log();
				else
					// _p.th = setTimeout(cn + '.do_log(' + t.id + ');',
					// t.interval);
					_p.th = setTimeout(function() {
						_.do_log(t.id);
					}, t.interval);

			if (t.do_event)
				t.do_event();
		},

		/*
		 * TODO: other methods: INFO,DEBUG,WARNING,ERROR,FATAL,UNKNOWN
		 */

		/**
		 * save message
		 * 
		 * @_name _module_.prototype.save
		 */
		save : function() {
			do_save_log('', this.id, 1/* force */);
		},

		/**
		 * <code>

		 ** important ** 這邊不能作 object 之 initialization，否則因為 object 只會 copy reference，因此 new 時東西會一樣。initialization 得在 _() 中作！

		 </code>
		 */
		// className_set : {},
		/**
		 * log a warning / caution / alert / 警告.
		 * 
		 * @_name _module_.prototype.warn
		 */
		warn : function(m, clean) {
			this.log(m, clean, 'warn');
		},

		/**
		 * deal with error message
		 * 
		 * @_name _module_.prototype.error
		 */
		error : function error(e, clean) {
			var caller = '';
			if (has_caller) {
				caller = '' + error.caller;
				if (caller.indexOf('.error.apply(') !== -1)
					// ** 判斷 call from _.extend. TODO: 應該避免!
					caller = caller.caller;
			}

			this.log(Array.isArray(e) || library_namespace.is_Object(e) ? e : _
					.get_error_message(e, this.save_line_separator, caller),
					clean, 'error');
		},

		timezone_offset : /* msPerMinute */60000 * (new Date)
				.getTimezoneOffset(),

		/**
		 * 在 log 中依照格式顯示時間。
		 * 
		 * @param {Date}date
		 * @returns {String} 依照格式顯示成之時間。
		 * @_name _module_.prototype.show_time
		 * @since 2012/3/16 22:36:46
		 */
		show_time : function show_time(date, time) {
			var add_s, _diff_ms,
			//
			date_stamp = (date.getMonth() + 1) + '/' + date.getDate() + ' '
					+ show_date(date),
			//
			diff_ms = has_performance_now && this.last_show ? time
					- this.last_show : (_diff_ms = date
					- (this.last_show || this.timezone_offset));

			if (diff_ms > 0)
				if (diff_ms < 60000) {
					add_s = diff_ms >= 1000 && (diff_ms /= 1000);
					diff_ms = diff_ms.to_fixed ? String(diff_ms.to_fixed(3))
							.replace(/^0/, '')
					// : diff_ms.toFixed ? diff_ms.toFixed(3)
					: (diff_ms | 0);
					if (add_s)
						diff_ms += 's';
				} else
					diff_ms = show_date(new Date(diff_ms + this.timezone_offset));

			this.last_show = has_performance_now ? time : date;

			// 不用 CSS.quotes: 在舊版 browser 上可能無效，但本 module 須在舊版上亦正常作動。
			return '<span class="' + this.className_set.time + '" title="'
					+ date_stamp + '  '
					+ (has_performance_now ? time : '+' + _diff_ms) + ' ms">['
					+ diff_ms + ']</span> ';
		},

		/**
		 * 當記錄太長時，限制記錄數目在 max_logs。超過這個數目就會把之前的最舊的紀錄消除掉。
		 * 
		 * @param {Natural}max_logs
		 *            最大記錄數目
		 */
		set_max_logs : function(max_logs) {
			var _t = this, _p = p[_t.id];
			max_logs = Math.floor(max_logs);
			// accept NaN
			_p.max_logs = max_logs < 0 ? 0 : max_logs;
		},

		/**
		 * 設定寫入到哪<br />
		 * set log board for each instance (document object)
		 * 
		 * @_name _module_.prototype.set_board
		 */
		set_board : function(o) {
			var _t = this, _p = p[_t.id];
			if (o)
				if (typeof o === 'function')
					_p.do_function = o;

				else {
					if (typeof o !== 'object' && typeof document === 'object')
						o = document.getElementById(o);
					if (o
					// TODO
					// && library_namespace.is_HTML_obj(o)
					) {
						_p.board = o;
						_t.set_controller();
						if (_t = _t.className_set.panel)
							o.className += ' ' + _t;
						delete _p.do_function;
					}
				}

			return _p.board;
		},

		// TODO: 若之後才 include 'interact.DOM'，則 controller 沒辦法顯示出來 @ Chrome/25。
		set_controller : function(c) {
			var b = p[this.id].board;
			if (b && (c || (c = this.controller))
					&& (c = new_node(c, [ b, 0 ])) !== using_DOM_new_node) {
				if ('controller' in this)
					delete this.controller;
				// c.style.height = '1em';
				// c.style.height = '';
			}
		},

		/**
		 * 獲取當前 buffer 中的 log。
		 * 
		 * @_name _module_.prototype.get_log
		 */
		get_log : function() {
			return p[this.id].lbuf;
		},

		/**
		 * show/hide log board. 切換可見狀態。
		 * 
		 * @_name _module_.prototype.toggle
		 */
		toggle : function(s) {
			return library_namespace.toggle_display(p[this.id].board, s) !== 'none';
		},

		/**
		 * clear log board. TODO: use .remove_all_child().
		 * 
		 * @_name _module_.prototype.clear_board
		 */
		clear_board : function(b) {
			b.innerHTML = '';
		},

		/**
		 * 清除全部訊息 clear message
		 * 
		 * @_name _module_.prototype.clear
		 */
		clear : function() {
			var _p = p[this.id];
			if (_p.board) {
				this.clear_board(_p.board);
			}
			_p.lbuf = [];
		}

	};

	return (_// JSDT:_module_
	);

}

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

function finish(name_space) {
	// 為 module log 所作的初始化工作。

	var module_name = this.id;

	// 確認 cssRules 之後才作 delete，否則就得按順序先增者後減。因為刪掉 [2] 之後，後面全部皆會遞補，[3] 會變成 [2]。
	// TODO: 一般化。
	function search_CSS_rule(style_sheet, selector) {
		var rules = style_sheet.cssRules || style_sheet.rules, i = 0, l = rules.length;
		for (; i < l; i++)
			if (selector === rules[i].selectorText)
				return i;
	}

	// WScript.Echo(n.extend);

	if (false)
		code_for_including[generateCode.dLK] = '*var Debug={log:code_for_including()};';

	// include resources of module.
	CeL.run(CeL.get_module_path(module_name, 'log.css'));

	// 為本 library 用
	if (!CeL.Log) {
		var i, l, log_controller = name_space.extend(), has_caller,
		// 偵錯等級, debug level, log level.
		log_icon = {
			/**
			 * MEMO (U+1F4DD).<br />
			 * http://codepoints.net/U+1F4DD http://wiki.livedoor.jp/qvarie/
			 */
			log : '📝',
			/**
			 * emphasized text<br />
			 * U+2383 EMPHASIS SYMBOL<br />
			 * http://codepoints.net/U+2383
			 */
			em : '⎃',
			/**
			 * 資訊,消息,報告,通知,情報<br />
			 * WARNING SIGN (U+26A0) @ Miscellaneous Symbols.
			 */
			warn : '⚠',
			/**
			 * error / fault<br />
			 * U+2620 SKULL AND CROSSBONES
			 */
			error : '☠',
			/**
			 * U+2139 INFORMATION SOURCE<br />
			 * http://en.wiktionary.org/wiki/%E2%84%B9
			 */
			info : 'ℹ',
			/**
			 * U+1F41B BUG
			 */
			debug : '🐛',
			/**
			 * U+1F463 footprints
			 * https://unicode.org/emoji/charts/full-emoji-list.html
			 */
			trace : '👣'
		},
		// base path of icon
		icon_path = CeL.get_module_path(module_name, 'icon/');

		try {
			has_caller = function(a) {
				'use strict';
				return arguments.callee.caller !== undefined;
			};
			has_caller = (function() {
				return has_caller();
			})();
		} catch (e) {
			has_caller = false;
		}

		// console.log('override: CeL.Log = ' + log_controller[0]);
		CeL.Log = log_controller[0];
		// console.log('setup CeL.Log.className_set');
		Object.assign(CeL.Log.className_set, {
			info : 'debug_info',
			em : 'debug_em',
			debug : 'debug_debug'
		});

		// log 支援 gettext.
		CeL.Log.message_prefix = function(level) {
			if (level in log_icon) {
				return {
					img : null,
					'class' : 'debug_icon',
					src : icon_path + level + '.png',
					alt : '[' + log_icon[level] + ']',
					title : log_icon[level] + ' '
					// gettext_config:{"id":"log-type-fatal","mark_type":"combination_message_id"}
					// gettext_config:{"id":"log-type-error","mark_type":"combination_message_id"}
					// gettext_config:{"id":"log-type-warn","mark_type":"combination_message_id"}
					// gettext_config:{"id":"log-type-em","mark_type":"combination_message_id"}
					// gettext_config:{"id":"log-type-info","mark_type":"combination_message_id"}
					// gettext_config:{"id":"log-type-log","mark_type":"combination_message_id"}
					// gettext_config:{"id":"log-type-debug","mark_type":"combination_message_id"}
					// gettext_config:{"id":"log-type-trace","mark_type":"combination_message_id"}
					+ CeL.gettext('log-type-' + level)
				};
			}
			return '';
		};

		// TODO: copy result, paste code.
		var controller = [ ':', {
			// U+239A CLEAR SCREEN SYMBOL
			a : '⎚',
			href : '#',
			title : "clear / 清除所有訊息",
			onclick : function() {
				CeL.Log.clear();
				return false;
			}
		}, {
			// toggle / switch
			// U+1F50C ELECTRIC PLUG
			a : '🔌',
			href : '#',
			title : "切換訊息面板\nshow/hidden log panel",
			onclick : function() {
				CeL.set_class(this, 'debug_hide', {
					remove : CeL.Log.toggle()
				});
				return false;
			}
		}, {
			span : '↑',
			title : "提升偵錯等級",
			S : 'cursor:pointer;font-size:.7em;',
			onselect : function() {
				return false;
			},
			onclick : function() {
				CeL.set_debug(CeL.is_debug() + 1);
				CeL.debug('提升偵錯等級至 ' + CeL.is_debug(), 1, 'Log.controller');
				return false;
			}
		}, {
			span : '↓',
			title : "降低偵錯等級",
			S : 'cursor:pointer;font-size:.7em;',
			onselect : function() {
				return false;
			},
			onclick : function() {
				CeL.set_debug(CeL.is_debug() - 1);
				CeL.debug('降低偵錯等級至 ' + CeL.is_debug(), 0, 'Log.controller');
				return false;
			}
		}, {
			span : '↓',
			title : "取消 debug",
			S : 'cursor:pointer;font-size:.7em;text-decoration:underline;',
			onselect : function() {
				return false;
			},
			onclick : function() {
				CeL.set_debug(0);
				return false;
			}
		}, {
			br : null
		} ];
		l = {
			debug : 0,
			log : 0,
			info : 'information',
			em : 'emphasis',
			warn : 'warning',
			error : 'error'
		};
		for (i in l) {
			controller.push(' ', {
				a : log_icon[i],
				href : '#',
				title : 'toggle [' + i + ']\n切換 ' + (l[i] || i) + ' 訊息',
				onclick : function() {
					var tag = this.title.match(/\[([^\]]+)\]/);
					if (tag)
						CeL.set_class(this, 'debug_hide', {
							remove : CeL.toggle_log(tag[1])
						});
					return false;
				}
			});
		}
		// 增加 group 以便在多項輸入時亦可 toggle 或排版。
		CeL.Log.controller = {
			div : [ {
				a : 'log',
				href : '#',
				title : 'log 控制項',
				onclick : function() {
					var parentNode = this.parentNode;
					if (parentNode.force_show) {
						// DOM 不可使用 delete @ IE9
						// delete parentNode.force_show;
						parentNode.force_show = false;
					} else {
						CeL.toggle_display(this.nextSibling,
						//
						parentNode.force_show = true);
					}
					return false;
				}
			}, {
				span : controller,
				C : 'debug_controller'
			} ],
			// TODO: 即使僅是移動 mouse 進入 child，也會執行多次。
			onmouseover : function() {
				CeL.toggle_display(this.firstChild.nextSibling, 1);
			},
			onmouseout : function() {
				if (!this.force_show) {
					CeL.toggle_display(this.firstChild.nextSibling, 0);
				}
			},
			C : 'debug_controller_panel'
		};

		// 在 CeL.log 被重新設定前先 cache 一下。
		var log_buffer = CeL.log && CeL.log.buffer;

		// --------------------------------------------------------------------------------------------
		// front ends of log function

		/**
		 * 警告: 在 node.js v0.10.25, v0.11.16 中，不使用 var 的模式設定 function，會造成:<br />
		 * In strict mode code, functions can only be declared at top level or
		 * immediately within another function.
		 * 
		 * 在 node.js v4.2.1 中可以順利 pass。
		 */

		var log_front_end_fatal =
		// fatal: the most serious 致命錯誤。
		function log_front_end_fatal(message, error_to_throw) {
			if (CeL.is_WWW())
				try {
					console.trace(error_to_throw);
					// 模擬 throw 以 get .stack
					throw CeL.is_type(error_to_throw, 'Error') ? error_to_throw
							: new Error(error_to_throw || 'Fatal error');
				} catch (e) {
					// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
					CeL.error(e.stack ? message
							+ '<br />stack:<div class="debug_stack">'
							+ (typeof e.stack === 'string' ? e.stack.replace(
									/\n/g, '<br />') : e.stack) + '</div>'
							: message);
					if (typeof console === 'object' && console.trace) {
						// Will show stacks
						console.trace(e);
					}
				}
			else
				CeL.error(message);

			if (typeof error_to_throw === 'undefined')
				// 預設會 throw message.
				error_to_throw = message;

			if (error_to_throw) {
				if (CeL.platform.nodejs && error_to_throw !== message)
					// node.js 中，throw Error 可能無法顯示 local encoding，因此在此先顯示一次。
					console.error(error_to_throw);
				throw CeL.is_type(error_to_throw, 'Error') ? error_to_throw
						: new Error(error_to_throw);
			}
		}

		var log_front_end_debug =
		// 增加 debug 訊息。
		function log_front_end_debug(message, level, caller, clean) {
			if (false) {
				alert(CeL.is_debug() + ',' + l + '(' + (l === undefined) + '),'
						+ message);
			}

			if (!CeL.is_debug(level)) {
				return;
			}

			if (typeof message === 'function') {
				// for .debug(function(){return some_function(..);}, 3);
				message = 'function: [' + message + ']<br />return: ['
						+ message() + ']';
			}

			if (!caller && has_caller) {
				// TODO: do not use arguments
				caller = caller !== arguments.callee
						&& CeL.get_function_name(arguments.callee.caller);
				if (false) {
					CeL.log(CeL.is_type(arguments.callee.caller));
					CeL.log(Array.isArray(caller));
					CeL.log(caller + ': ' + arguments.callee.caller);
					CeL.warn(CeL.debug);
				}
			}
			if (caller) {
				message = CeL.is_WWW() ? [ {
					// (caller.charAt(0) === '.' ? CeL.Class + caller :
					// caller)
					span : caller,
					'class' : 'debug_caller'
				}, ': ', message ] : CeL.to_SGR([ '', 'fg=yellow',
						caller + ': ', '-fg', message ]);
			}

			CeL.Log.log(message, clean, {
				level : 'debug',
				add_class : 'debug_' + (level || CeL.is_debug())
			});
		}

		var log_front_end_info =
		//
		function log_front_end_info(message, clean) {
			// information
			CeL.Log.log.call(CeL.Log, message, clean, 'info');
			// CeL.log.apply(CeL, arguments);
		};

		var log_front_end_toggle_log =
		// 切換(顯示/隱藏)個別訊息。
		function log_front_end_toggle_log(type, show) {
			if (!type)
				type = 'debug';
			var hiding = type in CeL.Log.class_hide;
			if (typeof show === 'undefined' || show && hiding || !show
					&& !hiding)
				try {
					// need switch.
					var style_sheet = document.styleSheets[0], selector = '.'
							+ CeL.Log.className_set[type], CSS_index = hiding ? search_CSS_rule(
							style_sheet, selector)
							: undefined;
					if (isNaN(CSS_index)) {
						// assign a new index.
						CSS_index = style_sheet.cssRules
								&& style_sheet.cssRules.length ||
								// IE6
								style_sheet.rules && style_sheet.rules.length
								|| 0;
						CeL.debug('insert CSS index: ' + CSS_index, 2,
								'toggle_log');
						var style = 'display:none';
						style_sheet.insertRule ?
						/**
						 * firefox, IE 必須輸入 index.<br />
						 * <a
						 * href="https://developer.mozilla.org/en/DOM/CSSStyleSheet/insertRule"
						 * accessdate="2012/5/14 13:13">insertRule - MDN</a>
						 */
						style_sheet.insertRule(selector + '{' + style + ';}',
								CSS_index) :
						/**
						 * IE6: <a
						 * href="http://msdn.microsoft.com/en-us/library/aa358796%28v=vs.85%29.aspx"
						 * accessdate="2012/5/14 13:13">IHTMLStyleSheet::addRule
						 * method (Internet Explorer)</a>
						 */
						style_sheet.addRule(selector, style, CSS_index);

						// OK 之後才設定.
						CeL.Log.class_hide[type] = CSS_index;

					} else {
						CeL.debug('delete CSS index: ' + CSS_index, 2,
								'toggle_log');
						style_sheet.deleteRule ? style_sheet
								.deleteRule(CSS_index) :
						// IE6
						style_sheet.removeRule(CSS_index);
						// OK 之後才 delete.
						delete CeL.Log.class_hide[type];
					}
					hiding = !hiding;
				} catch (e) {
					CeL
							.log('The browser may not support <a href="http://www.w3.org/TR/DOM-Level-2-Style/css" target="_blank">Document Object Model CSS</a>? Cannot toggle debug message: <em>'
									+ e.message + '</em>');
				}
			return !hiding;
		}

		var log_front_end_assert =
		/**
		 * 斷定/測試/驗證 verify/檢查狀態。<br />
		 * 
		 * @param {Boolean|Array|Function}condition
		 *            test case.<br />
		 *            {Function} testing function to run. Using default expected
		 *            value: true<br />
		 *            {Array} [ condition 1, condition 2 ]<br />
		 *            {Object} 直接將之當作 options
		 * 
		 * @param {Object}[options]
		 *            附加參數/設定選擇性/特殊功能與選項。 {<br />
		 *            {String}name: test name 此次測試名稱。,<br />
		 *            {String}NG: meaning of failure,<br />
		 *            {String}OK: meaning of passed,<br />
		 *            {String}hide_OK: false: 當 passed 時不顯示,<br />
		 *            {Boolean}ignorable: false / need 手動 check,<br />
		 *            {String|Object}type: expected type,<br />
		 *            {Boolean}no_cache: false,<br />
		 *            {Any}expect: expected value 預期的結果。should be what value.,<br />
		 *            {Number}error_rate > 0: 容許誤差率 permissible error ratio.
		 *            e.g., Number.EPSILON,<br />
		 *            {Boolean}exactly: true, need exactly (value === expected)
		 *            or false: equal (value == expected) is also OK.<br />
		 *            {Boolean}force_true: false, 當測試效能時，強迫測試結果一定成功。<br />
		 *            {String}eval: testing expression code to eval = value /
		 *            function(){return value_to_test;}<br />
		 *            {Function}callback: 回調函數。 callback(passed)<br /> }
		 * 
		 * @returns {Boolean|...} {Boolean}assertion is succeed.<br />
		 *          {...} ignorable message.
		 * 
		 * @since 2012/9/19 00:20:49, 2015/10/18 21:31:35 重構
		 */
		function log_front_end_assert(condition, options) {

			// --------------------------------
			// 前置處理作業: condition。
			if (!options) {
				if (CeL.is_Object(condition)) {
					// 直接將之當作 options
					options = condition;
					condition = options.eval;
				} else {
					// 前置處理作業: options。
					// (undefined | null).attribute is NOT accessable.
					// ('attribute' in false), ('attribute' in 0) is NOT
					// evaluable.

					// options = Object.create(null);
					// This is faster.
					options = new Boolean;

					// assert: options.attribute is accessable.
					// assert: ('attribute' in options) is evaluable.
				}
			} else if (typeof options === 'string') {
				options = {
					name : options
				};
			} else if (typeof options === 'function') {
				options = {
					callback : options
				};
			}

			var type = options.type;
			if (Array.isArray(condition)) {
				condition = condition.slice(0, type ? 1 : 2);
				if (!type && typeof condition[1] !== 'function'
						&& typeof condition[1] !== 'object')
					// record original condition.
					condition.original = condition[0];
			} else {
				// 有 options.type 將忽略 options.expect 以及 condition[1]!!
				if (type) {
					condition = [ condition ];
				} else {
					condition = [ condition,
					// default expected value: true
					'expect' in options ? options.expect : true ];
					// record original condition.
					condition.original = condition[0];
				}
			}
			// assert: condition = {Array} [ condition 1, condition 2 ]

			function condition_handler(_c, index) {
				if (options.eval && typeof _c === 'string')
					_c = CeL.eval_parse(_c);

				if (typeof _c === 'function')
					_c = _c();

				// may use .map()
				condition[index] = _c;
			}

			// fatal error
			var fatal;
			// if (!options.force_true)
			condition.forEach(options.no_cache ? condition_handler
			//
			: function(_c, index) {
				try {
					condition_handler(_c, index);
				} catch (e) {
					// 執行 condition 時出錯，throw 時的處置。
					fatal = e || true;
					CeL.warn('assert: 執行 condition 時出錯: ' + e.message);
					if (typeof console === 'object' && console.trace) {
						// Will show stacks
						console.trace(e);
					}
				}
			});

			// assert: condition =
			// {Array} [ 純量 value 1, 純量 value 2: expected value ]
			// condition = The actual value to test.

			// --------------------------------
			var exactly, equal;

			if (!fatal
			// && !options.force_true
			) {
				if (type) {
					// 處理作業: type。
					condition = condition[0];
					exactly = equal = typeof type === 'string'
					//
					? typeof condition === type || CeL.is_type(condition, type)
					// TODO: check
					// String|Function|Object|Array|Boolean|Number|Date|RegExp|Error|undefined
					: condition.constructor === type
							|| Object.getPrototypeOf(condition) === type
							|| (type = CeL.native_name(type))
							&& CeL.is_type(condition, type);
				} else if ((equal = +options.error_rate) > 0) {
					// 容許誤差率 permissible error ratio / rate. e.g.,
					// Number.EPSILON
					exactly = equal = Math.abs(1 - +condition[0]
							/ +condition[1]) <= equal;
				} else {
					exactly = equal = Object.is(condition[0], condition[1]);
					if (!exactly) {
						// Do not use "==="
						equal = condition[0] == condition[1];
					}
				}
			}

			// --------------------------------
			// report.

			var MAX_LENGTH = 200;

			function get_type_of(value, with_quote) {
				var type, is_native_type;
				if (Array.isArray(value)) {
					type = 'Array';
					is_native_type = true;
				} else if (CeL.is_Object(value)) {
					type = 'Object';
					is_native_type = true;
				} else if (CeL.is_RegExp(value)) {
					type = 'RegExp';
					is_native_type = true;
				} else if (CeL.is_Date(value)) {
					type = 'Date';
					is_native_type = true;
				} else if (value instanceof Error) {
					type = 'Error';
					is_native_type = true;
				} else {
					type = typeof value;
					if (type === 'object') {
						type = CeL.is_type(value) || type;
					} else if ([ 'string', 'number', 'boolean', 'function',
					// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof
					'bigint', 'symbol' ].includes(type)) {
						is_native_type = true;
						type = type.charAt(0).toUpperCase() + type.slice(1);
					}
				}
				return with_quote ? is_native_type ? '{' + type + '}' : '('
						+ type + ')' : type;
			}

			function quote(message, add_type) {
				if (add_type &&
				// 有些 value 沒必要加上 type。
				message !== null && message !== undefined
				// is not NaN
				&& message === message) {
					add_type = get_type_of(message, true) + ' ';
				} else {
					add_type = '';
				}

				if (typeof message === 'string' && add_type
						&& message.length <= MAX_LENGTH
						&& typeof JSON === 'object' && JSON.stringify) {
					message = JSON.stringify(message);
				} else {
					message = String(message).replace(/\r/g, '\\r');
					if (message.length > MAX_LENGTH)
						message = '[' + message.slice(0, MAX_LENGTH) + ']...'
								+ message.length;
					else
						message = '[' + message + ']';
				}

				return add_type + message;
			}

			var test_name = options.name ? quote(options.name) : 'Assertion';

			// --------------------------------
			// failed.

			if (!options.force_true && (!equal || !exactly &&
			// assert: exactly === true 的條件比 equal === true 嚴苛。
			(!('exactly' in options) || options.exactly))) {
				var error_message = options.NG;
				if (!error_message) {
					error_message = [ test_name,
					// if fault, message: 失敗時所要呈現訊息。
					CeL.to_SGR([ ' ', 'fg=red', 'failed:', '-fg;-bg', ' ' ]) ];
					if (type) {
						error_message.push('type of ' + quote(condition)
								+ ' is not (' + type + ')');
					} else {
						if (('original' in condition)
								&& condition[0] !== condition.original) {
							var original = '' + condition.original;
							if (typeof condition.original === 'function') {
								var matched = original
										.match(CeL.PATTERN_function);
								if (matched) {
									original = matched[1];
								}
							}
							error_message.push(quote(original) + '→');
						}
						error_message.push(quote(condition[0], true)
						//
						+ ' !== ' + quote(condition[1], true));
					}

					if (equal) {
						error_message.push('，但 "==" 之關係成立。');
					}

					error_message = error_message.join('');
				}

				CeL.fatal(error_message, CeL.assert.throw_Error ?
				// exception to throw
				new Error(error_message) : false);

				var ignorable = options.ignorable;
				return ignorable ? ignorable === true ? 'ignored' : ignorable
						: fatal ? undefined : false;
			}

			// --------------------------------
			// passed. 無錯誤發生。

			if (!options.hide_OK && CeL.is_debug()) {
				var passed_message = options.OK;
				if (!passed_message) {
					passed_message = [ test_name,
					//
					CeL.to_SGR([ ' ', 'fg=green', 'passed', '-fg', ' ' ]),
					//
					quote(condition[0], true) ].join('');
				}
				CeL.debug(passed_message, 1,
				// caller: see: CeL.debug
				has_caller && CeL.get_function_name(arguments.callee.caller));
			}

			return true;
		}

		var log_front_end_test =
		/**
		 * 整套測試, unit test 單元測試。
		 * 
		 * @example <code>

		CeL.test([ [ 'aa', {
			type : String
		} ], [ 456, {
			type : 123
		} ], [ {}, {
			type : Object
		} ], [ false, {
			type : Boolean
		} ] ], 'type test');


		// --------------------------------------
		// TODO:

		// may ignore:
		CeL.setup_test(test_group_name);

		CeL.test(test_group_name, conditions, options);
		// conditions #1: [
		// [ test value: true / false, 'test_group_name' ],
		// [ test value: true / false, {name:'test_group_name'} ],
		// [ test value: true / false, {options} ],
		// [ test value: true / false ],
		// test value: true / false,
		//
		// [ [ test value 1, test value 2 ], 'test_group_name' ],
		// [ [ test value 1, test value 2 ], {name:'test_group_name'} ],
		// [ [ test value 1, test value 2 ], {options} ],
		// [ [ test value 1, test value 2 ] ],
		//
		// [ function tester(), 'test_group_name' ],
		// [ function tester() ],
		// [ function tester(callback), {need_callback:true} ],
		// function tester(),
		// function tester() { return new Promise },
		// async function tester(),
		//
		// ]

		// conditions #2:
		// function async_tester(assert)
		// async function async_tester(assert)
		//
		// CeL.test(test_group_name, function async_tester(assert, callback), {need_callback:true});
		//
		// assert(test value: true / false, 'test_group_name');
		// assert(test value: true / false, options);
		// assert(test value: true / false);
		// assert([ test value 1, test value 2 ], 'test_group_name');
		// assert([ test value 1, test value 2 ]);

		CeL.test_finished();


		</code>
		 * 
		 * @param {String}[test_group_name]
		 *            test name 此次測試名稱。
		 * @param {Array|Function}conditions
		 *            condition list passed to assert(): [ [ condition / test
		 *            value, options ], [], ... ].<br />
		 *            允許 {Function}condition(assert, test_handler)
		 * @param {Object}[options]
		 *            附加參數/設定選擇性/特殊功能與選項。 {<br />
		 *            {String}name: test name 此次測試名稱。<br />
		 *            {Object}options: default options for running CeL.assert().<br />
		 *            {Function}callback: 回調函數。 callback(recorder,
		 *            test_group_name)<br /> }
		 * 
		 * @returns {Integer}有錯誤發生的數量。
		 * 
		 * @since 2012/9/19 00:20:49, 2015/10/18 23:8:9 refactoring 重構
		 */
		function log_front_end_test(test_group_name, conditions, options) {
			if ((Array.isArray(test_group_name) || typeof test_group_name === 'function')
					&& !options) {
				// shift arguments: 跳過 test_group_name。
				options = conditions;
				conditions = test_group_name;
			}

			if (!Array.isArray(conditions) && typeof conditions !== 'function') {
				throw new Error(CeL.Class
						+ '.test: Please input {Array} or {Function}!');
				return;
			}

			var assert = CeL.assert,
			// default options for running CeL.assert().
			default_options;

			if (options) {
				if (typeof options === 'function') {
					// console.log('Set callback: ' + options);
					options = {
						callback : options
					};
				} else if (typeof options === 'string') {
					if (!test_group_name)
						test_group_name = options;
					options = undefined;
				} else if ('options' in options)
					default_options = options.options;
			}

			default_options = Object.assign({
				hide_OK : true,
				no_cache : true
			}, default_options);

			var recorder = {
				// OK
				passed : [],
				// skipped
				ignored : [],
				// value is not the same.
				failed : [],
				// 執行 condition 時出錯，throw。
				fatal : [],
				//
				all : []
			};

			function handler(condition_arguments) {
				if (!condition_arguments) {
					// skip this one.
					return;
				}

				recorder.all.push(condition_arguments);

				var result;
				try {
					if (typeof condition_arguments === 'function') {
						result = condition_arguments(assert_proxy);
					} else {
						// assert: Array.isArray(condition_arguments) or
						// arguments
						var options = condition_arguments[1],
						//
						condition = condition_arguments[0];
						// 前置處理作業: condition, options。
						// copy from log_front_end_assert()
						// 目的在將輸入轉成 {Object}，以添入 options。
						if (!options) {
							if (CeL.is_Object(condition)) {
								// 直接將之當作 options
								options = condition;
								condition = options.eval;
							}
						} else if (typeof options === 'string') {
							options = {
								name : options
							};
						} else if (typeof options === 'function') {
							options = {
								callback : options
							};
						}

						// 不汙染 default_options, options
						options = Object.assign(Object.clone(default_options),
								options);

						result = assert(condition, options);
					}

				} catch (e) {
					if (typeof console === 'object' && console.trace) {
						// Will show stacks
						console.trace(e);
					}
					recorder.fatal.push(condition_arguments);
					return;
				}

				switch (result) {
				case true:
					recorder.passed.push(condition_arguments);
					break;
				case false:
					recorder.failed.push(condition_arguments);
					break;
				default:
					recorder.ignored.push(condition_arguments);
					break;
				}

				return result === true;
			}

			// 模擬 CeL.assert()
			function assert_proxy() {
				var sub_test_data = assert_proxy.tests_left[assert_proxy.latest_sub_test_name];
				if (sub_test_data)
					sub_test_data.assert_count++;
				return handler.call(null, arguments);
			}

			// --------------------------------
			// report. 當有多個 setup_test()，report() 可能執行多次！
			var report = function(sub_test_name) {
				var messages;
				if (test_group_name) {
					messages = [ 'Test '
					// asynchronous operations
					+ (assert_proxy.asynchronous ? 'asynchronous ' : '') + '[',
							'fg=cyan', test_group_name ];
					if (sub_test_name) {
						messages.push('-fg',
						// ']=>[', ': ', ']→[', '：', ': '
						': ', 'fg=cyan', sub_test_name);
					}
					messages.push('-fg', ']: ');
					messages = [ CeL.to_SGR(messages) ];
				} else {
					messages = [];
				}

				function join() {
					if (recorder.ignored.length > 0)
						messages.push(CeL.to_SGR([
								', ' + recorder.ignored.length + ' ',
								'fg=yellow', 'ignored', '-fg' ]));

					// Time elapsed, 使用/耗費時間。cf. eta, Estimated Time of Arrival
					var elapsed = Date.now() - assert_proxy.starts;
					if (elapsed >= 1000)
						messages.push(', ' + (elapsed / 1000).to_fixed(2)
								+ ' s');
					messages.push(elapsed === 0 ? ', 0 s elapsed.' : ', '
							+ ((elapsed = recorder.all.length / elapsed) < 1
							// Hz
							? (1000 * elapsed).to_fixed(2) + ' tests/s.'
									: elapsed.to_fixed(2) + ' tests/ms.'));
					// console.trace(messages);
					return messages.join('');
				}

				var error_count = recorder.failed.length
						+ recorder.fatal.length;
				function finish() {
					// 確保 callback 會在本函數之後執行。
					// 最後執行setTimeout(options.callback)，使options.callback在訊息顯示完之後才執行。
					// 因為已 callback，自此後不應改變 recorder，否則不會被 callback 處理。
					if (options && typeof options.callback === 'function') {
						setTimeout(function() {
							options.callback(recorder, error_count,
									test_group_name);
						}, 0);
					}
					return error_count;
				}

				if (error_count === 0) {
					// all passed. 測試通過。
					messages.push(CeL.to_SGR([
							'All ' + recorder.passed.length + ' ', 'fg=green',
							'passed', '-fg' ]));

					log_front_end_info(join());
					return finish();
				}

				// not all passed.
				messages.push(recorder.failed.length + '/'
				//
				+ (recorder.failed.length + recorder.passed.length));
				if (recorder.failed.length + recorder.passed.length !== recorder.all.length)
					messages.push('/' + recorder.all.length);
				messages.push(CeL.to_SGR([ ' ', 'fg=red', 'failed', '-fg' ]));
				if (recorder.fatal.length > 0) {
					// fatal exception error 致命錯誤
					messages.push(CeL.to_SGR([
							', ' + recorder.fatal.length + ' ',
							'fg=red;bg=white', 'fatal', '-fg;-bg' ]));
				}

				// 不採用 log_controller，在 console 會出現奇怪的著色。
				// e.g., @ Travis CI
				if (recorder.passed.length > 0) {
					// CeL.warn(join());
					log_controller[2](join());
				} else {
					// CeL.error(join());
					log_controller[3](join()
					// hack: fg=whilte → fg=red
					.replace(/\x1B\[37m/g, '\x1B[31m'));
				}

				return finish();
			}

			assert_proxy.test_group_name = test_group_name;
			assert_proxy.report = report;
			assert_proxy.options = default_options;
			assert_proxy.starts = Date.now();

			// --------------------------------
			// ready to go

			if (Array.isArray(conditions)) {
				conditions.forEach(handler);
			} else {
				var tests_count = 0,
				// assert: tests_count === Object.keys(tests_left).length
				tests_left = assert_proxy.tests_left = Object.create(null),
				// assert: typeof conditions === 'function'
				setup_test = function setup_test(sub_test_name, test_function) {
					// need wait (pending)
					assert_proxy.asynchronous = true;
					if (sub_test_name) {
						assert_proxy.latest_sub_test_name = sub_test_name;
						if (sub_test_name in tests_left) {
							CeL.warn('已登記過任務組 [' + sub_test_name + ']。');
							return true;
						}
						tests_count++;
						tests_left[sub_test_name] = {
							// sub_test_data
							assert_count : 0
						};
						CeL.debug('增加任務組 [' + sub_test_name + ']。尚餘 '
								+ tests_count + ' 個任務組測試中。', 1, 'CeL.log.test');
					}
					if (typeof test_function === 'function') {
						try {
							test_function(finish_test);
						} catch (e) {
							if (typeof console === 'object' && console.trace) {
								// Will show stacks
								console.trace(e);
							}
							recorder.fatal.push(sub_test_name);
						}
						if (sub_test_name)
							finish_test(sub_test_name);
					}
				},
				//
				finish_test = function finish_test(sub_test_name) {
					if (sub_test_name in tests_left) {
						delete tests_left[sub_test_name];
						tests_count--;
					} else {
						// 重複呼叫?
						CeL.warn('已登記過完成測試任務組 [' + sub_test_name + ']。');
						return;
					}
					CeL.debug('完成測試 [' + sub_test_name + ']。尚餘 ' + tests_count
							+ ' 個任務組測試中。', 1, 'CeL.log.test');
					// console.trace([ tests_count, tests_left ]);
					if (tests_count === 0
					// && CeL.is_empty_object(tests_left)
					) {
						if (assert_proxy.tests_loaded)
							report(sub_test_name);
						else
							delete assert_proxy.asynchronous;
					}
				}, conditions_error = function(error) {
					assert_proxy.asynchronous = false;
					handler([ [ error, "OK" ], test_group_name ]);
				};

				try {
					var result = conditions(assert_proxy, setup_test,
							finish_test, {
								test_name : test_group_name
							});
					// allow async functions
					if (CeL.is_thenable(result)) {
						result.then(function() {
							assert_proxy.tests_loaded = true;
							// CeL.log('CeL.test: All tests loaded.');
							if (tests_count > 0) {
								CeL.error('CeL.test: ' + tests_count
										+ ' sub test(s) still running: '
										+ Object.keys(tests_left));
							}
							report();
						}, function(error) {
							console.trace(error);
							conditions_error(error);
						});
						// Waiting...
						return;
					}

					assert_proxy.tests_loaded = true;
					if (tests_count > 0) {
						// console.trace([ tests_count, tests_left ]);
						CeL.debug('尚餘 ' + tests_count + ' 個任務組測試中。', 1,
								'CeL.log.test');
					}
				} catch (e) {
					// has_console
					if (typeof console === 'object' && console.error) {
						// Warning: console.error() won't show stacks @ node
						// v0.10
						// console.trace(e) will show a wrong one.
						if (e && e.stack && CeL.platform.nodejs
								&& !CeL.platform('node', 8)) {
							console.error(e.stack);
						} else {
							// Will show stacks
							console.error(e);
						}
					}
					conditions_error(e);
				}
			}

			if (!assert_proxy.asynchronous) {
				return report();
			} else {
				// Waiting...
			}
		}

		// 在 console 則沿用舊 function。
		// 這裡的判別式與 base.js 中的相符: "_.to_SGR = is_WWW ? SGR_to_plain : to_SGR;"
		// 因為 base.js 中的 styled log 也需要此條件才能發動。
		// TODO: 增加 console 的 style (color)
		if (CeL.is_WWW()) {
			// 這裡列出的是 base.js 中即已提供，不設定也會由原先之預設函式處理的函式。
			Object.assign(CeL, {
				log : log_controller[1],
				warn : log_controller[2],
				error : log_controller[3],

				info : log_front_end_info,

				debug : log_front_end_debug
			});
		}

		Object.assign(CeL, {
			em : function log_front_end_em(message, clean) {
				// emphasis
				CeL.Log.log.call(CeL.Log, message, clean, 'em');
			},

			// 致命錯誤。
			fatal : log_front_end_fatal,

			// 增加 debug 訊息。
			trace : function log_front_end_trace() {
				// 使用 .apply() 預防 override。
				// trace: the least serious
				CeL.debug.apply(CeL, arguments);
			},

			// 切換(顯示/隱藏)個別訊息。
			toggle_log : log_front_end_toggle_log,

			assert : log_front_end_assert,
			// CeL.test()
			test : log_front_end_test
		});

		// 處理 loading 本 module 前即已 log 之 message。
		if (Array.isArray(log_buffer) && log_buffer.length > 0) {
			CeL.debug({
				em : 'Before loading ' + module_name
						+ ', there are some debugging message.'
			});
			log_buffer.forEach(function(message) {
				CeL.debug(message);
			});

			CeL.debug('<em>' + module_name + ' loaded.<\/em>');
		}

	}
}
