#!/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, html, csv, re, random, gc, time
import concurrent.futures
from config import CONST

class DifferentConfigHashError(ValueError):
	"""This configuration hash value is different than expected."""
	pass

class IncorrectConfigError(ValueError):
	"""This configuration is incorrect."""
	pass

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 split_path(path, char_for_split="/"): #パスを分断することでソートを助ける関数
	return str(path).split(str(char_for_split))

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 format_number_and_marker_list(number_and_marker_list): #コード及びマーカーリストの変換器
	if number_and_marker_list is None or type(number_and_marker_list) not in (tuple, list):
		return list()
	else:
		buffer = [b for b in number_and_marker_list if len(b) == 2 and type(b[0]) == str and type(b[1]) == str and len(b[0]) > 0 and len(b[1]) == 1]
		return_buffer = []
		for b in buffer:
			number_buffer = None
			if "AB" == b[0][0:2].upper():
				number_buffer = 1 #"ABNORMAL"
			elif "B" == b[0][0:1].upper():
				number_buffer = 2 #"BEHOLDED"
			elif "I" == b[0][0:1].upper():
				number_buffer = 3 #"IGNORE"
			elif "AP" == b[0][0:2].upper():
				number_buffer = 4 #"APPENDED"
			elif "C" == b[0][0:1].upper():
				number_buffer = 5 #"CORRUPTED"
			elif "D" == b[0][0:1].upper():
				number_buffer = 6 #"DELETED"
			elif "E" == b[0][0:1].upper():
				number_buffer = 7 #"ERROR"
			if number_buffer is not None:
				return_buffer.append((number_buffer, b[1]))
		return return_buffer[::-1]

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

