#!/usr/bin/python
# -*- coding: utf-8 -*-

#Version: 1.6.6
# * Python: x >= 3.7

import site
import sys, io
sys.dont_write_bytecode = True
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")

import glob, os, hashlib, datetime, json, re, random, gc, time
import concurrent.futures
from config import CONST

def format_timedelta(total_sec): #経過秒数を日時形式にする関数
	days, rem = divmod(total_sec, 86400)  # 1日 = 86400秒
	hours, rem = divmod(rem, 3600) #１時間 = 3600秒
	minutes, rem = divmod(rem, 60) #１分 = 60秒
	seconds = int(rem)
	milliseconds = int(round((rem - seconds) * 1000))  # ミリ秒
	return "{0}day {1:02d}:{2:02d}:{3:02d}.{4:03d}".format(
		int(days), int(hours), int(minutes), seconds, milliseconds
	)

class Printer:
	def __init__(self, print_code_list):
		self.save_list, self.buffer = [], []
		self.print_code_list = print_code_list
	def print(self, *msg, sep=" ", end="\n", code=None, buffer_mode=False, fresh=False):
		self.save_list.append((sep.join(msg), code))
		if self.print_code_list is None or code is None or code in self.print_code_list:
			if buffer_mode:
				self.buffer.append(sep.join(msg)+end)
			else:
				#print(sep.join(msg)+end, end="")
				sys.stdout.write(sep.join(msg)+end)
				self.fresh() #更新
	def print_buffer(self):
		if len(self.buffer) > 0:
			#print("".join(self.buffer), end="")
			sys.stdout.write("".join(self.buffer))
			self.fresh() #更新
		self.buffer = list()
	def fresh(self):
		sys.stdout.flush()
	def dump(self, code_list=None):
		return [s[0] for s in self.save_list if code_list is None or s[1] in code_list]
	def get(self):
		return self.save_list
	def extend(self, extend_save_list):
		self.save_list.extend(extend_save_list)

def sort_path_list(path_list, char_for_split="/"): #パスのリストをソートする関数
	buffer = [str(p).split(str(char_for_split)) for p in list(path_list)]
	buffer = sorted(buffer)
	return [str(char_for_split).join(b) for b in buffer]

def split_value(value, limit): #数値分割関数
	result, buffer = list(), int(value)
	if limit is None:
		result = [value]
	else:
		while buffer > 0:
			chunk = min(limit, buffer)
			result.append(chunk)
			buffer -= chunk
	return result


