#!/usr/bin/python3

#	cve-manager : CVE management tool
#	Copyright (C) 2017-2025 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/>.

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

from os                   import cpu_count
from types                import SimpleNamespace
from ax.filesystem        import PrepareDir
from cve_manager.conf     import COMMON_SEC
from cve_manager.parallel import Parallel
from cpe_map.const        import M_NAME, M_BIN_NAME
from cpe_map.init         import Init
from cpe_map.common       import BlankArgParser, NewArgParser, \
	CompatibleGroups, MatchesStr
from cpe_map.control      import TerminateIfAbsent, TerminateOrSkip
from cpe_map.name_conv    import NAME_GROUP, NAME_HASH0, NAME_HASH1, \
	NAME_HASH2, NAME_NUMERICAL_PART, NAME_GOT_LIB_PREFIX_OR_SUFFIX, \
	NAME_GOT_SPEC_PREFIX_OR_SUFFIX

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Forming a set of arguments

argparser = BlankArgParser()
argparser.add_argument(
	'--bin',
	action='store_true',
	help='Perform matching of binary package names'
	)
argparser.add_argument(
	'--prepare',
	action='store_true',
	help='Prepare supplementary data even if it\'s been prepared before'
	)
argparser = NewArgParser(base=argparser, ptype='p')

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Get matches between our name and their names

KEYS_TO_TRY = [
	NAME_NUMERICAL_PART,
	NAME_GOT_LIB_PREFIX_OR_SUFFIX,
	NAME_GOT_SPEC_PREFIX_OR_SUFFIX,
	]

def GetMatchesForName(our_name, their_names):

	matches = {}

	def Cond0(their_name):
		return CompatibleGroups(our_name[NAME_GROUP], their_name[NAME_GROUP])

	def Cond1(their_name):
		return our_name[NAME_HASH2] == their_name[NAME_HASH0]

	def Cond2(their_name):
		return our_name[NAME_HASH1] == their_name[NAME_HASH1]

	def Cond3(their_name):
		return our_name[NAME_HASH2] == their_name[NAME_HASH2]

	for _their_name, their_name in their_names.items():

		score = 1.0

		if not Cond0(their_name) or (
				not Cond1(their_name) and
				not Cond2(their_name) and
				not Cond3(their_name)):
			continue

		for k in KEYS_TO_TRY:
			if our_name[k] != their_name[k]:
				# A situation where there is a numerical part in a product name,
				# but not in a package name, is unacceptable
				if k == NAME_NUMERICAL_PART and their_name[k]:
					score = None
					break
				score -= 0.1

		if score:
			matches[_their_name] = score

	return matches

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Checking for partial name match

def GetMatches(our_names, extra_params, send_pipe, _):

	matches = {}

	for _our_name, our_name in our_names.items():
		matches_for_our_name = GetMatchesForName(
			our_name, extra_params.their_names)
		if matches_for_our_name:
			matches[_our_name] = MatchesStr(matches_for_our_name)

	send_pipe.send(matches)

	return

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Forming dict of complete package names matches

if __name__ == '__main__':

	# Parsing the args and getting the helper objects
	args = argparser.parse_args()
	type_of_matching = M_BIN_NAME if args.bin else M_NAME
	conf, speaker, mediator = Init(type_of_matching, args)

	# Checking cve-manager home dir
	common_params, = conf.Get([COMMON_SEC])
	home_dir, err = PrepareDir(common_params.get('download'))
	if not home_dir:
		speaker.Status(err=err)
		exit(1)

	# Getting package names
	mx_of_our_names = mediator.QueryPackages(args.packages, args.names,
		cpu_count())
	TerminateIfAbsent(mx_of_our_names)

	# For every type of selected data source (nvd, fstec)
	for data_source in args.data_sources:

		# Getting product names
		their_names = mediator.QueryProducts(data_source)
		if TerminateOrSkip(their_names):
			continue

		# Gathering extra params for GetMatches func
		extra_params = SimpleNamespace(their_names=their_names)

		# Running multiple processes of matching
		speaker.Op(data_source, mx_of_our_names, their_names)
		matches, ok = Parallel(GetMatches, mx_of_our_names, extra_params)
		if not ok:
			speaker.Status(err='Some process has terminated with an error')
			exit(1)
		speaker.Status(matches=matches)

		# Updating the table with the results
		if not mediator.SendMatches(data_source, matches):
			exit(1)

	exit(0)
