#!/usr/bin/python3

#	control++ : System configuration tool
#	Copyright (C) 2017-2021 Alexey Appolonov
#
#	This program is free software: you can redistribute it and/or modify
#	it under the terms of the GNU General Public License as published by
#	the Free Software Foundation, either version 3 of the License, or
#	(at your option) any later version.
#
#	This program is distributed in the hope that it will be useful,
#	but WITHOUT ANY WARRANTY; without even the implied warranty of
#	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#	GNU General Public License for more details.
#
#	You should have received a copy of the GNU General Public License
#	along with this program.  If not, see <http://www.gnu.org/licenses/>.

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

import os
import argparse
import subprocess
from common       import MODES, Const, CatchExc
from mode_desc    import ModeDesc
from test_env     import TestEnv
from ax.printer   import Printer

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Parsing the arguments

argparser = argparse.ArgumentParser()
argparser.add_argument(
	'-l', '--list',
	type=str, choices=['white', 'black'], required=True,
	help='Type of list (white | black)'
	)
argparser.add_argument(
	'-i', '--indent',
	action='store_true',
	help='Add indent before each print operation'
	)
argparser.add_argument(
	'-c', '--clear',
	action='store_true',
	help='Clear everything'
	)
argparser.add_argument(
	'-m', '--mode',
	metavar='MODE', type=str,
	choices=MODES, required=True,
	help=f'Execution mode ({" | ".join(MODES)})',
	)
args = argparser.parse_args()

# Initialize a namespace containing string constants
const = Const(args.mode)

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Defines

WHITELIST = (args.list == 'white')
LIST = args.list + 'list'
SUB_MODE = 'test_' + LIST
LIST_PATH = os.path.join(const.CONF_DIR, SUB_MODE + '.txt')
LIST_FILES = [path for path in const.REG_FILES_ODD if path in const.INSIDE_FILES]
MODE_SETTING = const.PERM_MODE_SETTING + [SUB_MODE, '--plain']
MODE_RESETTING = const.PERM_MODE_SETTING + ['--reset', '--plain']

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Initialising the printer

printer = Printer(plain=True, silent=False, block_printing=False)

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Helper functions and classes

class BWList():

	def __init__(self, list_path, file_paths, base_dir):

		self.path = list_path
		self.rel_paths = \
			[path[len(base_dir):] for path in file_paths if path.find(base_dir) == 0] \
			if base_dir else file_paths

	# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

	def __Stringify(self):

		return '\n'.join(self.rel_paths)

	# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

	@CatchExc
	def Write(self):

		with open(self.path, 'w') as f:
			f.write(self.__Stringify())

	# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

	@CatchExc
	def Clear(self):

		os.remove(self.path)

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

def Run(command):

	if args.mode == 'check':
		command.append('--running_in_build_env')

	printer.LineBegin("Running '" + ' '.join(command) + "'")
	printer.LineEnd('[IN PROGRESS]')

	res = subprocess.run(command)

	printer.LineRestart()

	if res.returncode == 0:
		printer.LineEnd('[V]')
		return True

	printer.LineEnd('[X]')

	return False

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

if __name__ == '__main__':

	# Preparing the dir for performing the tests on it
	test_env = TestEnv(const.TEST_DIR, const.TEST_FILES, const.EXEC_DIR,
		recreate=False, printer=printer)
	if not test_env.Prepare():
		exit(1)

	# Prepearing the (sub) mode description
	desc = ModeDesc(os.path.join(const.UNIT_PERM_DESC, SUB_MODE),
		const.UNIT_PERM_DESC)
	desc.NewSec(LIST)
	desc.AddField('path = ' + LIST_PATH)
	desc.AddField('base_dir = ' + const.BASE_DIR)
	desc.AddField('mode_for_dirs = *********')
	if not desc.Write():
		exit(1)

	# Prepearing the white/black list
	bw_list = BWList(LIST_PATH, LIST_FILES, const.BASE_DIR)
	if not bw_list.Write():
		exit(1)

	# Running white/black list modesetting
	if not Run(MODE_SETTING):
		exit(1)

	# Checking permissions of the files, all the files were executable beforehand
	for cur_dir, sub_dirs, reg_files in os.walk(const.TEST_DIR):
		all_files_of_dir = [os.path.join(cur_dir, f) for f in (sub_dirs + reg_files)]
		for file_name in all_files_of_dir:
			printer.LineBegin('Checking ' + file_name)
			file_mode = os.stat(file_name).st_mode
			file_exec_perm = bool(file_mode & const.EXEC_BITS)
			printer.LineAddExtra('Exec perm bit is ' + ('on' if file_exec_perm else 'off'))
			error = False
			# If file not in a base dir or file is a dir (not a regular file)
			if (file_name in const.OUTSIDE_FILES) or \
					(file_name in const.DIR_FILES):
				if not file_exec_perm:
					error = True
			# If file is a regular file
			elif file_name in const.REG_FILES:
				if WHITELIST:
					# Exec although not in a whitelist, or
					#	not exec although in a whitelist
					if (file_exec_perm and file_name not in LIST_FILES) or \
							(not file_exec_perm and file_name in LIST_FILES):
						error = True
				else:
					# Exec although in a blacklist, or
					#	not exec although not in a blacklist
					if (file_exec_perm and file_name in LIST_FILES) or \
							(not file_exec_perm and file_name not in LIST_FILES):
						error = True
			# Unknown file
			else:
				error = True
			if error:
				printer.LineEnd('[X]')
				exit(1)
			else:
				printer.LineEnd('[V]')

	# Resetting the mode and removing tmp files (black/white list will be
	# removed every time, whereas mode desc and testing dir wil be removed
	# only when the '--clear flag is passed', because they might be needed
	# at future mode settings)
	if not bw_list.Clear() or (
			args.clear and (
				not Run(MODE_RESETTING) or
				any(not obj.Clear() for obj in (desc, test_env))
				)):
		exit(1)

	exit(0)