class GetHashWorker: #ラッピングクラス
	@classmethod
	def get_file_hash(
		self, target_data, print_mode=CONST.PRINT_MODE, read_byte=CONST.READ_BYTE, 
		read_first_flag=CONST.READ_FIRST_FLAG, read_last_flag=CONST.READ_LAST_FLAG,
		buffer_size=1024, add_first_message=None, worker_name=None
	): #ハッシュ値取得関数
		printer = Printer(print_code_list=[0, 1] if print_mode else [])
		json_dict, ban_error_flag, error_message_list = dict(), False, list()
		for td_raw in target_data:
			if td_raw is None or type(td_raw) not in (list, tuple) or len(td_raw) != 2:
				ban_error_flag = True
				raise Exception("This is logic error. (td_raw= {0})".format(td_raw))
			else:
				td_title, td_path = td_raw
				mas_m = "{0}[ ]: {2}{1}".format(" "*(0 if add_first_message is None else 4), td_title, "" if worker_name is None else "({0}): ".format(worker_name))
				printer.print(mas_m, code=0, buffer_mode=True)
				file_size, hash_object = None, None
				try:
					if td_title in json_dict.keys():
						ban_error_flag = True
						raise ValueError("This path has already been taken. ({0})".format(td_title))
					hash_object = hashlib.sha512()
					with open(td_path, "rb") as fr:
						#ファイルサイズ取得
						fr.seek(0, os.SEEK_END)
						file_size = fr.tell()
						#ファイルハッシュ取得
						fr.seek(0, os.SEEK_SET)
						if read_byte is None or (not(read_first_flag) and not(read_last_flag)):
							while True:
								chunk = fr.read(buffer_size)
								if not(chunk):
									break
								hash_object.update(chunk)
						elif type(read_byte) == int and read_byte > 0:
							pointer = 0
							if read_first_flag:
								for read_byte_buffer in split_value(value=read_byte, limit=buffer_size):
									chunk = fr.read(read_byte_buffer)
									if not(chunk):
										break
									hash_object.update(chunk)
									pointer += len(chunk)
							if read_last_flag and (pointer == read_byte or not(read_first_flag)):
								seek_number = read_byte if read_byte * 2 < file_size else file_size - read_byte
								seek_number = file_size if seek_number > file_size else seek_number
								fr.seek(-seek_number, os.SEEK_END)
								for read_byte_buffer in split_value(value=seek_number, limit=buffer_size):
									chunk = fr.read(read_byte_buffer)
									if not(chunk):
										break
									hash_object.update(chunk)
					hash_object.update(str(file_size).encode()) #サイズで更新
					json_dict[td_title] = hash_object.hexdigest()
				except Exception as e:
					mas = "{0}[E]: {1}".format(" "*(4 if add_first_message is None else 8), "[{0}]: {1}".format(type(e).__name__, str(e)))
					if isinstance(e, PermissionError):
						msg = "{0}[E]: {1}".format(
							" "*(4 if add_first_message is None else 8), 
							"The file cannot be access. It may lack sufficient permissions."
						)
					elif isinstance(e, FileNotFoundError):
						msg = "{0}[E]: {1}".format(
							" "*(4 if add_first_message is None else 8), 
							"The file cannot be found. It may have been deleted or moved recently."
						)
					printer.print(mas, code=0, buffer_mode=True)
					error_message_list.extend([mas_m, mas] if add_first_message is None else [add_first_message, mas_m, mas])
					json_dict[td_title] = None
					if ban_error_flag:
						raise
				finally:
					printer.print_buffer()
					del hash_object
					gc.collect() #未参照オブジェクトを即座に解放
		return (json_dict, ban_error_flag, error_message_list, printer.get(), worker_name)

class PathConverter: #パス変換クラス
	def __init__(self, abspath_mode=True, current_path=None, format_separator_mode=True, normpath_mode=True): #初期化関数
		self.abspath_mode = abspath_mode
		self.format_separator_mode = format_separator_mode
		self.current_path = os.path.dirname(os.path.abspath(__file__)) if current_path is None else os.path.abspath(str(current_path)) #相対パスモードでカレントパスが不明な場合は自分のディレクトリを起点とする
		self.normpath_mode = normpath_mode
	def format(self, path): #人間の目にも理解しやすいようにパスを変換する関数
		buffer = os.path.normpath(str(path)) if self.normpath_mode else str(path)
		buffer = self.abs(path) if self.abspath_mode else self.rel(path)
		return str(buffer).replace(os.path.sep, "/") if self.format_separator_mode else buffer
	def abs(self, path, force_flag=False): #絶対パスを取得する関数
		return str(path) if os.path.isabs(str(path)) and not(force_flag) else os.path.normpath(os.path.join(self.current_path, str(path))) 
	def rel(self, path, force_flag=False): #相対パスを取得する関数
		return os.path.relpath(str(path), start=self.current_path) if force_flag or os.path.isabs(str(path)) else str(path)
	def norm(self, path): #正規化する関数
		return os.path.normpath(str(path))
	def normp(self, path): #正規化する関数＆ＯＳ共通対応
		return self.norm(path).replace(os.path.sep, "/")

class RegexTester: #パス正規表現テストクラス
	def __init__(self, pattern, default=False): #初期化関数
		self.pattern = None if pattern is None else str(pattern)
		self.free_mode = pattern is None
		self.default = default
		self.compiler = None
		self.error = None
		try:
			self.compiler = re.compile(str(pattern))
		except re.error as e:
			self.free_mode = True
			self.error = e
	def test(self, target): #合致判定関数
		if self.free_mode:
			return self.default
		return self.compiler.fullmatch(str(target)) is not None
	def get_error(self): #エラー取得関数
		return self.error
	def is_error(self): #エラーフラグ取得関数
		return self.error is not None
	def is_free(self): #フリーフラグ取得関数
		return self.free_mode
	def get_pattern(self): #正規表現パターン取得関数
		return self.pattern