class InspectWorker: #ラッピングクラス
	file_hash_dict = None
	
	@classmethod
	def initialization(cls, file_hash_dict): #比較するハッシュ値を管理する関数
		cls.file_hash_dict = file_hash_dict #読み込み専用
	
	@classmethod
	def inspect_file_hash(
		cls, target_data, regex_beholded_tester, regex_ignore_tester, path_converter,
		beholded_path_list=[], not_beholded_path_list=[], ignore_change_path_list=[], not_ignore_change_path_list=[], 
		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 [])
		if cls.file_hash_dict is None:
			raise Exception("Not initialize inspect worker.")
		file_hash_dict = cls.file_hash_dict #比較辞書
		error_message_list, tag_path_list, last_error_flag, ban_error_flag, json_dict = list(), list(), False, False, dict()
		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
				#注視系による勧告 START
				abnormal_flag, beholded_target_tag, beholded_flag, ignore_flag, error_flag = True, "", False, False, False
				appended_flag, corrupte_flag, deleted_flag = False, False, False
				if td_path.startswith(tuple(beholded_path_list)) and not(td_path.startswith(tuple(not_beholded_path_list))):
					beholded_target_tag, beholded_flag = "", True
					for beholded_path_one in beholded_path_list:
						if td_path.startswith(beholded_path_one):
							beholded_target_tag = " ('{0}' ({1}))".format(
								path_converter.format(beholded_path_one),
								"Single" if os.path.isfile(beholded_path_one) else "Multiple"
							)
							break
				elif regex_beholded_tester.test(td_title): #正規表現パターンがNoneである場合は常にFalse
					beholded_target_tag, beholded_flag = " ('{0}' ({1}))".format(regex_beholded_tester.get_pattern().replace("'", "\\'"), "Regex"), True
				#注視系による勧告 END
				msg_m = "{0}[{2}]: {3}{1}".format(" "*(0 if add_first_message is None else 4), td_title, "B" if beholded_flag else " ", "" if worker_name is None else "({0}): ".format(worker_name))
				printer.print(msg_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()
					if td_title in file_hash_dict.keys():
						if json_dict[td_title] is None or file_hash_dict[td_title] is None or json_dict[td_title] != file_hash_dict[td_title]:
							msg = "{0}[E]: This message should not be outputed.".format(" "*(4 if add_first_message is None else 8)) #このメッセージは表示されるべきでない
							if json_dict[td_title] is None:
								last_error_flag, error_flag = True, True
								msg = "{0}[E]: Could not read the hash value information for this file.".format(" "*(4 if add_first_message is None else 8))
								if beholded_flag: #注視メッセージ
									msg += " (The file path is beholded{0})".format(beholded_target_tag)
							elif file_hash_dict[td_title] is None:
								last_error_flag, error_flag = True, True
								msg = "{0}[E]: The hash value for this file, which should be in 'SYSTEM_FILE_HASH_MAP', does not exist.".format(" "*(4 if add_first_message is None else 8))
								if beholded_flag: #注視メッセージ
									msg += " (The file path is beholded{0})".format(beholded_target_tag)
							elif td_path.startswith(tuple(ignore_change_path_list)) and not(td_path.startswith(tuple(not_ignore_change_path_list))):
								corrupte_flag = True
								target_tag, ignore_flag, abnormal_flag = "", True, False
								for ignore_update_path_one in ignore_change_path_list:
									if td_path.startswith(ignore_update_path_one):
										target_tag = " ('{0}' ({1}))".format(
											path_converter.format(ignore_update_path_one),
											"Single" if os.path.isfile(ignore_update_path_one) else "Multiple"
										)
										break
								msg = "{0}[I]: This file has been corrupted or changed. (Differences in this file definition were ignored{1})".format(" "*(4 if add_first_message is None else 8), target_tag)
								if beholded_flag: #注視メッセージ
									msg += " (The file path is beholded{0})".format(beholded_target_tag)
							elif regex_ignore_tester.test(td_title): #正規表現パターンがNoneである場合は常にFalse
								corrupte_flag = True
								target_tag, ignore_flag, abnormal_flag = " ('{0}' ({1}))".format(regex_ignore_tester.get_pattern().replace("'", "\\'"), "Regex"), True, False
								msg = "{0}[I]: This file has been corrupted or changed. (Differences in this file definition were ignored{1})".format(" "*(4 if add_first_message is None else 8), target_tag)
								if beholded_flag: #注視メッセージ
									msg += " (The file path is beholded{0})".format(beholded_target_tag)
							else:
								corrupte_flag = True
								last_error_flag = True
								msg = "{0}[W]: This file has been corrupted.".format(" "*(4 if add_first_message is None else 8))
								if beholded_flag: #注視メッセージ
									msg += " (The file path is beholded{0})".format(beholded_target_tag)
							printer.print(msg, code=0, buffer_mode=True)
							error_message_list.extend([msg_m, msg] if add_first_message is None else [add_first_message, msg_m, msg])
						else:
							abnormal_flag = False #完全一致
					else:
						msg = "{0}[E]: This message should not be outputed.".format(" "*(4 if add_first_message is None else 8)) #このメッセージは表示されるべきでない
						appended_flag = True
						if td_path.startswith(tuple(ignore_change_path_list)) and not(td_path.startswith(tuple(not_ignore_change_path_list))):
							target_tag, ignore_flag, abnormal_flag = "", True, False
							for ignore_update_path_one in ignore_change_path_list:
								if td_path.startswith(ignore_update_path_one):
									target_tag = " ('{0}' ({1}))".format(
										path_converter.format(ignore_update_path_one),
										"Single" if os.path.isfile(ignore_update_path_one) else "Multiple"
									)
									break
							msg = "{0}[I]: This file has been appended or changed. (Differences in this file definition were ignored{1})".format(" "*(4 if add_first_message is None else 8), target_tag)
							if beholded_flag: #注視メッセージ
								msg += " (The file path is beholded{0})".format(beholded_target_tag)
						else:
							last_error_flag = True
							msg = "{0}[W]: This file has been appended.".format(" "*(4 if add_first_message is None else 8))
							if beholded_flag: #注視メッセージ
								msg += " (The file path is beholded{0})".format(beholded_target_tag)
						printer.print(msg, code=0, buffer_mode=True)
						error_message_list.extend([msg_m, msg] if add_first_message is None else [add_first_message, msg_m, msg])
					tag_path_list.append((
						td_title, 
						abnormal_flag, 
						beholded_flag, 
						ignore_flag, 
						appended_flag, 
						corrupte_flag, 
						deleted_flag, 
						error_flag,
						file_hash_dict[td_title] if td_title in file_hash_dict.keys() else None,
						json_dict[td_title] if td_title in json_dict.keys() else None,
					))
				except Exception as e:
					msg = "{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."
						)
					if beholded_flag: #注視メッセージ
						msg += " (The file path is beholded{0})".format(beholded_target_tag)
					printer.print(msg, code=0, buffer_mode=True)
					error_message_list.extend([msg_m, msg] if add_first_message is None else [add_first_message, msg_m, msg])
					last_error_flag, error_flag, abnormal_flag = True, True, True
					tag_path_list.append((
						td_title, 
						abnormal_flag, 
						beholded_flag, 
						ignore_flag, 
						appended_flag, 
						corrupte_flag, 
						deleted_flag, 
						error_flag,
						json_dict[td_title] if td_title in json_dict.keys() else None,
						file_hash_dict[td_title] if td_title in file_hash_dict.keys() else None,
					))
					if ban_error_flag:
						raise
				finally:
					printer.print_buffer()
					del hash_object
					gc.collect() #未参照オブジェクトを即座に解放
		del json_dict
		gc.collect() #未参照オブジェクトを即座に解放
		return (error_message_list, last_error_flag, tag_path_list, printer.get(), worker_name)

PLAIN_ICON_DATA = "AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAA==" #プレーンアイコン

