/**
 * @name CeL function for thread
 * @fileoverview 本檔案包含了 thread / process 流程控制的 functions。
 * @since 2012/2/3 19:13:49
 */

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

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

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

	require : 'data.code.compatibility.',

	// 設定不匯出的子函式。
	// 完全不 export 至 library_namespace.
	no_extend : '*',

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

function module_code(library_namespace) {

	/**
	 * null module constructor
	 * 
	 * @class thread 的 functions
	 */
	var _// JSDT:_module_
	= function() {
		// null module constructor
	};

	/**
	 * for JSDT: 有 prototype 才會將之當作 Class
	 */
	_// JSDT:_module_
	.prototype = {
	// constructor : _
	};

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

	var
	/**
	 * const, 不可能為 setTimeout() id.
	 */
	is_running = [ 'is running' ],
	/**
	 * const, 紀錄 process hook。
	 */
	Serial_execute_process = {},
	/**
	 * const, Serial_execute core data 中，可被變更的值。
	 */
	Serial_execute_allow_to_set = {
		interval : '{Integer}設定執行之週期間隔(ms)',

		thread : '{Function}設定每次 loop 所欲執行之執行緒 (handler thread)。',

		// 傳給 handler thread 之 this 與 arguments。
		'this' : '在各 thread 間當作 this 傳遞的 data. "this" argument send to thread.',
		argument : 'argument : 傳給 handler 之 arguments',

		// loop 序號控制。單純輸入數字相當於 {start : 1, length : 自然數序號}
		index : '{Integer}index : process to 哪一序號',
		start : '{Integer}start from 哪一序號(index)',
		// length, last 二選一。
		length : '{Integer}執行 length 次',
		last : '{Integer}執行至哪一序號(end index)',

		// 設定流程控制用 signals。
		stopped : '{Boolean}process stopped',
		finished : '{Boolean}process finished',
		terminated : '{Boolean}process terminated',

		// 設定當 handler 產生錯誤時，是否繼續執行下去。Or stop on throw error of thread.
		skip_throw : '{Boolean}skip throw of thread'
	};

	/**
	 * 設定循序執行(serial execution) 程序，並可以作 stop, resume 等流程控制 (inter-process
	 * communication)。<br />
	 * 本函數可代替迴圈操作 loop, for, setTimeout / setInterval，亦可避免長時間之迴圈操作被 browser
	 * 判別為耗時 loop 而 hang 住。<br />
	 * 可視作一種 iterator / forEach()。<br />
	 * This module use asynchronous method (e.g., setTimeout) to avoid the
	 * "Script Taking Too Long" message.
	 * 
	 * TODO:<br />
	 * .result[], .next() = .step(), input array as option
	 * 
	 * <code>
	// 單 thread
	var i=0,s=0;for(;i<100;i++)s+=Math.random();alert(s);
	
	CeL.run('data.code.thread');
	// 方法1
	new CeL.Serial_execute(function(i, d) {d.s+=Math.random();}, {length: 100, first: function(d) {d.s=0;}, final: function(i, d) {alert(d.s);}});
	// 方法2
	new CeL.Serial_execute(function() {this.s+=Math.random();}, {length: 100, first: function() {this.s=0;}, final: function() {alert(this.s);}});
	 * </code>
	 * 
	 * @param {Function}loop_thread
	 *            loop_thread({Integer}process_to_index) {<br />
	 *            return<br />
	 *            'SIGABRT': terminated (accident occurred), won't run
	 *            options.final();<br />
	 *            others(!0): all done<br /> }
	 * 
	 * @param {Object}[options]
	 *            設定選項。<br /> {<br />
	 *            {String}id : process id (有設定 id 則可以從
	 *            Serial_execute.process(id) 控制。),<br />
	 *            <br />
	 *            {Integer}start : start from 哪一序號(index),<br />
	 *            {Integer}index : process to 哪一序號,<br /> // length, last 二選一。<br />
	 *            {Integer}length : 執行 length 次,<br />
	 *            {Integer}last : 執行至哪一序號(end index),<br />
	 *            <br />
	 *            argument : 傳給 handler 之 arguments,<br />
	 *            {Integer}interval : 週期間隔(ms),<br />
	 *            {Function}first : run first,<br />
	 *            {Function}final : run after all, 結尾, epilogue.<br />
	 *            {Boolean}skip_throw : skip throw of thread(),<br /> }
	 * 
	 * @returns {Serial_execute}process handler
	 * 
	 * @constructor
	 * 
	 * @see http://wiki.ecmascript.org/doku.php?id=strawman:async_functions
	 *      http://support.mozilla.org/en-US/kb/warning-unresponsive-script
	 *      http://support.microsoft.com/kb/175500
	 * 
	 * @since 2012/2/3 18:38:02 初成。<br />
	 *        2012/2/4 12:31:53 包裝成物件。<br />
	 *        2012/11/16 19:30:53 re-write start。<br />
	 *        2012/11/23 23:51:44 re-write finished。<br />
	 *        2013/3/3 19:20:52 重新定義 options.length。<br />
	 */
	function Serial_execute(loop_thread, options) {
		if (typeof loop_thread !== 'function')
			return;

		var tmp,
		// (private) 行程間核心 data.
		core_data = {
			start_time : new Date,
			// interval : 0,
			thread : loop_thread,
			skip_throw : false,
			count : 0
		};

		// public interface.
		// 處理初始化必要，且不允許被 loop_thread 改變的 methods/設定/狀態值.
		this.get = function(name) {
			if (name in core_data)
				return core_data[name];
		};
		this.set = function(name, value) {
			if (name in Serial_execute_allow_to_set) {
				if (arguments.length > 1)
					return core_data[name] = value;
				else
					delete core_data[name];
			}
		};

		if (library_namespace.is_digits(options))
			// 當作執行次數。
			options = {
				length : options
			};
		if (Array.isArray(options))
			// 當作 Array.prototype.forEach()
			options = {
				list : options
			};

		// 處理 options 中與執行相關，且不允許被 loop_thread 改變的設定。
		if (library_namespace.is_Object(options)) {
			// 將 options 之 digits 設定 copy 到 core_data。
			library_namespace.set_method(core_data, options, [ function(key) {
				return !library_namespace.is_digits(options[key]);
			}, 'start', 'last', 'index', 'interval' ]);
			if (false)
				library_namespace.extend(
						[ 'start', 'last', 'index', 'interval' ], core_data,
						options, function(i, v) {
							return !library_namespace.is_digits(v);
						});

			if (!('last' in core_data) && options.length > 0) {
				if (!library_namespace.is_digits(core_data.start))
					core_data.start = 1;
				core_data.last = core_data.start + options.length - 1;
			}
			// 從這邊起，.last 表示結束之序號。

			// 將 options 之 digits 設定 copy 到 core_data。
			library_namespace.set_method(core_data, options, [ 'skip_throw',
					'final', 'this' ]);
			if (false)
				library_namespace.extend([ 'skip_throw', 'final', 'this' ],
						core_data, options);

			if ('argument' in options)
				core_data.argument = Array.isArray(tmp = options.argument) ? tmp
						: [ tmp ];

			// data list.
			if ('list' in options)
				try {
					// check if list is an array-like object, we can use []
					// operator and .length.
					var last = options.list.length - 1, test = options.list[last];
					if (!library_namespace.is_digits(last))
						// 確認 test 會被演算。
						throw '' + test;

					// 這邊不作 Array.prototype.slice.call()，讓 caller 可再作更動。
					// 若希望保留 const，caller 需要自己作 Array.prototype.slice.call()。
					core_data.list = options.list;

					if (!library_namespace.is_digits(options.start))
						// 若尚未設定 .start，則定為 0。
						core_data.start = 0;

					if (!library_namespace.is_digits(core_data.last))
						core_data.last = last;

					// add an item to list.
					this.add = function(item) {
						// 為確保為 generic method，不用 .push()!
						core_data.list[++core_data.last] = item;
					};
					// 取得/設定當前 index。
					this.index = function(index) {
						if (library_namespace.is_digits(index))
							core_data.index = index;
						else
							return core_data.index;
					};

				} catch (e) {
					if (library_namespace.is_Object(options.list)) {
						// TODO
					}
				}

			if (options.record) {
				// 執行結果將會依序置於此 Array。
				core_data.result = [];
			}

			// 登記 process id.
			if (tmp = options.id) {
				if (tmp in Serial_execute_process)
					library_namespace.debug('已有相同 id (' + tmp
							+ ') 之 process 執行中!');
				else
					// 作個登記。
					Serial_execute_process[core_data.id = tmp] = this;
			}

		} else
			// 還是給予個預設值，省略判斷，簡化流程。
			options = {};

		// 外包裹執行緒: 可寫在 prototype 中。
		this.package_thread = Serial_execute_package_thread.bind(this,
				core_data);

		// 既然首尾都設定了，自動設定 index。
		if (!library_namespace.is_digits(core_data.index)
				&& library_namespace.is_digits(core_data.start)
				&& library_namespace.is_digits(core_data.last))
			// start from 哪一序號。
			core_data.index = core_data.start;

		// 必須先執行之程序。
		tmp = options.first;
		if (typeof tmp === 'function')
			if (core_data.argument)
				tmp.apply(core_data['this'] || this, core_data.argument);
			else
				tmp.call(core_data['this'] || this, core_data);

		// 預設自動開始執行。
		if (!('autostart' in options) || options.autostart)
			setTimeout(this.package_thread, 0);
	}

	/**
	 * 取得指定 id 之控制程序。
	 */
	Serial_execute.process = function(id) {
		if (id in Serial_execute_process)
			return Serial_execute_process[id];
	};

	/**
	 * signal 定義。
	 * 
	 * @see <a href="http://en.wikipedia.org/wiki/Unix_signal"
	 *      accessdate="2012/2/4 15:35">Unix signal</a>
	 */
	Serial_execute.signal = {
		// running : 0,
		STOP : 1,
		// 結束程序。
		FINISH : 2,
		// abort
		TERMINATE : 3
	};

	/**
	 * Serial_execute controller.
	 * 
	 * @param signal
	 * @param result
	 * @returns {Serial_execute_controller}
	 */
	function Serial_execute_controller(signal, result) {
		this.signal = signal;
		if (arguments.length > 1)
			this.result = result;
	}

	Serial_execute.controller = Serial_execute_controller;

	// private: 預設外包裹執行緒。iterator.
	function Serial_execute_package_thread(data, force) {

		if (data.stopped) {
			library_namespace.debug('執行緒已被中止。欲執行請先解除暫停設置。', 1,
					'Serial_execute_package_thread');
			return;
		}

		if (data.timer_id !== undefined) {
			if (data.timer_id === is_running) {
				library_namespace.debug('執行緒正執行中，忽略本次執行要求。', 1,
						'Serial_execute_package_thread');
				return;
			}
			clearTimeout(data.timer_id);
		}
		// lock
		data.timer_id = is_running;

		var result, list = data.list, to_terminate = data.terminated, argument_array,
		// 設定是否已結束。
		to_finish = to_terminate || data.finished || data.index > data.last,
		// debug 用
		id_tag = 'process [' + data.id + '] @ ' + data.index + ' / '
				+ data.start + '-' + data.last;

		if (!to_finish) {
			// 已執行幾次。在 thread 中為從 1，而非從 0 開始！
			data.count++;
			library_namespace.debug('實際執行 loop thread()。', 2,
					'Serial_execute_package_thread');

			if (list)
				list = [ list[data.index], data.index, list ];

			if (data.argument) {
				// data.argument 應為 Array。
				argument_array = data.argument;
				if (list)
					// 不動到原先的 data.argument。
					// 將 data.argument 當作最前面的 arguments，之後才填入 list 的部分。
					argument_array = argument_array.concat(list);
			} else if (list)
				// 當作 Array.prototype.forEach()
				argument_array = list;

			try {
				result = argument_array ?
				//
				data.thread.apply(data['this'] || this, argument_array) :
				//
				data.thread.call(data['this'] || this, data.index, data.count);
				library_namespace.debug('loop thread() 程序執行完畢。', 2,
						'Serial_execute_package_thread');
				if (data.result)
					data.result.push(result);

			} catch (e) {
				if (e.constructor === Serial_execute_controller) {
					// signal cache
					var signal = Serial_execute.signal;
					switch (e.signal) {
					case signal.STOP:
						library_namespace.debug('Stop ' + id_tag, 1,
								'Serial_execute_package_thread');
						data.stopped = true;
						break;
					case signal.TERMINATE:
						library_namespace.debug('Terminate ' + id_tag, 1,
								'Serial_execute_package_thread');
						to_terminate = true;
						// terminate 的同時，也設定 to_finish。
					case signal.FINISH:
						to_finish = true;
						break;
					default:
						// ignore others.
						break;
					}

					result = e.result;
					if (data.result)
						data.result.push(result);
				} else {
					library_namespace.warn(id_tag + ' failed.');
					library_namespace.error(e);
					if (!data.skip_throw)
						data.stopped = true;
				}
			}
		}

		data.index++;

		if (to_finish) {
			library_namespace.debug('執行收尾/收拾工作。', 1,
					'Serial_execute_package_thread');
			if (!to_terminate && typeof data['final'] === 'function')
				try {
					argument_array = [ data.result || data.count ];
					data['final'].apply(data['this'] || this,
							data.argument ? data.argument
									.concat(argument_array) : argument_array);
				} catch (e) {
					library_namespace.error(e);
				}

			if (data.id in Serial_execute_process)
				delete Serial_execute_process[data.id];

			data.stopped = data.finished = true;
			// TODO: delete all elements in this.
		} else if (data.stopped)
			delete data.timer_id;
		else {
			data.timer_id = setTimeout(this.package_thread, data.interval | 0);
		}

		return result;
	}
	;

	library_namespace.set_method(Serial_execute.prototype, {

		// 行程控制。
		// run, continue, resume
		start : function() {
			this.set('stopped', false);
			library_namespace.debug('Resume [' + this.get('id') + ']');
			return this.package_thread();
		},

		// pause. 中止/停止執行緒。
		stop : function() {
			this.set('stopped', true);
		},

		// next one, step, moveNext.
		next : function() {
			var result = this.start();
			this.stop();
			return result;
		},

		// set position = start.
		rewind : function() {
			this.set('index', this.get('start') || 0);
		},

		// 結束程序。
		finish : function() {
			// gettext_config:{"id":"finished"}
			this.set('finished', true);
			this.set('stopped', false);
			// return this.package_thread();
		},

		// abort (abnormal termination), remove.
		terminate : function() {
			this.set('terminated', true);
			this.set('stopped', false);
			// return this.package_thread();
		},

		// -----------------------------------------------------------------------------------------------------
		// status / property

		// 每隔多少 ms 執行一次。
		interval : function(interval_ms) {
			if (library_namespace.is_digits(interval_ms))
				this.set('interval', interval_ms);
		},

		finished : function() {
			// gettext_config:{"id":"finished"}
			return this.get('finished');
		},

		stopped : function() {
			return this.get('stopped');
		},

		argument : function() {
			if (arguments.length)
				this.set('argument', arguments.length > 1
				//
				? Array.prototype.slice.call(arguments)
				//
				: Array.isArray(argument) ? argument : [ argument ]);

			return this.get('argument');
		},

		length : function() {
			return this.get('length');
		}

	});

	_.Serial_execute = Serial_execute;

	/**
	 * <code>

	// testing for data.code.thread
	
	// adding 0 to 100.
	CeL.run('data.code.thread', function() {
		if (typeof runCode === 'object')
			runCode.setR = 0;
		p = new CeL.Serial_execute(function(i) {
			CeL.debug(this.sum += i);
		}, {
			// id : 't',
			interval : 800,
			length : 100,
			first : function() {
				this.sum = 0;
				CeL.log('Setuped.');
			},
			final : function(i) {
				CeL.log('Done @ ' + i);
			}
		});
		// p.stop();
	});
	// p.next();
	// p.terminate();
	// CeL.log(p);
	
	// adding 0 to 100.
	CeL.Serial_execute(function(data) {
		data[1] += ++data[0];
		CeL.debug(data[0]);
	}, {
		argument : [[ 0, 0 ]],
		length : 100,
		final : function(data) {
			CeL.log('done @ ' + data[0] + ' : ' + data[1]);
		}
	});
	
	//	run 100 times: 0~99.
	new CeL.Serial_execute(function(i) {
		CeL.log(i + ': ' + (this.s = (this.s || 0) + i));
	}, 100);
	
	new CeL.Serial_execute(function(i) {
		this.s = (this.s || 0) + i;
		if (i === 100) {
			CeL.log(i + ':' + this.s);
			this.finish();
		}
	});
	
	
	
	
	// 當作 Array.prototype.forEach()
	
	CeL.run('data.code.thread');
	new CeL.Serial_execute(function(item, index) {
		CeL.log(item);
	}, [2, 1, 3, 6]);


	</code>
	 */

	// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
	return (_// JSDT:_module_
	);
}