def check_input(input_value, match_value="save", any_mode=True): #多モード合致確認関数
	return_flag = True
	if match_value is None or len(match_value) < 1:
		return True
	if input_value is None or len(input_value) < 1:
		return False
	if any_mode:
		for s1, s2 in zip(list(str(input_value)), list(str(match_value))):
			if not(s1.upper() == s2.upper()):
				return_flag = False
				break
	elif str(input_value).upper() != str(match_value).upper():
		return_flag = False
	return return_flag

def check_open_wmode(pass_flag, message, path): #ファイルの書き込みロック判定関数
	if not(pass_flag):
		return (None, "Nothing.")
	if not(os.path.isfile(path)):
		return (True, "Not exist '{0}' file.".format(message))
	flag = (True, "Check readying '{0}' file succeeded.".format(message))
	try:
		os.makedirs(os.path.dirname(path), exist_ok=True)
		with open(path, mode="w", encoding="utf-8-sig", newline="") as f:
			f.write("出力検証モジュールによる書き込み")
	except OSError as e:
		flag = (False, "Check readying '{0}' file failed.".format(message))
	return flag

def memorize(argv=[]):
	#sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8-sig")
	#基本準備処理
	sub_strings = " ({0})".format(" ".join([str(s) for s in list(argv[2:])])) if len(argv) > 1 else ""
	printer = Printer(print_code_list=[0, 1] if CONST.PRINT_MODE else list())
	save_flag = check_input(
		input_value=str(sys.argv[1]) if len(sys.argv) > 1 else None, 
		match_value=CONST.ARGS_MATCH_TEXT, 
		any_mode=CONST.ARGS_ANY_MODE
	) #記録処理の記録有無指定
	pcf_s = PathConverter(
		abspath_mode=CONST.ABSPATH_MODE, 
		current_path=None,
		format_separator_mode=True, 
		normpath_mode=True
	)
	pcf_r = PathConverter(
		abspath_mode=CONST.ABSPATH_MODE, 
		current_path=pcf_s.abs(CONST.ROOT_PATH),
		format_separator_mode=True, 
		normpath_mode=True
	)
	#設定集
	save_option_raw = {
        "SCRIPT_PATH"                  : pcf_s.normp(pcf_s.rel(os.path.dirname(os.path.abspath(__file__)))),
        "ROOT_PATH"                    : pcf_s.normp(CONST.ROOT_PATH), 
        "SAVE_REMEMBER_FILE_NAME"      : CONST.SAVE_REMEMBER_FILE_NAME,
        "READ_BYTE"                    : CONST.READ_BYTE, 
        "READ_FIRST_FLAG"              : CONST.READ_FIRST_FLAG, 
        "READ_LAST_FLAG"               : CONST.READ_LAST_FLAG,
        "EXCEPT_PATH_LIST"             : tuple([pcf_r.normp(l) for l in list(CONST.EXCEPT_PATH_LIST)]),
        "NOT_EXCEPT_PATH_LIST"         : tuple([pcf_r.normp(l) for l in list(CONST.NOT_EXCEPT_PATH_LIST)]),
        "EXCEPT_PATH_LIST_FSD"         : tuple([pcf_r.normp(l) for l in list(CONST.EXCEPT_PATH_LIST_FROM_SCRIPTS_DIRECTORY)]),
        "NOT_EXCEPT_PATH_LIST_FSD"     : tuple([pcf_r.normp(l) for l in list(CONST.NOT_EXCEPT_PATH_LIST_FROM_SCRIPTS_DIRECTORY)]),
        "IGNORE_CHANGE_PATH_LIST"      : tuple([pcf_r.normp(l) for l in list(CONST.IGNORE_CHANGE_PATH_LIST)]),
        "NOT_IGNORE_CHANGE_PATH_LIST"  : tuple([pcf_r.normp(l) for l in list(CONST.NOT_IGNORE_CHANGE_PATH_LIST)]),
        "DIRECTORY_FOR_SAVE_FILE_PATH" : pcf_s.normp(CONST.DIRECTORY_FOR_SAVE_FILE_PATH),
        "ABSPATH_MODE"                 : CONST.ABSPATH_MODE,
        "INCLUDE_PATH_REGEX_PATTERN"   : CONST.INCLUDE_PATH_REGEX_PATTERN,
        "IGNORE_PATH_REGEX_PATTERN"    : CONST.IGNORE_PATH_REGEX_PATTERN,
	}
	save_option = hashlib.sha512(str(sorted(save_option_raw.items())).encode()).hexdigest()
	start_datetime = datetime.datetime.now()
	#付与パスの構成
	except_path_list = [pcf_r.normp(pcf_r.abs(p, force_flag=True)) if len(p) > 0 else pcf_r.normp(pcf_r.abs(".", force_flag=True)) for p in CONST.EXCEPT_PATH_LIST]
	not_except_path_list = [pcf_r.normp(pcf_r.abs(p, force_flag=True)) if len(p) > 0 else pcf_r.normp(pcf_r.abs(".", force_flag=True)) for p in CONST.NOT_EXCEPT_PATH_LIST]
	except_path_list_fsd = [pcf_s.normp(pcf_s.abs(p, force_flag=True)) if len(p) > 0 else pcf_s.normp(pcf_r.abs(".", force_flag=True)) for p in CONST.EXCEPT_PATH_LIST_FROM_SCRIPTS_DIRECTORY]
	not_except_path_list_fsd = [pcf_s.normp(pcf_s.abs(p, force_flag=True)) if len(p) > 0 else pcf_s.normp(pcf_r.abs(".", force_flag=True)) for p in CONST.NOT_EXCEPT_PATH_LIST_FROM_SCRIPTS_DIRECTORY]
	except_path_list.extend(except_path_list_fsd) #合流１
	not_except_path_list.extend(not_except_path_list_fsd) #合流２
	ignore_change_path_list = [pcf_r.normp(pcf_r.abs(p, force_flag=True)) if len(p) > 0 else pcf_r.normp(pcf_r.abs(".", force_flag=True)) for p in CONST.IGNORE_CHANGE_PATH_LIST]
	not_ignore_change_path_list = [pcf_r.normp(pcf_r.abs(p, force_flag=True)) if len(p) > 0 else pcf_r.normp(pcf_r.abs(".", force_flag=True)) for p in CONST.NOT_IGNORE_CHANGE_PATH_LIST]
	ignore_change_path_list_fsd = [pcf_s.normp(pcf_s.abs(p, force_flag=True)) if len(p) > 0 else pcf_s.normp(pcf_s.abs(".", force_flag=True)) for p in CONST.IGNORE_CHANGE_PATH_LIST_FROM_SCRIPTS_DIRECTORY]
	not_ignore_change_path_list_fsd = [pcf_s.normp(pcf_s.abs(p, force_flag=True)) if len(p) > 0 else pcf_s.normp(pcf_s.abs(".", force_flag=True)) for p in CONST.NOT_IGNORE_CHANGE_PATH_LIST_FROM_SCRIPTS_DIRECTORY]
	ignore_change_path_list.extend(ignore_change_path_list_fsd) #合流３
	not_ignore_change_path_list.extend(not_ignore_change_path_list_fsd) #合流４
	del except_path_list_fsd, not_except_path_list_fsd, ignore_change_path_list_fsd, not_ignore_change_path_list_fsd
	gc.collect() #未参照オブジェクトを即座に解放
	#正規表現パターン設定処理
	msg_m, msg, buffer_for_print_and_dump_list = "{0}[L]: Load regex config.".format(" "*0), "{0}[E]: This message should not be outputed.".format(" "*4), list()
	regex_include_tester = RegexTester(CONST.INCLUDE_PATH_REGEX_PATTERN, default=True) #'INCLUDE_PATH_REGEX_PATTERN'正規表現に見合う対象のみを取得する
	if not(regex_include_tester.is_error()):
		msg = "{0}[ ]: Interpreting include regex succeeded.{1}".format(" "*4, " (ALL PASS)" if regex_include_tester.is_free() else " (PATTERN= '{0}')".format(regex_include_tester.get_pattern()))
	else:
		msg = "{0}[W]: Interpreting include regex failed. (BECOUSE= '{1}')".format(" "*4, str(regex_include_tester.get_error()).replace("'", "\\'"))
	buffer_for_print_and_dump_list.append((msg, 0)) #後回し
	regex_ignore_tester = RegexTester(CONST.IGNORE_PATH_REGEX_PATTERN, default=False) #'IGNORE_PATH_REGEX_PATTERN'正規表現に見合う対象は無視される
	msg = "{0}[E]: This message should not be outputed.".format(" "*4)
	if not(regex_ignore_tester.is_error()):
		msg = "{0}[ ]: Interpreting ignore regex succeeded.{1}".format(" "*4, " (ALL BLOCK)" if regex_ignore_tester.is_free() else " (PATTERN= '{0}')".format(regex_ignore_tester.get_pattern()))
	else:
		msg = "{0}[W]: Interpreting ignore regex failed. (BECOUSE= '{1}')".format(" "*4, str(regex_ignore_tester.get_error()).replace("'", "\\'"))
	buffer_for_print_and_dump_list.append((msg, 0)) #後回し
	printer.print(msg_m, code=0)
	for msg_v, code in buffer_for_print_and_dump_list:
		printer.print(msg_v, code=code)
	printer.fresh() #強制更新
	del buffer_for_print_and_dump_list
	gc.collect() #未参照オブジェクトを即座に解放
	#データ取得
	msg_gm = "{0}[G]: Get system structure hash.".format(" "*0)
	printer.print(msg_gm, code=0)
	default_ishidden = glob._ishidden #通常は「.」から始まるファイルを処理しない
	glob._ishidden = lambda x: False #すべて処理させる
	structure_data = sort_path_list(
		[
			pcf_r.format(g)
			for g in list(set(glob.glob("**", recursive=True, root_dir=pcf_r.normp(pcf_r.abs("."))))) 
			if (pcf_r.normp(pcf_r.abs(g)).startswith(tuple(not_except_path_list))) or not(pcf_r.normp(pcf_r.abs(g)).startswith(tuple(except_path_list)))
		], 
		char_for_split="/"
	)
	if not(regex_include_tester.is_free()):
		structure_data = [d for d in structure_data if regex_include_tester.test(d)] #正規表現判定
	glob._ishidden = default_ishidden #設定を元に戻す
	json_strings_of_structure_data = json.dumps(structure_data, ensure_ascii=False) #json変換機構
	structure_option = hashlib.sha512(json_strings_of_structure_data.encode()).hexdigest()
	msg = "{0}[ ]: {1} objects are used to generate the system hash.".format(" "*4, len(structure_data))
	printer.print(msg, code=0)
	del json_strings_of_structure_data
	gc.collect() #未参照オブジェクトを即座に解放
	#無視系の考慮 START
	structure_data_ti = [t for t in structure_data if not(pcf_r.normp(pcf_r.abs(t)).startswith(tuple(ignore_change_path_list))) or pcf_r.normp(pcf_r.abs(t)).startswith(tuple(not_ignore_change_path_list))]
	if not(regex_ignore_tester.is_free()):
		structure_data_ti = [d for d in structure_data_ti if not(regex_ignore_tester.test(d))] #正規表現判定
	json_strings_of_structure_data_ti = json.dumps(structure_data_ti, ensure_ascii=False) #json変換機構
	structure_option_ti = hashlib.sha512(json_strings_of_structure_data_ti.encode()).hexdigest()
	del json_strings_of_structure_data_ti
	gc.collect() #未参照オブジェクトを即座に解放
	#無視系の考慮 END
	target_data = [(a, pcf_r.normp(pcf_r.abs(a))) for a in structure_data if os.path.isfile(pcf_r.abs(a))]
	json_dict, ban_error_flag, error_message_list = dict(), False, list()
	#個別検査
	#ファイルシャッフル
	if CONST.SHUFFLE_FILE_ORDER_MODE:
		random.shuffle(target_data)
	#マルチプロセス準備系 START
	target_data_list = [target_data] if CONST.MAXIMUM_NUMBER_OF_WORKERS is None or CONST.MAXIMUM_NUMBER_OF_WORKERS < 1 else [target_data[i:i+CONST.NUMBER_OF_TASK_ASSIGNED_TO_EACH_WORKER] for i in range(0, len(target_data), CONST.NUMBER_OF_TASK_ASSIGNED_TO_EACH_WORKER)]
	len_target_data_list = len(target_data_list)
	del target_data
	gc.collect() #未参照オブジェクトを即座に解放
	#マルチプロセス準備系 END
	buffer_for_msg_gm = "Workers: {0:0{1}d}".format(0, len(str(len_target_data_list))) if CONST.MAXIMUM_NUMBER_OF_WORKERS is None or CONST.MAXIMUM_NUMBER_OF_WORKERS < 1 else "Workers: {0:0{1}d}".format(len_target_data_list, len(str(len_target_data_list)))
	msg_gm = "{0}[G]: Get file hash. ({1})".format(" "*0, buffer_for_msg_gm)
	printer.print(msg_gm, code=0)
	#マルチプロセス系 START
	kwargs_source = {
        "print_mode"        : CONST.PRINT_MODE,
        "read_byte"         : CONST.READ_BYTE,
        "read_first_flag"   : CONST.READ_FIRST_FLAG,
        "read_last_flag"    : CONST.READ_LAST_FLAG,
        "buffer_size"       : CONST.BUFFER_SIZE,
        "add_first_message" : msg_gm,
	}
	get_hash_worker = GetHashWorker()
	if CONST.MAXIMUM_NUMBER_OF_WORKERS is None or CONST.MAXIMUM_NUMBER_OF_WORKERS < 1:
		kwargs = dict(kwargs_source)
		kwargs.update({
            "target_data" : target_data_list[0],
            "worker_name" : "Worker-0",
		})
		return_one = get_hash_worker.get_file_hash(**dict(kwargs))
		json_dict.update(return_one[0])
		ban_error_flag |= return_one[1]
		error_message_list.extend(return_one[2])
		printer.extend(return_one[3])
		del return_one
		gc.collect() #未参照オブジェクトを即座に解放
	else:
		executer_list = list()
		parallel_executor = concurrent.futures.ProcessPoolExecutor if CONST.USE_PROCESS_MODE else concurrent.futures.ThreadPoolExecutor
		with parallel_executor(max_workers=CONST.MAXIMUM_NUMBER_OF_WORKERS) as executer: #プロセス分割
			try:
				for n, target_data_one in enumerate(target_data_list):
					kwargs = dict(kwargs_source)
					kwargs.update({
                        "target_data" : target_data_one,
                        "worker_name" : "Worker-{0:0{1}d}".format(n+1, len(str(len_target_data_list))),
					})
					executer_list.append(executer.submit(get_hash_worker.get_file_hash, **dict(kwargs)))
				for executer_one in concurrent.futures.as_completed(executer_list):
					return_one = executer_one.result()
					json_dict.update(return_one[0])
					ban_error_flag |= return_one[1]
					error_message_list.extend(return_one[2])
					printer.extend(return_one[3])
					del return_one
					gc.collect() #未参照オブジェクトを即座に解放
			except Exception as e:
				for executer_one in executer_list:
					executer_one.cancel()
				executer.shutdown(wait=False, cancel_futures=True)
				raise e
		del executer_list, target_data_list, executer_one, executer
		gc.collect() #未参照オブジェクトを即座に解放
	#マルチプロセス系 END
	last_error_flag, write_error_flag = False, False
	#ファイル出力可否決定
	buffer_for_print_and_dump_list, dump_ok_flag_list = list(), [False for _ in range(1)]
	msg_m, msg = "{0}[O]: Check output files.".format(" "*0), "{0}[E]: This message should not be outputed.".format(" "*4)
	buffer_for_print_and_dump_list.append((msg_m, 0)) #後回し
	try:
		flag_list_of_open_modew = [
			check_open_wmode(
				True if CONST.SAVE_REMEMBER_FILE_NAME is not None else False, 
				"output remorize json", 
				os.path.join(pcf_s.abs(CONST.DIRECTORY_FOR_SAVE_FILE_PATH), "{0}.json".format(CONST.SAVE_REMEMBER_FILE_NAME))
			),
		]
		dump_ok_flag_list, counter_ft = [False for _ in range(len(flag_list_of_open_modew))], 0
		for ft in flag_list_of_open_modew:
			if ft[0] is not None and ft[0]:
				msg = "{0}[ ]: {1}".format(" "*4, ft[1])
				buffer_for_print_and_dump_list.append((msg, 0)) #後回し
				dump_ok_flag_list[counter_ft] = True
			elif ft[0] is not None and not(ft[0]):
				msg = "{0}[E]: {1}".format(" "*4, ft[1])
				buffer_for_print_and_dump_list.append((msg, 0)) #後回し
				error_message_list.extend([msg_m, msg])
				last_error_flag, write_error_flag = True, True
			counter_ft += 1
	except Exception as e:
		msg = "{0}[E]: {1}".format(" "*4, "[{0}]: {1}".format(type(e).__name__, str(e)))
		buffer_for_print_and_dump_list.append((msg, 0)) #後回し
		error_message_list.extend([msg_m, msg])
		last_error_flag, write_error_flag = True, True
	finally:
		for msg_v, code in buffer_for_print_and_dump_list:
			printer.print(msg_v, code=code)
		printer.fresh() #強制更新
	del buffer_for_print_and_dump_list, flag_list_of_open_modew
	gc.collect() #未参照オブジェクトを即座に解放
	#時間計測結果参照
	finish_datetime = datetime.datetime.now()
	msg_m = "{0}[T]: Show the elapsed time.".format(" "*0)
	msg = "{0}[ ]: {3} (Start: {1}, Finish: {2}, ElapsedSeconds: {4:.3f})".format(
		" "*4, 
		start_datetime.strftime("%Y/%m/%d %H:%M:%S"),
		finish_datetime.strftime("%Y/%m/%d %H:%M:%S"),
		format_timedelta((finish_datetime - start_datetime).total_seconds()),
		round((finish_datetime - start_datetime).total_seconds(), 3)
	)
	printer.print(msg_m, code=0)
	printer.print(msg, code=0)
	#保存
	if save_flag and dump_ok_flag_list[0]:
		os.makedirs(pcf_s.abs(CONST.DIRECTORY_FOR_SAVE_FILE_PATH), exist_ok=True)
		with open(os.path.join(pcf_s.abs(CONST.DIRECTORY_FOR_SAVE_FILE_PATH), "{0}.json".format(CONST.SAVE_REMEMBER_FILE_NAME)), "w", encoding="utf-8-sig") as fw:
			fw.write(
				json.dumps(
					{
                        "CONFIG_HASH"               : save_option,
                        "RAW_SYSTEM_STRUCTURE_HASH" : structure_option,
                        "RAW_SYSTEM_STRUCTURE_LIST" : structure_data,
                        "SYSTEM_STRUCTURE_HASH"     : structure_option_ti,
                        "SYSTEM_STRUCTURE_LIST"     : structure_data_ti,
                        "SYSTEM_FILE_HASH_MAP"      : json_dict,
                        "CREATE_START_DATETIME"     : start_datetime.strftime("%Y/%m/%d %H:%M:%S"),
                        "CREATE_FINISH_DATETIME"    : finish_datetime.strftime("%Y/%m/%d %H:%M:%S"),
					},
					indent=4 if CONST.INCREASE_READABILITY_OF_SAVE_FILE_MODE else None, 
					ensure_ascii=False if CONST.INCREASE_READABILITY_OF_SAVE_FILE_MODE else True
				)
			)
	warning_flag = False
	if len(error_message_list) > 0 or not(save_flag) or CONST.SAVE_REMEMBER_FILE_NAME is None:
		warning_flag = True
		printer.print("="*60, code=1)
		printer.print("[ Noteworthy message ]", code=1)
		printer.print("-"*60, code=1)
		if not(save_flag):
			printer.print("[D]: It was not saved because '{0}' was not entered as the first command line argument.".format(CONST.ARGS_MATCH_TEXT), code=1)
		if CONST.SAVE_REMEMBER_FILE_NAME is None:
			printer.print("[D]: It was not saved because the \"SAVE_REMEMBER_FILE_NAME\" constant has not been specified.", code=1)
		if len(error_message_list) > 0:
			last_error_flag = True
			for em in error_message_list:
				printer.print(em, code=1)
	printer.print("="*60, code=1)
	if last_error_flag or warning_flag:
		printer.print("《 Check noteworthy message 》", code=1)
	else:
		printer.print("《 No problem 》", code=1)
	return (103 if write_error_flag else 102) if last_error_flag else (101 if warning_flag else 0)

if "__main__" == __name__:
	return_code = memorize(sys.argv)
	#print("{0}{1}".format("-"*60, "\n"*2), end="")
	#CONST.show_counter() #アクセスカウント表示
	sys.exit(return_code)