def convert_plain_text_to_html_text(plain_text, title=""):
	return """
<!DOCTYPE html>
<html lang="ja">
	<head>
		<meta name="viewport" content="width=device-width,initial-scale=1">
		<meta charset="utf-8">
		<link rel="icon" type="image/vnd.microsoft.icon" href="{favicon_href}">
		<style type="text/css">
html {{
	width: 100%;
	height: 100%;
	margin: 0px;
	padding: 0px;
}}
body {{
	text-size-adjust: none;
	width: 100%;
	height: 100%;
	margin: 0px;
	padding: 0px;
}}
#plain_text_area {{
	width: calc(100% - 0.5em);
	height: calc(100% - 0.5em);
	resize: none;
	border: 0px;
	margin: 0px;
	padding: 0.25em;
	background-color: #FFFFFF;
	color: #000000;
	outline: 0px;
	white-space: pre;
	display:block;
	cursor: auto;
}}
		</style>
		<title>{title}</title>
	</head>
	<body>
		<textarea id="plain_text_area" disabled>{plain_text}</textarea>
	</body>
</html>
	""".strip().format(
		plain_text=html.escape(str(plain_text)), 
		title=str(title), 
		favicon_href="data:image/x-icon;base64,{0}".format(PLAIN_ICON_DATA)
	)

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_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 inspect(argv=[]):
	#sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8-sig")
	#基本準備処理
	sub_strings = " ({0})".format(" ".join([str(s) for s in list(argv[1:])])) if len(argv) > 1 else ""
	error_message_list, last_error_flag = list(), False
	printer = Printer(print_code_list=[0, 1] if CONST.PRINT_MODE else list())
	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()
	datetime_str, buffer_for_print_and_dump_list = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S"), list()
	#記録データの読み込み
	load_json_data, datetime_s, datetime_f = None, None, None
	system_structure_hash, file_hash_dict, system_structure_list = None, dict(), list()
	msg_m, msg = "{0}[L]: Load json file. ({1})".format(" "*0, "{0}.json".format(CONST.SAVE_REMEMBER_FILE_NAME)), "{0}[E]: This message should not be outputed.".format(" "*4)
	buffer_for_print_and_dump_list.append((msg_m, 0)) #後回し
	file_not_found_flag, different_config_hash_error_flag, dirty_flag, dirty_tips = False, False, False, None
	try:
		save_directory_path_buffer = pcf_s.abs(CONST.DIRECTORY_FOR_SAVE_FILE_PATH)
		save_file_path_buffer = os.path.join(save_directory_path_buffer, "{0}.json".format(CONST.SAVE_REMEMBER_FILE_NAME))
		os.makedirs(save_directory_path_buffer, exist_ok=True)
		if not(os.path.isfile(save_file_path_buffer)): #読み込みjson存在確認
			raise FileNotFoundError("No such the save file named '{0}.json',".format(
				os.path.join(
					CONST.DIRECTORY_FOR_SAVE_FILE_PATH,
					CONST.SAVE_REMEMBER_FILE_NAME
				).replace(os.path.sep, "/")
			))
		with open(save_file_path_buffer, "r", encoding="utf-8-sig") as fr:
			load_json_data, key_error_count = json.loads(fr.read()), 0
			if CONST.IGNORE_CONFIG_HASH_MODE:
				msg = "{0}[W]: The hash code called 'CONFIG_HASH' is not verified. This mode called 'IGNORE_CONFIG_HASH_MODE' state is not recommended.".format(" "*4, datetime_s, datetime_f)
				#printer.print(msg, code=0) #廃止
				dirty_flag, dirty_tips = True, [msg_m, msg] #エラー警告
				buffer_for_print_and_dump_list.append((msg, 0)) #後回し
			elif "CONFIG_HASH" not in load_json_data or load_json_data["CONFIG_HASH"] is None or load_json_data["CONFIG_HASH"] != save_option:
				raise DifferentConfigHashError("The hash code called 'CONFIG_HASH' in this script does not match that recorded in the json file.")
			try:
				datetime_s = load_json_data["CREATE_START_DATETIME"]
			except KeyError:
				if not(CONST.IGNORE_CONFIG_HASH_MODE):
					raise
				key_error_count += 1
			try:
				datetime_f = load_json_data["CREATE_FINISH_DATETIME"]
			except KeyError:
				if not(CONST.IGNORE_CONFIG_HASH_MODE):
					raise
				key_error_count += 1
			try:
				system_structure_hash = load_json_data["SYSTEM_STRUCTURE_HASH"]
			except KeyError:
				if not(CONST.IGNORE_CONFIG_HASH_MODE):
					raise
				key_error_count += 1
			try:
				system_structure_list = load_json_data["SYSTEM_STRUCTURE_LIST"]
			except KeyError:
				if not(CONST.IGNORE_CONFIG_HASH_MODE):
					raise
				key_error_count += 1
			try:
				file_hash_dict = dict(load_json_data["SYSTEM_FILE_HASH_MAP"])
			except KeyError:
				if not(CONST.IGNORE_CONFIG_HASH_MODE):
					raise
				key_error_count += 1
			msg = "{0}[E]: This message should not be outputed.".format(" "*4) #このメッセージは表示されるべきでない
			if datetime_s is not None and datetime_f is not None:
				msg = "{0}[I]: Loading succeeded.{3} (Start: {1}, Finish: {2})".format(
					" "*4, 
					datetime_s, 
					datetime_f,
					" (There are {0} keys that cannot be read.)".format(key_error_count) if key_error_count > 0 else ""
				)
			else:
				msg = "{0}[I]: Loading succeeded.{1}".format(
					" "*4,
					" (There are {0} keys that cannot be read.)".format(key_error_count) if len(key_error_count) > 0 else ""
				)
			#printer.print(msg, code=0) #廃止
			buffer_for_print_and_dump_list.append((msg, 0)) #後回し
	except Exception as e:
		file_not_found_flag = True if type(e) == FileNotFoundError else False
		different_config_hash_error_flag = True if type(e) == DifferentConfigHashError else False
		msg = "{0}[E]: Loading failed. ({1})".format(" "*4, "[{0}]: {1}".format(type(e).__name__, str(e)))
		#printer.print(msg, code=0) #廃止
		buffer_for_print_and_dump_list.append((msg, 0)) #後回し
		error_message_list.extend([msg_m, msg])
		last_error_flag = True
	finally:
		printer.print("<<< System inspect results ({0}Inspect: {1}){2} >>>".format(
			"" if datetime_s is None else "Memorize: {0}, ".format(datetime_s), 
			datetime_str, 
			sub_strings
		), code=3)
		printer.print("="*60, code=3)
		#後回しにしたprint及びdump登録を行う
		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() #未参照オブジェクトを即座に解放
	#CSV設定確認
	problem_of_csv_format_flag, buffer_for_print_and_dump_list, number_of_csv_column = False, list(), None
	if CONST.DUMP_CSV_FILE_NAME or CONST.DUMP_MODE_OF_DUMP_SUMMARY_CSV_FILE:
		msg_m, msg, buffer = "{0}[L]: Load csv format config.".format(" "*0), "{0}[E]: This message should not be outputed.".format(" "*4), list()
		buffer_for_print_and_dump_list.append((msg_m, 0)) #後回し
		try:
			try:
				buffer = CONST.DUMP_CSV_BODY_FUNCTION(["xxx", False, False, False, False, False, False, False, None, None])
				if buffer is None or type(buffer) not in (tuple, list):
					raise IncorrectConfigError("'DUMP_CSV_BODY_FUNCTION' constant is not in the expected format. (The output is not iterable type.)")
			except TypeError as e:
				raise IncorrectConfigError("The constant named 'DUMP_CSV_BODY_FUNCTION' is not in the expected format. ({0})".format(e))
			if CONST.DUMP_CSV_HEADER is None or type(CONST.DUMP_CSV_HEADER) not in (tuple, list):
				raise IncorrectConfigError("The constant named 'DUMP_CSV_HEADER' is not in the expected format.")
			if len(CONST.DUMP_CSV_HEADER) != len(buffer):
				raise IncorrectConfigError("Unmatch csv column format. (Header= {0}, Body={1})".format(len(CONST.DUMP_CSV_HEADER), len(buffer)))
			number_of_csv_column = len(buffer)
			msg = "{0}[ ]: Formatting csv succeeded.".format(" "*4)
			buffer_for_print_and_dump_list.append((msg, 0)) #後回し
			problem_of_csv_format_flag = True
		except IncorrectConfigError as e:
			msg = "{0}[E]: Formatting csv failed. ({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 = 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
	gc.collect() #未参照オブジェクトを即座に解放
	#本処理
	tag_path_list, abnormal_structure_flag_tuple = list(), (False, False)
	if len(error_message_list) < 1:
		#正規表現パターン設定処理
		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)) #後回し
		regex_beholded_tester = RegexTester(CONST.BEHOLDED_PATH_REGEX_PATTERN, default=False) #'BEHOLDED_PATH_REGEX_PATTERN'正規表現に見合う対象は注視される
		msg = "{0}[E]: This message should not be outputed.".format(" "*4)
		if not(regex_beholded_tester.is_error()):
			msg = "{0}[ ]: Interpreting beholded regex succeeded.{1}".format(" "*4, " (ALL BLOCK)" if regex_beholded_tester.is_free() else " (PATTERN= '{0}')".format(regex_beholded_tester.get_pattern()))
		else:
			msg = "{0}[W]: Interpreting beholded regex failed. (BECOUSE= '{1}')".format(" "*4, str(regex_beholded_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() #未参照オブジェクトを即座に解放
		#付与パスの構成
		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) #合流４
		beholded_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.BEFOLDED_PATH_LIST]
		not_beholded_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_BEFOLDED_PATH_LIST]
		beholded_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.BEFOLDED_PATH_LIST_FROM_SCRIPTS_DIRECTORY]
		not_beholded_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_BEFOLDED_PATH_LIST_FROM_SCRIPTS_DIRECTORY]
		beholded_path_list.extend(beholded_path_list_fsd) #合流５
		not_beholded_path_list.extend(not_beholded_path_list_fsd) #合流６
		del except_path_list_fsd, not_except_path_list_fsd, ignore_change_path_list_fsd, not_ignore_change_path_list_fsd
		del beholded_path_list_fsd, not_beholded_path_list_fsd
		gc.collect() #未参照オブジェクトを即座に解放
		#データ取得
		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()
		del json_strings_of_structure_data
		gc.collect() #未参照オブジェクトを即座に解放
		#全体検査
		msg_m = "{0}[C]: Check system structure hash.".format(" "*0)
		printer.print(msg_m, code=0)
		#無視系の考慮 START
		system_structure_list_ti = [t for t in system_structure_list 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))]
		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()):
			system_structure_list_ti = [d for d in system_structure_list_ti if not(regex_ignore_tester.test(d))] #正規表現判定
		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
		if structure_option_ti != system_structure_hash or system_structure_list_ti != structure_data_ti:
			msg = "{0}[E]: This message should not be outputed.".format(" "*4) #このメッセージは表示されるべきでない
			msg_c_list, diff_structure_flag = list(), False
			if structure_option_ti != system_structure_hash and system_structure_list_ti != structure_data_ti: #正常に間違っている
				msg = "{0}[W]: The system structure is incorrect.".format(" "*4)
				diff_structure_flag = True
				abnormal_structure_flag_tuple = (True, False)
			elif structure_option_ti == system_structure_hash and system_structure_list_ti != structure_data_ti: #異常に間違っている(ハッシュは正しい、リストは違う)
				msg = "{0}[E]: The system structure is most likely incorrect. (Hashes of the system structure match. However, pathlists in the system structure do not match.)".format(" "*4)
				diff_structure_flag = True
				abnormal_structure_flag_tuple = (True, True)
			elif structure_option_ti != system_structure_hash and system_structure_list_ti == structure_data_ti: #異常に間違っている(ハッシュは違う、リストは正しい)
				msg = "{0}[E]: The system structure is most likely incorrect. (Pathlists in the system structure match. However, hashes of the system structure do not match.)".format(" "*4)
				abnormal_structure_flag_tuple = (True, True)
			if diff_structure_flag:
				add_message, sub_message = "A", "D"
				add_set = set(structure_data_ti) - set(system_structure_list_ti) #増えたものについて
				add_set = {(v, add_message) for v in add_set}
				sub_set = set(system_structure_list_ti) - set(structure_data_ti) #減ったものについて
				sub_set = {(v, sub_message) for v in sub_set}
				for v, i in sorted(list(add_set | sub_set), key=lambda x: split_path(x[0], char_for_split="/")):
					msg_c = "{0}[{2:<{3}s}]: {1}".format(" "*8, v, i, max([len(str(s)) for s in [add_message, sub_message]]))
					msg_c_list.append(msg_c)
				del add_set, sub_set, add_message, sub_message
				gc.collect() #未参照オブジェクトを即座に解放
			printer.print(msg, code=0)
			for p in msg_c_list:
				printer.print(p, code=0)
			error_message_list.extend([msg_m, msg] + msg_c_list)
			last_error_flag = True
		else: #正常に正しい
			msg = "{0}[I]: The system structure hash is normal.".format(" "*4)
			printer.print(msg, code=0)
		printer.fresh() #強制更新
		del system_structure_list_ti, structure_data_ti, system_structure_list
		gc.collect() #未参照オブジェクトを即座に解放
		#個別検査
		target_data = [(a, pcf_r.normp(pcf_r.abs(a))) for a in structure_data if os.path.isfile(pcf_r.abs(a))]
		target_title_set = {a for a in structure_data if os.path.isfile(pcf_r.abs(a))}
		del structure_data
		gc.collect() #未参照オブジェクトを即座に解放
		#ファイルシャッフル
		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
		json_dict = dict()
		msg_gm = "{0}[C]: Search lost file. ({1})".format(" "*0, "Workers: {0:{1}d}".format(0, len(str(len_target_data_list))))
		printer.print(msg_gm, code=0)
		for td_title, td_hash in file_hash_dict.items():
			#注視系による勧告 START
			abnormal_flag, beholded_target_tag, beholded_flag, ignore_flag, error_flag = True, "", False, False, False
			appended_flag, corrupte_flag, deleted_flag = False, False, False
			if pcf_r.normp(pcf_r.abs(td_title)).startswith(tuple(beholded_path_list)) and not(pcf_r.normp(pcf_r.abs(td_title)).startswith(tuple(not_beholded_path_list))):
				beholded_target_tag, beholded_flag = "", True
				for beholded_path_one in beholded_path_list:
					if pcf_r.normp(pcf_r.abs(td_title)).startswith(beholded_path_one):
						beholded_target_tag = " ('{0}' ({1}))".format(
							pcf_r.format(beholded_path_one),
							"Single" if os.path.isfile(beholded_path_one) else "Multiple"
						)
						break
			#注視系による勧告 END
			msg_m = "{0}[{2}]: {3}{1}".format(" "*4, td_title, "B" if beholded_flag else " ", "(Worker-{0:0{1}d}): ".format(0, len(str(len_target_data_list))))
			printer.print(msg_m, code=0)
			if td_title not in target_title_set:
				msg = "{0}[E]: This message should not be outputed.".format(" "*8) #このメッセージは表示されるべきでない
				deleted_flag = True
				if pcf_r.normp(pcf_r.abs(td_title)).startswith(tuple(ignore_change_path_list)) and not(pcf_r.normp(pcf_r.abs(td_title)).startswith(tuple(not_ignore_change_path_list))):
					target_tag, ignore_flag, abnormal_flag = "", True, False
					for ignore_update_path_one in ignore_change_path_list:
						if pcf_r.normp(pcf_r.abs(td_title)).startswith(ignore_update_path_one):
							target_tag = " ('{0}' ({1}))".format(
								pcf_r.format(ignore_update_path_one),
								"Single" if os.path.isfile(ignore_update_path_one) else "Multiple"
							)
							break
					msg = "{0}[I]: This file has been deleted or changed. (Differences in this file definition were ignored{1})".format(" "*8, target_tag)
					if beholded_flag: #注視メッセージ
						msg += " (The file path is beholded{0})".format(beholded_target_tag)
				else:
					last_error_flag = True
					msg = "{0}[W]: This file has been deleted.".format(" "*8)
					if beholded_flag: #注視メッセージ
						msg += " (The file path is beholded{0})".format(beholded_target_tag)
				printer.print(msg, code=0)
				error_message_list.extend([msg_gm, msg_m, msg])
				tag_path_list.append((
					td_title, 
					abnormal_flag, 
					beholded_flag, 
					ignore_flag, 
					appended_flag, 
					corrupte_flag, 
					deleted_flag, 
					error_flag,
					td_hash,
					None,
				))
		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}[C]: Search appended file and corrupted file. ({1})".format(" "*0, buffer_for_msg_gm)
		printer.print(msg_gm, code=0)
		#マルチプロセス系 START
		kwargs_source = {
            "beholded_path_list"          : beholded_path_list,
            "not_beholded_path_list"      : not_beholded_path_list,
            "ignore_change_path_list"     : ignore_change_path_list,
            "not_ignore_change_path_list" : not_ignore_change_path_list,
            "regex_ignore_tester"         : regex_ignore_tester,
            "regex_beholded_tester"       : regex_beholded_tester,
            "path_converter"              : pcf_r,
            "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,
		}
		inspect_worker = InspectWorker()
		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",
			})
			inspect_worker.initialization(file_hash_dict=file_hash_dict) #比較辞書格納
			return_one = inspect_worker.inspect_file_hash(**dict(kwargs))
			error_message_list.extend(return_one[0])
			last_error_flag |= return_one[1]
			tag_path_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, initializer=inspect_worker.initialization, initargs=(file_hash_dict,)) 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(inspect_worker.inspect_file_hash, **dict(kwargs)))
					for executer_one in concurrent.futures.as_completed(executer_list):
						return_one = executer_one.result()
						error_message_list.extend(return_one[0])
						last_error_flag |= return_one[1]
						tag_path_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
			gc.collect() #未参照オブジェクトを即座に解放
		#マルチプロセス系 END
	#ファイル出力可否決定
	buffer_for_print_and_dump_list, dump_ok_flag_list = list(), [False for _ in range(5)]
	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(
				CONST.DUMP_MODE_OF_DUMP_REPORT_FILE, 
				"dump report", 
				os.path.join(pcf_s.abs(CONST.DIRECTORY_FOR_DUMP_FILE_PATH), "{0}.{1}".format(CONST.DUMP_REPORT_FILE_NAME, "html" if CONST.DUMP_HTML_MODE else "txt"))
			),
			check_open_wmode(
				CONST.DUMP_MODE_OF_DUMP_SUMMARY_REPORT_FILE, 
				"dump summary report", 
				os.path.join(pcf_s.abs(CONST.DIRECTORY_FOR_DUMP_FILE_PATH), "{0}.{1}".format(CONST.DUMP_REPORT_SUMMARY_FILE_NAME, "html" if CONST.DUMP_HTML_MODE else "txt"))
			),
			check_open_wmode(
				CONST.DUMP_CSV_FILE_NAME, 
				"dump csv", 
				os.path.join(pcf_s.abs(CONST.DIRECTORY_FOR_DUMP_FILE_PATH), "{0}.csv".format(CONST.DUMP_CSV_FILE_NAME))
			),
			check_open_wmode(
				CONST.DUMP_MODE_OF_DUMP_SUMMARY_CSV_FILE, 
				"dump summary csv", 
				os.path.join(pcf_s.abs(CONST.DIRECTORY_FOR_DUMP_FILE_PATH), "{0}.csv".format(CONST.DUMP_CSV_SUMMARY_FILE_NAME))
			),
			check_open_wmode(
				True if CONST.SAVE_INSPECT_FILE_NAME is not None else False, 
				"output result json", 
				os.path.join(pcf_s.abs(CONST.DIRECTORY_FOR_SAVE_FILE_PATH), "{0}.json".format(CONST.SAVE_INSPECT_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 = True
			counter_ft += 1
	except Exception as e:
		msg = "{0}[E]: Unknown exception. ({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 = 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, 
		datetime_str,
		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)
	#要約発表
	printer.print("<<< Summary of system inspect results ({0}Inspect: {1}){2} >>>".format(
		"" if datetime_s is None else "Memorize: {0}, ".format(datetime_s), 
		datetime_str, 
		sub_strings
	), code=2)
	if dirty_flag: #定数ハッシュ無視の場合のみ
		for dirty_log_one in reversed(dirty_tips):
			error_message_list.insert(0, dirty_log_one)
	if len(error_message_list) > 0:
		printer.print("="*60, code=1)
		printer.print("[ Noteworthy message ]", code=1)
		printer.print("-"*60, code=1)
		for em in error_message_list:
			printer.print(em, code=1)
	del error_message_list
	gc.collect() #未参照オブジェクトを即座に解放
	true_tag_path_list = sorted(tag_path_list, key=lambda x: split_path(x[0], char_for_split="/"))
	del tag_path_list
	gc.collect() #未参照オブジェクトを即座に解放
	#重複検査
	set_for_already_check, set_of_already_file = set(), set()
	for t in true_tag_path_list:
		if t[0] in set_for_already_check:
			set_of_already_file.add(t[0])
		set_for_already_check.add(t[0])
	if len(set_of_already_file) > 0:
		raise ValueError("This path has already been taken. ({0})".format(", ".join(["'{0}'".format(u) for u in list(set_of_already_file)])))
	del set_of_already_file, set_for_already_check
	gc.collect() #未参照オブジェクトを即座に解放
	#(パス, 異常フラグ, 注視フラグ, 無視フラグ, 追加フラグ, 差異フラグ, 削除フラグ, エラーフラグ)
	not_normal_tag_path_list = [t for t in true_tag_path_list if (t[1] or t[7]) and not(t[3])]
	if len(not_normal_tag_path_list) > 0:
		printer.print("="*60, code=1)
		printer.print("[ Abnormal file path ({0}) ]".format(len(not_normal_tag_path_list)), code=1)
		printer.print("-"*60, code=1)
		number_and_marker_list = format_number_and_marker_list(number_and_marker_list=CONST.SPECIAL_NUMBER_AND_MARKER_LIST_WHEN_INSPECT) #特殊マーカーの付与に関するリストを変換
		for t in not_normal_tag_path_list:
			buffer = CONST.BASE_MARKER_WHEN_INSPECT if CONST.BASE_MARKER_WHEN_INSPECT is not None and len(CONST.BASE_MARKER_WHEN_INSPECT) == 1 else "・" #基本マーカー
			for f in number_and_marker_list: #特殊マーカー
				buffer = f[1] if t[f[0]] else buffer
			printer.print("{1} {0}".format(t[0], buffer), code=1)
	#最終結果
	printer.print("="*60, code=1)
	if last_error_flag or dirty_flag:
		printer.print("《 Check noteworthy message 》", code=1)
	else:
		printer.print("《 No problem 》", code=1)
	#結果ファイル出力共通処理
	if CONST.DUMP_MODE_OF_DUMP_REPORT_FILE or CONST.DUMP_MODE_OF_DUMP_SUMMARY_REPORT_FILE or CONST.DUMP_CSV_FILE_NAME or CONST.DUMP_MODE_OF_DUMP_SUMMARY_CSV_FILE:
		os.makedirs(pcf_s.abs(CONST.DIRECTORY_FOR_DUMP_FILE_PATH), exist_ok=True)
	#結果REPORTファイルの出力
	if CONST.DUMP_MODE_OF_DUMP_REPORT_FILE and dump_ok_flag_list[0]:
		text_buffer = "\n".join(printer.dump(code_list=[0, 1, 3]))
		write_text = convert_plain_text_to_html_text(plain_text=text_buffer, title=CONST.DUMP_REPORT_FILE_NAME) if CONST.DUMP_HTML_MODE else text_buffer
		write_file_path = os.path.join(
			pcf_s.abs(CONST.DIRECTORY_FOR_DUMP_FILE_PATH),
			"{0}.{1}".format(CONST.DUMP_REPORT_FILE_NAME, "html" if CONST.DUMP_HTML_MODE else "txt")
		)
		with open(write_file_path, "w", encoding="utf-8-sig") as fw:
			fw.write(write_text)
		del text_buffer, write_text, write_file_path
		gc.collect() #未参照オブジェクトを即座に解放
	#結果要約REPORTファイルの出力
	if CONST.DUMP_MODE_OF_DUMP_SUMMARY_REPORT_FILE and dump_ok_flag_list[1]:
		text_buffer = "\n".join(printer.dump(code_list=[1, 2]))
		write_text = convert_plain_text_to_html_text(plain_text=text_buffer, title=CONST.DUMP_REPORT_FILE_NAME) if CONST.DUMP_HTML_MODE else text_buffer
		write_file_path = os.path.join(
			pcf_s.abs(CONST.DIRECTORY_FOR_DUMP_FILE_PATH),
			"{0}.{1}".format(CONST.DUMP_REPORT_SUMMARY_FILE_NAME, "html" if CONST.DUMP_HTML_MODE else "txt")
		)
		with open(write_file_path, "w", encoding="utf-8-sig") as fw:
			fw.write(write_text)
		del text_buffer, write_text, write_file_path
		gc.collect() #未参照オブジェクトを即座に解放
	#結果CSV関連の共通処理
	header_list, body_base_list = list(), list()
	if (CONST.DUMP_MODE_OF_DUMP_CSV_FILE or CONST.DUMP_MODE_OF_DUMP_SUMMARY_CSV_FILE) and problem_of_csv_format_flag:
		header_list = list(CONST.DUMP_CSV_HEADER)[:number_of_csv_column] + ["" for _ in range(number_of_csv_column - len(CONST.DUMP_CSV_HEADER)if len(list(CONST.DUMP_CSV_HEADER)[:number_of_csv_column]) < number_of_csv_column else 0)]
	#結果CSVファイルの出力 #true_tag_path_list
	if CONST.DUMP_MODE_OF_DUMP_CSV_FILE and problem_of_csv_format_flag and dump_ok_flag_list[2]:
		body_base_list = [CONST.DUMP_CSV_BODY_FUNCTION(t) for t in true_tag_path_list]
		write_file_path = os.path.join(
			pcf_s.abs(CONST.DIRECTORY_FOR_DUMP_FILE_PATH),
			"{0}.csv".format(CONST.DUMP_CSV_FILE_NAME)
		)
		with open(write_file_path, mode="w", encoding="utf-8-sig", newline="") as fw:
			csv.writer(fw, quoting=csv.QUOTE_ALL).writerows([header_list]+body_base_list)
		del body_base_list, write_file_path
		gc.collect() #未参照オブジェクトを即座に解放
	#結果要約CSVファイルの出力 #not_normal_tag_path_list
	if CONST.DUMP_MODE_OF_DUMP_SUMMARY_CSV_FILE and problem_of_csv_format_flag and dump_ok_flag_list[3]:
		body_base_list = [CONST.DUMP_CSV_BODY_FUNCTION(t) for t in not_normal_tag_path_list]
		write_file_path = os.path.join(
			pcf_s.abs(CONST.DIRECTORY_FOR_DUMP_FILE_PATH),
			"{0}.csv".format(CONST.DUMP_CSV_SUMMARY_FILE_NAME)
		)
		with open(write_file_path, mode="w", encoding="utf-8-sig", newline="") as fw:
			csv.writer(fw, quoting=csv.QUOTE_ALL).writerows([header_list]+body_base_list)
		del body_base_list, write_file_path
		gc.collect() #未参照オブジェクトを即座に解放
	#結果JSONデータの出力
	if dump_ok_flag_list[4]:
		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_INSPECT_FILE_NAME)), "w", encoding="utf-8-sig") as fw:
			fw.write(
				json.dumps(
					{
                        "CONFIG_HASH"             : save_option,
                        "FILE_MAP_LIST"           : [
                                                        {
                                                            "PATH"      : t[0], #パス
                                                            "ABNORMAL"  : t[1], #異常判定
                                                            "BEHOLDED"  : t[2], #注視判定
                                                            "IGNORE"    : t[3], #無視判定
                                                            "APPENDED"  : t[4], #追加判定
                                                            "CORRUPTED" : t[5], #改変判定
                                                            "DELETED"   : t[6], #削除判定
                                                            "ERROR"     : t[7], #エラー判定
                                                         } for t in true_tag_path_list
                                                     ],
                        "STRUCTURE_STATUS"        : "ERROR" if abnormal_structure_flag_tuple[1] else ("ABNORMAL" if abnormal_structure_flag_tuple[0] else "NORMAL"),
                        "STRUCTURE_ABNORMAL_FLAG" : True if abnormal_structure_flag_tuple[0] or abnormal_structure_flag_tuple[1] else False,
                        "INSPECT_START_DATETIME"  : datetime_str,
                        "INSPECT_FINISH_DATETIME" : datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
                        "CREATE_START_DATETIME"   : datetime_s,
                        "CREATE_FINISH_DATETIME"  : datetime_f,
					},
					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
				)
			)
	return (103 if file_not_found_flag else (104 if different_config_hash_error_flag else 102)) if last_error_flag else (101 if dirty_flag else 0)

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