#!/usr/bin/python3

#	cve-manager : CVE management tool
#	Copyright (C) 2017-2022 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 subprocess
from sys                     import argv
from cve_manager.common      import GetDef, Init
from cve_manager.conf        import DB_CON_SEC
from cve_manager.defines     import M_CHOICES, M_COMPLETE, M_PARTIAL
from cve_manager.intf_common import ErrEncode, ERR_MAX
from cpe_map.common          import BlankArgParser, NewArgParser
from cpe_map.groups          import Groups
from cpe_map                 import prepare_tables_for_products
from cpe_map                 import prepare_tables_for_packages
from cpe_map                 import prepare_tables_for_matches

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

EXT_M_CHOICES = M_CHOICES + ['all']

argparser = BlankArgParser()
argparser.add_argument(
	'-m', '--matching',
	metavar='TYPE_OF_MATCHING', type=str, nargs='+',
	choices=EXT_M_CHOICES,
	help='Type of matching that must be performed '
	f'({", ".join(EXT_M_CHOICES[:-1])}, or {EXT_M_CHOICES[-1]})'
	)
argparser.add_argument(
	'-c', '--makechoice',
	action='store_true',
	help='Map somewhat matched packages'
	)
argparser.add_argument(
	'-r', '--recreate',
	action='store_true',
	help='Recreate supplementary tables for the given branches'
	)
argparser = NewArgParser(base=argparser, ptype='bp')
args = argparser.parse_args()

if not args.matching:
	args.matching = []
elif 'all' in args.matching:
	args.matching = M_CHOICES

checklist = {target: False for target in args.matching}

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Terminate with a return code formed from a list of those types of mapping that
# have been requested but have not been successfully used

def Err(returncode=None):

	return exit(returncode) if returncode != None else \
		exit(ErrEncode(checklist, M_CHOICES))

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Initializing a printer and getting a connection configuration

printer, conf = Init(args)
mysql_config, = conf.Get([DB_CON_SEC])

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Run a module to perform a matching or to make a mapping choice

def RunModule(module, params=[]):
	command = [argv[0] + '-' + module] + params
	printer.LineEnd(f'\nRunning "{" ".join(command)}"')
	sp = subprocess.run(command)
	if not sp or sp.returncode != 0:
		Err()
	return

def Param(flag, value):
	if isinstance(value, bool):
		return [flag] if value else []
	if isinstance(value, list):
		return [flag] + value if value else []
	return [flag, value] if value else []

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

if __name__ == '__main__':

	if len(argv) < 2:
		argparser.print_help()
		Err(ERR_MAX)

	# Forming a list of branches that will be processed
	args.branches, err = conf.SelectBranches(args.branches)
	if err or not args.branches:
		printer.Err(err if err else 'Can\'t form a list of branch names')
		Err(ERR_MAX)

	# Filtering excluded data sources
	args.data_sources, _ = conf.SelectDataSources(args.data_sources)

	# There is a need only in tables with intermediate results if there will be
	# only choice making, no matching procedures
	if args.matching:
		conf_dir = GetDef('CONF_DIR', args.debug)
		# Preparing a dict of groups that is used to prevent matches between
		# products and packages of different groups
		groups = Groups(conf_dir, mysql_config, printer)
		if not groups.Prepare():
			Err(ERR_MAX)
		# Preparing tables for (united) product names and their timelines
		products = prepare_tables_for_products.Worker(conf_dir, mysql_config,
			printer, args.data_sources)
		if not products.Prepare(groups, args.recreate):
			Err(ERR_MAX)
		# Preparing tables for (united) package names and their timelines
		packages = prepare_tables_for_packages.Worker(mysql_config, printer,
			args.branches)
		if not packages.Prepare(groups, args.recreate):
			Err(ERR_MAX)

	# Preparing tables for intermediate results
	matches = prepare_tables_for_matches.Worker(mysql_config, printer,
		args.data_sources, args.branches)
	if not matches.Prepare(args.recreate):
		Err(ERR_MAX)

	# Params that used both for matching and for choice making
	common_params = Param('--names', args.names) + \
		Param('--packages', args.packages) + \
		Param('--noupdate', args.noupdate) + \
		Param('--silent', args.silent) + \
		Param('--plain', args.plain) + \
		Param('--debug', args.debug)

	# Running modules of different types of matching one by one
	for m in args.matching:
		for data_source in args.data_sources:
			params = ['--data_sources', data_source]
			# Running same exe for bin types of matching but with '--bin' flag
			for src_m in (M_COMPLETE, M_PARTIAL):
				if src_m in m and src_m != m:
					m = src_m
					params.append('--bin')
			RunModule(m, params + common_params)
			checklist[m] = True

	# Making a mapping choices
	if args.makechoice:
		for data_source in args.data_sources:
			params = ['--data_sources', data_source] + \
				Param('--branches', args.branches)
			RunModule('choice', params + common_params)

	exit(0)
