/**
 * @name CeL function for storage.
 * @fileoverview 載入在不同執行環境與平台皆可使用的檔案操作功能公用API，以統一使用介面。
 * @since 2017/1/27
 */

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

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

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

	// 依照不同執行環境與平台載入可用的操作功能。
	require : detect_require,

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

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

function detect_require(library_namespace) {
	if (library_namespace.platform.nodejs) {
		return 'application.platform.nodejs.';
	}

	// 理想作法應該偵測JScript與COM環境。
	// @see CeL.application.OS.Windows.file
	this.has_ActiveX = typeof WScript === 'object'
			|| typeof ActiveXObject === 'function'
			|| typeof Server === 'object' && Server.CreateObject;

	if (this.has_ActiveX) {
		// TODO: application.OS.Windows.archive.
		return 'application.OS.Windows.file.';
	}

	library_namespace.error('It seems I am running on an unknown OS.');
}

function module_code(library_namespace) {

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

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

	// -------------------------------------------------------------------------
	// 維護公用API。

	/**
	 * 公用API: 有些尚未完備，需要先確認。<code>

	// set/get current working directory. 設定/取得目前工作目錄。
	// current_directory() ends with path_separator
	CeL.storage.working_directory([change_to_directory])

	CeL.storage.fso_status(fso_path)
	CeL.storage.chmod(fso_path, options)
	CeL.storage.fso_exists(file_path)
	CeL.storage.file_exists(file_path)
	// get the contents of file
	CeL.storage.read_file(file_path, character_encoding = 'UTF-8').toString()
	CeL.storage.write_file(file_path, contents, character_encoding = 'UTF-8')
	CeL.storage.append_file(file_path, contents, character_encoding = 'UTF-8')
	// alias: delete
	CeL.storage.remove_file(file_path / directory_path_list)
	// alias: rename
	CeL.storage.move_file(move_from_path, move_to_path)
	CeL.storage.copy_file(copy_from_path, copy_to_path)

	// .folder_exists()
	CeL.storage.directory_exists(directory_path)
	// get the fso list (file and sub-directory list) of the directory.
	CeL.storage.read_directory(directory_path, options)
	CeL.storage.directory_is_empty(directory_path, options)
	// alias: mkdir
	CeL.storage.create_directory(directory_path / directory_path_list)
	// alias: delete. recursive: clean directory
	CeL.storage.remove_directory(directory_path / directory_path_list, recursive)
	// alias: rename
	CeL.storage.move_directory(move_from_path, move_to_path)
	CeL.storage.copy_directory(copy_from_path, copy_to_path)

	// 遍歷檔案系統，對每個 FSO 執行指定的動作。
	// TODO: 以 data.file.file_system_structure 代替 traverse_file_system()
	CeL.storage.traverse_file_system(directory_path, handler, filter or options)

	</code>
	 */

	// main module of OS adapted functions
	var storage_module;

	if (library_namespace.platform.nodejs) {
		library_namespace.debug('application.storage: use node.js functions.');
		storage_module = library_namespace.application.platform.nodejs;

		/** node.js file system module */
		var node_fs = require('fs');

		// _.current_directory()
		_.working_directory = storage_module.working_directory;

		// 警告: 此函數之API尚未規範。
		// .file_stats()
		// Not exist: will return false.
		_.fso_status = storage_module.fs_status;

		_.chmod = storage_module.chmod;

		_.file_exists = storage_module.file_exists;
		_.directory_exists = storage_module.directory_exists;

		_.read_file = storage_module.fs_read;

		_.write_file = storage_module.fs_write;

		_.copy_file = storage_module.fs_copySync;

		_.remove_file = _.remove_directory = function(path, recursive) {
			return storage_module.fs_remove(path, recursive);
		};

		_.move_directory = _.move_file =
		//
		_.move_fso = storage_module.fs_move;

		_.read_directory = function(directory_path, options) {
			try {
				// fso_name_list
				return node_fs.readdirSync(directory_path, options);
			} catch (e) {
				library_namespace.debug('Error to read directory: '
						+ directory_path);
			}
		};

		// directory exists and is empty
		_.directory_is_empty = function(directory_path, options) {
			var fso_name_list = _.read_directory(directory_path, options);
			return Array.isArray(fso_name_list) && fso_name_list.length === 0;
		};

		_.create_directory = storage_module.fs_mkdir;

		_.traverse_file_system = storage_module.traverse_file_system;

	} else if (this.has_ActiveX) {
		storage_module = library_namespace.application.OS.Windows.file;

		_.working_directory = storage_module.working_directory;

		_.read_file = storage_module.read_file;

		_.write_file = storage_module.write_file;

		// TODO: many

		// others done @ CeL.application.OS.Windows.file

	}

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

	function write_file(file_path, data, options) {
		// options = library_namespace.new_options(options);
		if (library_namespace.is_Object(data)
		// JSON.stringify() 放在 try 外面。這樣出現 circular structure 的時候才知道要處理。
		&& /.json$/i.test(file_path)) {
			// 自動將資料轉成 string。
			try {
				data = JSON.stringify(data);
			} catch (e) {
				// data 可能很長，擺在首位。
				library_namespace.debug(data);
				library_namespace.error('write_file: Failed to write to '
						+ file_path + ': ' + e);
				library_namespace.error(e);
				throw e;
			}
		}

		if (options && options.changed_only) {
			var original_data = _.read_file(file_path, options);
			if (String(data) === String(original_data))
				return new Error('Nothing changed');
			// delete options.changed_only;
		}

		if (options && options.backup && _.file_exists(file_path)) {
			var backup_options = typeof options.backup === 'string' ? /[\\\/]/
					.test(options.backup) ? {
				directory : options.backup
			} : {
				directory_name : options.backup
			} : library_namespace.new_options(options.backup);

			if (!backup_options.directory && backup_options.directory_name) {
				// 設定備份目錄於與檔案相同的目錄下。
				backup_options.directory = file_path.replace(/[^\\\/]+$/, '')
						+ backup_options.directory_name;
			}

			var backup_file_path = backup_options.directory
			//
			? append_path_separator(backup_options.directory,
			// get file name only
			file_path.match(/[^\\\/]+$/)[0])
			// append file name extension
			: backup_options.extension ? file_path + backup_options.extension
			// preserve original file name extension
			: file_path.replace(/(\.\w+)$/,
					(backup_options.file_name_mark || '.bak') + '$1');

			// Create backup
			if (backup_options.directory)
				_.create_directory(backup_options.directory);
			_.remove_file(backup_file_path);
			_.move_file(file_path, backup_file_path);
		}

		return _.write_file__OS_adapted(file_path, data, options);
	}

	if (_.write_file) {
		_.write_file__OS_adapted = _.write_file;
		_.write_file = write_file;
	}

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

	get_not_exist_filename.PATTERN = /( )?(?:\((\d{1,3})\))?(\.[^.]*)?$/;
	get_not_exist_filename.max_index = 999;

	// 找到下一個可用的檔案名稱。若是有重複的檔案存在，則會找到下一個沒有使用的編號為止。
	// recheck: 從頭檢查起。否則接續之前的序號檢查。
	// CeL.next_fso_NO_unused("n (2).txt") 先檢查 "n (2).txt", "n (3).txt"，
	// CeL.next_fso_NO_unused("n (2).txt", true) 先檢查 "n.txt", "n (1).txt"
	function get_not_exist_filename(move_to_path, recheck) {
		if (recheck) {
			move_to_path = move_to_path.replace(get_not_exist_filename.PATTERN,
			//
			function(all, prefix_space, index, extension) {
				return extension || '';
			});
		}
		while (_.fso_status(move_to_path)) {
			move_to_path = move_to_path.replace(
			// Get next index that can use.
			get_not_exist_filename.PATTERN, function(all, prefix_space, index,
					extension) {
				if (index > get_not_exist_filename.max_index) {
					throw new Error('get_not_exist_filename: The index '
							+ index + ' is too big! ' + move_to_path);
				}
				return (prefix_space || !index ? ' ' : '') + '('
						+ ((index | 0) + 1) + ')' + (extension || '');
			});
		}
		return move_to_path;
	}
	_.next_fso_NO_unused = get_not_exist_filename;
	_.move_fso_with_NO = function(from_path, to_path) {
		to_path = get_not_exist_filename(to_path);
		if (false)
			library_namespace.info(library_namespace.display_align([
					[ 'Move: ', from_path ], [ '→ ', move_to_path ] ]));
		_.move_fso(from_path, to_path);
	};

	// 從一個目錄或檔案列表中，找出第一個存在的。
	_.first_exist_fso = function(fso_list) {
		var first_exist;
		if (!Array.isArray(fso_list)) {
			fso_list = [ fso_list ];
		}
		if (fso_list.some(function(fso) {
			if (_.fso_status(fso)) {
				first_exist = fso;
				return true;
			}
		})) {
			return first_exist;
		}
	};

	// -------------------------------------------------------------------------
	// 一些與平台無關，且常用的檔案操作函數。或者簡單並且依賴於上面所列出操作的函數。
	// platform-independent model (PIM)
	// https://en.wikipedia.org/wiki/Platform-independent_model

	var path_separator = library_namespace.env.path_separator;

	// join path 自動加上最後的路徑分隔符號/目錄分隔符號。
	function append_path_separator(directory_path, file_name) {
		if (!directory_path) {
			return directory_path;
		}

		// assert: typeof directory_path === 'string'

		// 正規化成當前作業系統使用的路徑分隔符號。
		if (false) {
			directory_path = directory_path.replace(/[\\\/]/g, path_separator);
		}

		if (!/[\\\/]$/.test(directory_path)) {
			directory_path +=
			// 所添加的路徑分隔符號，以路徑本身的路徑分隔符號為主。
			directory_path.includes('/') ? '/'
			//
			: directory_path.includes('\\')
			// e.g., 'C:'
			|| directory_path.endsWith(':') ? '\\'
			//
			: path_separator;

		} else {
			// 去除末尾太多個、不需要也不正規的路徑分隔符號。
			directory_path = directory_path.replace(/[\\\/]{2,}$/, function(
					path_separators) {
				return path_separators[0];
			});
		}

		// library_namespace.simplify_path()
		if (file_name || file_name === 0)
			file_name = String(file_name).replace(/^(\.{0,2}[\\\/])+/, '');
		return file_name ? directory_path + file_name : directory_path;
	}

	_.append_path_separator = append_path_separator;

	function extract_wildcard(pattern, options) {
		var matched = library_namespace.simplify_path(pattern).match(
				/^([\s\S]*[\\\/])?([^\\\/]+)$/);
		if (!matched)
			return [ pattern ];

		var directory = matched[1] || '.' + path_separator;
		matched = matched[2];
		pattern = library_namespace.wildcard_to_RegExp(matched);
		// console.trace(pattern);

		var fso_list = library_namespace.read_directory(directory);
		// console.trace(fso_list);
		fso_list = fso_list.filter(function(fso_name) {
			// console.trace([ fso_name, pattern, pattern.test(fso_name) ]);
			return pattern.test(fso_name)
			// e.g., 包含 "[", "]" 時。
			|| fso_name === matched;
		});
		// console.trace(fso_list);

		if (!options || !options.get_name) {
			fso_list = fso_list.map(function(fso_name) {
				return directory + fso_name;
			});
		}
		return fso_list;
	}

	_.extract_wildcard = extract_wildcard;

	// 決定預設的主要下載目錄。
	// macOS dmg APP 中無法將檔案儲存在APP目錄下。
	// 另外安裝包也比較適合放在 home directory 之下。
	// test_current_directory: 先嘗試下載於當前目錄下。
	function determin_download_directory(test_current_directory) {
		var download_directory = test_current_directory
				&& library_namespace.platform.nodejs && require.main
				&& require.main.filename;
		if (download_directory
		// macOS dmg electron APP 中: require.main.filename 例如為
		// /Applications/work_crawler.app/Contents/Resources/app.asar/gui_electron/gui_electron.html
		//
		// Linux Mint 中 AppImage: require.main.filename 例如為
		// /tmp/.mount_work_cWtD4AY/resources/app.asar/gui_electron/gui_electron.html
		//
		// Linux Mint 中 electron: require.main.filename 例如為
		// .../work_crawler-master/gui_electron/gui_electron.html
		//
		// Windows 10 中 electron: require.main.filename 例如為
		// ...\work_crawler\gui_electron\gui_electron.html
		//
		// 2021/4/20 11:36:5 require.main===undefined @ new electron-builder
		// package
		// may use `module.filename`
		// in electron-builder package: e.g.,
		// "C:\Users\user_name\AppData\Local\Programs\work_crawler\resources\app.asar\gui_electron\gui_electron.html"
// NOT in electron-builder package: e.g.,
		// "/program/work_crawler/gui_electron/gui_electron.html"
		&& !/\.html?$/i.test(download_directory)) {
			download_directory = download_directory.match(/[^\\\/]+$/)[0]
					.replace(/\.js$/i, '');

		} else if (test_current_directory && (download_directory =
		// 避免 "/". e.g., macOS dmg APP 中 process.cwd() === '/'
		_.working_directory().replace(/[\\\/]+$/, ''))) {
			;

		} else if (download_directory = library_namespace.env.home) {
			if ([ 'Downloads', '下載' ]
			// '下載': Linux Mint
			.some(function(user_download_directory) {
				user_download_directory = append_path_separator(
						download_directory, user_download_directory);
				if (_.directory_exists(user_download_directory)) {
					download_directory = user_download_directory;
					return true;
				}
			})) {
				library_namespace.debug('預設的主要下載目錄設置於用戶預設之下載目錄下: '
						+ download_directory, 1, 'determin_download_directory');

			} else {
				library_namespace.debug(
				// 家目錄 @see os.userInfo().homedir , os.homedir()
				'預設的主要下載目錄設置於用戶個人文件夾  home directory 下: ' + download_directory,
						1, 'determin_download_directory');
			}

		} else {
			// 應該不會到這邊來。
			library_namespace
					.warn('determin_download_directory: Cannot determin main download directory!');
			download_directory = '.';
		}

		// main_directory 必須以 path separator 作結。
		download_directory = append_path_separator(download_directory);
		library_namespace.debug('預設的主要下載目錄: ' + download_directory, 1,
				'determin_download_directory');
		return download_directory;
	}

	_.determin_download_directory = determin_download_directory;

	return (_// JSDT:_module_
	);
}
