# Check Capella Requiements
#
# 
# 
# -== Description ==-
# 
# Purpose of this script is to check if:
# - requirements from an reqif file have been imported into a capella model
# - all requirements have been linked to capella model objects
# - requirements imported into capella model have had their xhtml doc generated 
#
#
#
# -== Dependencies ==-
#
# capella 5.1.0
# capella addon vp-requirements
# capella addon xhtml
# python 3.8.x+
#
# pip install platformdirs lxml markupsafe Pillow svgwrite pandas openpyxl textx
# pip install poetry
#
# install capellambse
# git clone https://github.com/DSD-DBS/py-capellambse.git
# cd py-capellambse
# pip install -e .
#
# IMPORTANT NOTE 220127: DO NOT INSTALL THE FOLLOWING IF reqif=0.0.13 strictdoc=0.0.18!!!
# pip install -r contrib\strictdoc\requirements.txt
# pip uninstall reqif
#
# OTHERWISE:
# pip install reqif strictdoc
#
#
# 
# -== How to use ==-
#
# for the parser to work the following capella project files are required
# *.aird, *.capella, *.afm
#
#
# 1. start capella and create a new project (e.g. import the IFES example)
#	 this main folder of this project becomes the project location for "prjpath"
# 2. create an sdoc file in the project location and define requirements  
# 3. open cmd  
#    cd <path>\req_validation
# 4. run cmd: strictdoc export --formats=reqif-sdoc --output-dir=. <filename>.sdoc
#    220127 DEV VERSION of strictdoc and reqif require these commands (update workspace to your path!)
#    python contrib\strictdoc\strictdoc\cli\main.py export --format=reqif-sdoc --output-dir="c:\workspace\IFE_samplemodel.zip_expanded\In-Flight Entertainment System" "c:\workspace\IFE_samplemodel.zip_expanded\In-Flight Entertainment System\IFES-requirements.sdoc"
# 5. import reqif in capella    
#	 Remember to enable vp-requirement addon for this model by doing:
#    Window->Show View->Other->Kitalpha->Viewpoint Manager
#    select Model (left) then right-click "Capella Requirements" and select "Reference"
# 6. allocate req in capella  
# 7. save capella model    
# 8. right-click the capella project and create a "html_docu" folder  
# 9. richt-click the capella model and select "HTML Documentation Generation"
# 10. select the new html_docu folder as "Output Folder" and wait for this process to complete
# 11. run cmd: python map_reqifcapella.py --prjpath "c:\workspace\IFE_samplemodel.zip_expanded\In-Flight Entertainment System"  
#	  if no error occurs it will create a new reqif file in the original folder
# 12. 


import sys
import os
import json
import xml.sax.saxutils as saxutils

print(sys.path)
sys.path.insert(1, os.path.dirname(os.path.abspath(__file__)) + "/../../contrib/py-capellambse")
sys.path.insert(1, os.path.dirname(os.path.abspath(__file__)) + "/../../contrib/strictdoc")
sys.path.insert(1, os.path.dirname(os.path.abspath(__file__)) + "/../../contrib/reqif")

from reqif.parser import ReqIFParser
from reqif.unparser import ReqIFUnparser
from reqif.models.reqif_data_type import ReqIFDataTypeDefinitionString
from reqif.models.reqif_spec_object import SpecObjectAttribute
from reqif.models.reqif_types import SpecObjectAttributeType
from reqif.models.reqif_spec_object_type import SpecAttributeDefinition
from reqif.models.reqif_reqif_header import ReqIFReqIFHeader

import capellambse
import datetime
from enum import Enum
import argparse

import tools.generate_uid as genid
import strictdoc.backend.sdoc.models.capella_data as cdatat

import tools.logging as logging

# ---- DEFAULT VALUES - MAY CHANGE -----

logging.DEBUGGING = True # set True to enable debugging output

# DEVNOTE: for simplicity lets assume that all files are located within the same folder 
# TODO: make file/folder locations configurable
_prjpath = "./testprj_3" #os.getcwd()
#_reqiffile= "Spec10.reqif"
_reqiffile= "reqif/output.reqif"
#_reqiffile= "Sample.reqif"
_capella_aird_file = "In-Flight Entertainment System.aird"
# path to the xhtml docu generated by the XHTML capella addon
# Note: this folder must conntain the following subfolder: css, img, output, scripts
_xhtml_doc_path="html_docu"


# --------- NO CHANGES BEYOND THIS LINE !!!--------



prjpath = None
reqif_file = None
capella_aird_file = None
xhtml_doc_path = None
folderconfig = None

# constant definitions
CAPELLA_REQ_DATATYPE_STR = "Capella.Req"
CAPELLA_ALLOCS_DATATYPE_STR = "Capella.Allocs"
CAPELLA_DELIM_DATATYPE_STR = "Capella.Escjson"

class UID_Gen_Type(Enum):
	GUID = 0	# e.g. 0db13de1-da59-450b-bca9-c2652c2783ee
	EGUID = 1	# e.g. REQUIREMENT-93b1d52c-8b51-4f6a-af46-6f6b2a1db0e3
	EMFID = 2	# e.g. _OV_2SnIWEeycM78HBslL7g
g_reqifid_type = UID_Gen_Type.GUID	# flag indicating the type of id being used in the reqif file (used for reqif data insertion)


# - Processing order:

def map_requirements(_prjpath,_reqif_file,_capella_aird_file,_xhtml_doc_path,_sdoc_doc_path, folder_config):
		
	global xhtml_doc_path
	global capella_aird_file
	global reqif_file
	global prjpath
	global folderconfig
	xhtml_doc_path = _xhtml_doc_path
	capella_aird_file = _capella_aird_file
	reqif_file = _reqif_file
	prjpath = _prjpath
	folderconfig = folder_config

	try:

		# TODO: a better place for this is inside an extra bash or python script which calls this one!
		# --- a sdoc file needs to be converted into a reqif first
		#if get_file_ext(reqif_file) == ".sdoc":
		#	convert_sdoc_reqif(reqif_file)
		#	reqif_file = get_file_no_ext(reqif_file) + ".reqif"

		# --- read reqif file and get all requirement names and ids
		req_regif_list = read_reqif_file(reqif_file)

		# --- read capella file and get all requirement names and ids
		req_capella_list = read_capella_file(capella_aird_file)

		# --- compare names and ids --> stop and output error/notice on missmatch
		#	  TODO: capella uses a GUID/UUID while reqif exported by other tools like Reqif-Studio uses something else
		# --- count links for capella requirements --> output error/notice if unassigned
		# --- generate static relative xhtml link based on UUID of the cappela model
		#	  e.g ./html_docu/output/In-Flight%20Entertainment%20System/4696b5f595ee47099ec12913154d1fb1.html
		#	  alternatively, maybe storing the UUID will be enough?!
		# --- check that a doc file exists for that generated link
		proc_reqif_capella_req(req_regif_list, req_capella_list)

		# --- check if generated link exists in reqif file or add it
		write_reqif_file(reqif_file, req_regif_list)

	except ValueError as ex:
		print("Processing terminated prematurely because:", ex)



# - Function examples:

class common_req:
	# not sure if needed?!?
	# common temporary requirement storage to match capella with reqif and update reqif
	
	def __init__(self, name, robj, id, cobj, uuid):
		self.name = name		# requirement name
		self.reqif_obj = robj	# pointer to reqif data
		self.reqif_id = id		# requif identifier
		self.capella_obj = cobj # pointer to capella data
		self.uuid = uuid		# capella unique identifier		
	


def uuid_compact(uuid):
	return uuid.replace('-','')


# return filename without path and extension
def get_filename_no_ext(file):
	base = os.path.basename(file)
	return os.path.splitext(base)[0]


# return file extension (no filename or path!)
def get_file_ext(file):
	filename, file_extension = os.path.splitext(file)
	return file_extension

# return file with path without the extension
def get_file_no_ext(file):
	filename, file_extension = os.path.splitext(file)
	return filename


# reads and parses reqif file
# returns a list of all requirements contained
def read_reqif_file(file):
	if not os.path.exists(file):
		raise FileNotFoundError("Missing: " + file)

	reqif_bundlea = ReqIFParser.parse(file)
	# determine uid format which be stored in globally accessibale g_reqifid_type   
	global g_reqifid_type
	g_reqifid_type = reqif_get_uid_type(reqif_bundlea)  
	return reqif_bundlea
		



def write_reqif_file(file, reqif_reqs):
	l_ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
	nfn = file + "_" + l_ts + ".bak"
	try:
		os.rename(file, nfn)
	except FileNotFoundError as ex:
		pass # output didn't exist yet, no big deal!
	except FileExistsError:
		pass 
	reqif_content = ReqIFUnparser.unparse(reqif_reqs)
	with open(file, "w", encoding="utf8") as output_file:
		output_file.write(reqif_content)
	



# reads and parses capella file
# returns a list of all requirements contained	
def read_capella_file(file):
	if not os.path.exists(file):
		raise FileNotFoundError("Missing: " + file)

	model = capellambse.MelodyModel(file)	
	
	return model.oa.all_requirements
	



# check if the capella generated xhtml doc exists
def get_capella_xhtmldoc_path(uid):
	# check if the xhtml documentation has been generated for the requirement
	global capella_aird_file, xhtml_doc_path, prjpath
	t_cprj = get_filename_no_ext(capella_aird_file)
	t_comp_cuuid = uuid_compact(uid)
	cdocpath = "/output/" + t_cprj + "/" + t_comp_cuuid + ".html"
	fcdocpath = os.path.join(xhtml_doc_path,"output", t_cprj, t_comp_cuuid + ".html")
	logging.print_info("trying to find capella xhtml doc in " + fcdocpath)
	if not os.path.exists( fcdocpath):
		return None
	else:		
		return "../../../" + folderconfig.docu_systemarchi + cdocpath	# need return relative path from strictdoc html





# - matches requirements between the two lists
# - issues error/notice in case user interaction is required
# - updates reqif file
def proc_reqif_capella_req(reqif_reqs, capella_reqs):	

	if len(capella_reqs) == 0:
		logging.print_error("capella model has no requirements. \n Open capella and import the reqif file")
	else:
		logging.print_info("found " + str(len(capella_reqs)) + " capella requirements")

	reqif_spec = reqif_reqs.core_content.req_if_content.specifications[0]
	rcnt = 0
	for current_hierarchy in reqif_reqs.iterate_specification_hierarchy(reqif_spec):
		rcnt += 1

	if rcnt == 0:
		logging.print_error("reqif contains no requirements.\nCheck reqif file and try again")		
	else:
		logging.print_info("found " + str(rcnt) + " reqif requirements")

	reqif_update_header(reqif_reqs)

	# <---> Loop Capella Requirements
	total_req_cnt = 0
	for creq in capella_reqs:
		found_match = False

		# no point in matching if capella xhtml doc is missing
		cdoc_path = get_capella_xhtmldoc_path(creq.uuid)
		if cdoc_path == None:
			logging.print_error("capella xhtml doc could not found.\nOpen capella and re-generate the xhtml doc into the project folder")
			continue
		else:
			logging.print_info("capella xhtml doc found.")

		# match requirements between reqif and capella 
	
					
		# <---> Loop ReqIF Requirements
		for rreq in reqif_reqs.iterate_specification_hierarchy(reqif_spec): #reqif_reqs:
			
			if rreq.children != None:
				logging.print_info("spec-hierarcy contains "+ str(len(rreq.children)))
				
			#	for rreqc in reqif_reqs.iterate_specification_hierarchy(rreq.children):
			#		rreq_idn = reqif_obtain_requirement_idn(reqif_reqs, rreqc)
			#else:
			rreq_idn = reqif_obtain_requirement_idn(reqif_reqs, rreq)


			logging.print_debug("comparing -C-ID:" + str(creq.uuid) + " -R-ID-:" +str( rreq.identifier) + " -C-LN-:" + str(creq.long_name) + " -R-LN-:" + str(rreq.long_name))
			logging.print_debug("also comp -C-LN-:" + str(creq.long_name) + " and -C-N-:" + str(creq.name) + " with extracted id value: " + str(rreq_idn))
			uuid_match = False # if ids match as well (indicates changes to the model!)




			# trying to match requirements based on string
			# TODO: determine proper matching using name only
            #  Note 220208 - a cappella issue exists that stores the imported reqif but does not
			#				 provide (hides) a useable field containing the "REQ-xxx" string			 
			if (rreq.long_name != None and creq.long_name != None and rreq.long_name == creq.long_name) \
				or (rreq.long_name != None and creq.name != None and rreq.long_name == creq.name) \
				or (rreq.long_name != None and creq.long_name != None and rreq.long_name in creq.long_name)	\
				or (rreq.long_name != None and creq.name != None and rreq.long_name in creq.name )	\
				or (rreq_idn != None and creq.long_name != None and rreq_idn in creq.long_name) \
				or (rreq_idn != None and creq.name != None and rreq_idn in creq.name) \
				or (rreq_idn != None and creq.name != None and rreq_idn.find(creq.name) > -1) \
				or (rreq_idn != None and creq.prefix != None and rreq_idn.find(creq.prefix) > -1) \
				or (hasattr(creq,'identifier') and hasattr(rreq,'spec_object') and rreq.spec_object != None and creq.identifier != None and rreq.spec_object == creq.identifier) \
				or (creq.attributes['IE PUID'] != None and rreq_idn != None and creq.attributes['IE PUID'] == rreq_idn) \
				:
				logging.print_debug("maching req string found")
				found_match = True

				# if uuid is not set or does not match we want to update it the allocations below because something changed!
				if rreq.identifier == creq.uuid \
				:
#					or rreq.refs_capella_req.uui == creq.uuid \
					logging.print_debug("ids also matched")
					uuid_match = True # TODO: not needed: ment to indicate required reqif check and update, but its better to check either way
				
				
				reqif_update_reqif_capella_req_ref(reqif_reqs, rreq, creq, cdoc_path)
					
					
				# check requirement connections in matching reqif
				print(len(creq.requirements))		
				if len(creq.requirements) > 0:
					total_req_cnt = total_req_cnt + 1		

					logging.print_debug("capella req is connected. check for updates")

					for con in creq.requirements:
						print(con.name + " " + con.uuid)

					reqif_update_reqif_capella_alloc(reqif_reqs, rreq, creq)


				else:
					logging.print_warning("requirement(s) with no capella assignments found.\nOpen capella and assign all requirements, then rerun this script")


			if found_match:
				# reqif requirement has been matched there is no need to look further
				logging.print_debug("matching reqif req found for capella req.")	
				break
			else:
				logging.print_debug("NO MATCHING reqif req found for capella req.")

		# TODO: testing - remove
		global prjpath		
		write_reqif_file(prjpath + "/_testout.reqif.txt", reqif_reqs)				
		
		

		if not found_match:
			logging.print_warning("capella model is missing requirements.\nOpen capella and re-import the reqif file")			

				

	



def reqif_update_header(reqif_reqs):

	if reqif_reqs.req_if_header == None:
		reqif_reqs.req_if_header = ReqIFReqIFHeader(
			identifier=reqif_gen_uid(),
			creation_time=datetime.datetime.now(datetime.datetime.now().astimezone().tzinfo).isoformat(),
			title="CapReqSync",
			req_if_tool_id="CapReqSync",
			req_if_version="1.0",
			source_tool_id="CapReqSync",
			repository_id=None,
			comment=None
			)

	
# update the capella allocations in the reqif data structure
#
# example capella output:
# ID: REQ-001 UUID: 4696b5f5-95ee-4709-9ec1-2913154d1fb1
# 3
# REQ-002
# Entertain During Flight
# Perform Flight On-Board Announcements
# ID: REQ-002 UUID: 3291b1d4-7b1e-47e7-94df-3318cce31ccb
# 2
# REQ-001
# Implement a Commercial Strategy
# ID: REQ-003 UUID: 651467eb-5fcc-4e46-b9dd-0b5d4a00183a
# 0	
def reqif_update_reqif_capella_alloc(reqif_reqs, rreq, creq):
	
	if len(creq.requirements) == 0:
		raise ValueError('no allocation to update.') # DevNote: this only happens if calling function is not properly doing santiy checks

	reqobj = reqif_reqs.get_spec_object_by_ref(rreq.spec_object)
	
	alltypeid = reqif_get_capella_sobjtype_id(reqif_reqs, rreq, CAPELLA_ALLOCS_DATATYPE_STR)

	data = reqif_encode_capella_allocs(creq)



	# TODO: this block is the same as for reqif_update_reqif_capella_req_ref ~ line 617

	# the spec object may have multiple attributes of different type
	# have to traverse the list to find if an REQ or ALLOC entry already exists.
	# if it exists the value gets updated, otherwise a new attribute is created and appended
	found_attrib = False
	for attrib in reqobj.attributes:		
		if attrib.attribute_type == alltypeid \
			or attrib.attribute_type.name == alltypeid \
			or attrib.definition_ref == alltypeid:
			attrib.value = data
			found_attrib = True

	if not found_attrib:
		natrib = SpecObjectAttribute(
			attribute_type=SpecObjectAttributeType.STRING,
			definition_ref=alltypeid,
			value=data,
			xml_node=None
			)
		reqobj.attributes.append(natrib)

	# Below code outlines implementation in case of putting all allocs in seperate reqif attributes
	# kept below for future reference

	#if len(reqobj.refs_capella.allocations) == 0 \
	#	or len(reqobj.refs_capella.allocations) != len(calloc) \
	#	:						
	#	for creq_con in calloc:
	#		print(creq_con.name + " ")
	#		match_found = False
	#		for rreq_con in reqobj.refs_capella.allocations:
	#			# TODO check match can be found
	#			if rreq_conf.id == creq_con.uuid:
	#				match_found = True
	#				# TODO: check the rest of the fields and update if required


	#		if not match_found:
	#			# TODO insert capella allocations
	#			reqobj.refs_capella.allocations.append()

	#else:
	#	error_info("capella requirement assignment unchanged")

	



def reqif_gen_uid( str = "", type:UID_Gen_Type=None ):
	global g_reqifid_type
	
	if type == None:
		l_type = g_reqifid_type
	else:
		l_type = type

	if l_type == UID_Gen_Type.GUID:
		return genid.gen_guid()
	elif l_type == UID_Gen_Type.EGUID:
		return str + "-" + genid.gen_guid()
	elif l_type == UID_Gen_Type.EMFID:
		return genid.gen_emf_uuid()
	else:
		logging.print_error("uuid not supported for generation" + g_reqifid_type)




def reqif_get_uid_type(reqif_reqs):
	reqif_spec = reqif_reqs.core_content.req_if_content.specifications[0]  
	gen = reqif_reqs.iterate_specification_hierarchy(reqif_spec)
	rreq = next(gen)
	uid = reqif_reqs.get_spec_object_by_ref(rreq.spec_object).spec_object_type
	if str(uid)[:1] == "_":
		# emf uuid
		g_reqifid_type = UID_Gen_Type.EMFID
	elif len(str(uid).split("-")) == 6:
		g_reqifid_type = UID_Gen_Type.EGUID
	elif len(str(uid).split("-")) == 5:
		g_reqifid_type = UID_Gen_Type.GUID
	else:		
		g_reqifid_type = UID_Gen_Type.GUID
	return g_reqifid_type







class CapellaData:
	def __init__(self, uuid, name, long_name, doc_url):
		self.uuid = uuid
		self.name = name
		self.long_name = long_name
		self.doc_url = doc_url



def reqif_encode_capella_req(creq, cdocpath):	
	# TODO: maybe a json format is the better solution
	#return str(creq.uuid) + ";" + str(creq.long_name) + ";" + str(creq.name) + ";" + cdocpath
    
	cd = CapellaData(creq.uuid, creq.name, creq.long_name, cdocpath)
	out = json.dumps(cd.__dict__)
	out = escape(out)
	return out



def reqif_decode_capella_req(json):
	jsin = unescape(json)
	jss = json.loads(jsin)
	return CapellaData(**js)



def reqif_encode_capella_allocs(creq, ignore_req=False):
	# TODO: maybe a json format is the better solution
	# https://pythonexamples.org/convert-python-class-object-to-json/        
	clst = []
	for alloc in creq.requirements:		
		docpath = get_capella_xhtmldoc_path(alloc.uuid)
		if hasattr(alloc,"long_name") and not ignore_req:
			# if the capella object has a long_name it is very likely an imported requirement!
			cd = CapellaData(alloc.uuid, alloc.name, alloc.long_name, docpath)
		else:
			cd = CapellaData(alloc.uuid, alloc.name, None, docpath)
		clst.append(cd)
	out = json.dumps([ob.__dict__ for ob in clst])
	out = escape(out)
	return out
	


def reqif_decode_capella_allocs(jsonstr):
	jsin = unescape(jsonstr)
	jss = json.loads(jsin)
	out = []
	for js in jss:
		 out.append(CapellaData(**js))
	return out


escape_table = {
		"&": "&amp;",
		'"': "&quot;",
		"'": "&apos;",
		">": "&gt;",
		"<": "&lt;",
		}
unescape_table = {v:k for k, v in escape_table.items()}

def escape(text):
	return saxutils.escape(text, escape_table)

def unescape(text):
	return saxutils.unescape(text, unescape_table)



# update the capella requirement reference in the reqif data structure
# 
def reqif_update_reqif_capella_req_ref(reqif_reqs, rreq, creq, cdocpath):
	
	reqobj = reqif_reqs.get_spec_object_by_ref(rreq.spec_object)
	specobjidx = 0
	specobjfound = False
	for specobj in reqif_reqs.core_content.req_if_content.spec_objects:
		if specobj.identifier == rreq.spec_object:
			specobjfound = True
			break;
		specobjidx += 1
	if not specobjfound:
		raise ValueError("spec object could not be found (id: " + str(rreq.spec_object) + ")")

	if not reqif_capella_data_exists(reqif_reqs,rreq):
		reqif_add_capella_types(reqif_reqs, rreq)

	creqtypeid = reqif_get_capella_sobjtype_id(reqif_reqs, rreq, CAPELLA_REQ_DATATYPE_STR)
		
	data = reqif_encode_capella_req(creq, cdocpath)
	data = cdatat.CapellaData.encode_req(creq, cdocpath)


	# TODO: this block is the same as for reqif_update_reqif_capella_req_ref ~ line 453

	# the spec object may have multiple attributes of different type
	# have to traverse the list to find if an REQ or ALLOC entry already exists.
	# if it exists the value gets updated, otherwise a new attribute is created and appended
	found_attrib = False
	for attrib in reqobj.attributes:		
		if attrib.attribute_type == creqtypeid \
			or attrib.attribute_type.name == creqtypeid \
			or attrib.definition_ref == creqtypeid:
			attrib.value = data
			found_attrib = True
			break

	if not found_attrib:
		natrib = SpecObjectAttribute(
			xml_node=None,
			attribute_type=SpecObjectAttributeType.STRING,
			definition_ref=creqtypeid,
			value=data,
			#enum_values_then_definition_order=None
			)
		reqobj.attributes.append(natrib)
		#reqif_reqs.core_content.req_if_content.spec_objects[specobjidx].attributes.append(natrib)
		pass


	# TODO: add capella req name, id and path to reqif object
	# update the reqif data structure
#	rreq.refs_capella.req.uuid = creq.uuid
#	rreq.refs_capella.req.long_name = creq.long_name
#	rreq.refs_capella.req.doc_relpath = cdocpath
	# TODO add the creq.uuid 
	# TODO add the creq.long_name
    # TODO add the cdocpath



# find and extract a identifier name (string) from the reqif data structure
#
# the RegIF Standard allows for a flexible definition of Requirement Identifiers (uuids or names)
# and differnt tules use different positions and naming rules to add a requiremnt identifier
# this makes finding the proper name for matching more difficult
#
# currently 3 differnt types of files are know
# - ReqIF Studio v10 => tores REQ-x as ATTRIBUTE-VALUE-STRING attribute THE-VALUE in the SPEC-OBJECT, 
#   uses a SPEC-TYPE defintion (Requirement Type -> ReqIF.ForeignID)
#   and uses an unchangable autoincrement for the REQ-x number
# - strictdoc => also stores REQ-x as ATTRIBUTE-VALUE-STRING attribute THE-VALUE in the SPEC-OBJECT
#	uses a SPEC-TYPE definition (REQUIREMENT -> UID)
# - DOORs => does not use REQ but Requirement-x
#   stores Requirement-x inside SPEC-OBJECT attribute LONG-NAME
#	it also stores it inside SPEC-HIERARCHY attribute LONG-NAME (which is what reqif.py uses)
#	
# common resolution path?
# ReqifStudio, DOORS:
# req-if/core-content/req-if-content/specifications/specification/children/spec-hirarchy/object/spec-object-ref --> val = identifier
# Sdoc:
# req-if/core-content/req-if-content/specifications/specification/children/spec-hirarchy/children/spec-hirarchy/object/spec-object-ref --> ::identifier
#
# DOORS:
# req-if/core-content/req-if-content/spec-objects/spec-object(::identifier match) --> ::long-name => Requirement-x
#
# SD:
# req-if/core-content/req-if-content/spec-objects/spec-object(::identifier match)/values/attribute-value-string --> ::the-value => REQ-x 
# if sub elem of .../attribute-value-string/definition/attribute-definition-string-ref == "UID"
#
# ReqifStudio:
# req-if/core-content/req-if-content/spec-objects/spec-object(::identifier match)/values/attribute-value-string --> ::the-value => REQ-x 
# if sub elem of .../attribute-value-string/definition/attribute-definition-string-ref --> val = id, resolved to "ReqIF.ForeignID" following
# req-if/core-content/req-if-content/spec-types/spec-object-type/spec-attributes/attribute-definition-string(::identifier match) --> ::long-name == ReqIf.ForeignID
#
				
			
# >> ATTEMPT 1 (20220113) this check is too simple, because of the different encodings it is required to resolve the spec-object-type to the end!
# (kept for future reference)
#if reqobj.spec_object_type != 'REQUIREMENT': 
#	continue			

# >> ATTEMPT 2 (20220113) using the internal datatype lookup impl of reqif is not able find the key
# reqdt = reqif_reqs.lookup.get_data_type_by_ref(reqobj.spec_object_type) 
			
# >> ATTEMPT 3 (20220113) resolve on our own
# the sdoc may encapsulate requirements inside sections, which also needs to be resolved
def reqif_obtain_requirement_idn(reqif_reqs, rreq ): 
	
	reqobj = reqif_reqs.get_spec_object_by_ref(rreq.spec_object)

	isrequirementtype = False
	l_rreq_idn = None # req identifier name to match with
	rst = None
	#for spectype in reqif_reqs.core_content.req_if_content.spec_types:
	#	# check that  spec_object is a requirement
	#	if spectype.identifier != reqobj.spec_object_type:
	#		continue
	#	else:
	#		logging.print_debug("spec type id found." + str(reqobj.spec_object_type) + " == " + str(spectype.identifier))
					
	#		if spectype.long_name != None \
	#			and (spectype.long_name == "Requirement Type" \
	#			or spectype.long_name == "REQUIREMENT") \
	#			:
	#			isrequirementtype = True
	#			rst = spectype
	#			break
			
	#if isrequirementtype:

	rst = reqif_get_requirement_type(reqif_reqs, rreq)
	if rst != None:
		# spec_object is a requirement, try to extract REQ to match
		isrequirementidname = False
		for robja in reqobj.attributes:
			# need to identifiy the element with the REQ text, which is of type UID, ReqIF.ForeignID or IE PUID
			for spectypeattribdef in rst.attribute_definitions:
				if spectypeattribdef.identifier != robja.definition_ref:
				#robja.name:
					continue
				else:
					logging.print_debug("spec type id found." + str(robja.definition_ref) + " == " + str(spectypeattribdef.identifier))

					if spectypeattribdef.long_name == "ReqIF.ForeignID" \
						or spectypeattribdef.long_name == "UID" \
						or spectypeattribdef.long_name == "IE PUID" \
						:
							isrequirementidname = True
							break

			if isrequirementidname:
				logging.print_debug("got req id name to match: " + robja.value)
				l_rreq_idn = robja.value
				break
	return l_rreq_idn




# check if capella data is already in the reqif structure
# this is identified by containing the capella_req and capella_allocs data types
def reqif_capella_data_exists(reqif_reqs, rreq):
	
	reqtypes = reqif_get_requirement_type(reqif_reqs, rreq)
	for rt in reqtypes.attribute_definitions:
		if rt.long_name == CAPELLA_REQ_DATATYPE_STR \
			or rt.long_name == CAPELLA_ALLOCS_DATATYPE_STR \
			:
			return True
	return False

# get the datatype id for dt
# where dt is DATATYPE_CAPELLA_REQ_STR or DATATYPE_CAPELLA_ALLOCS_STR
def reqif_get_capella_sobjtype_id(reqif_reqs, rreq, dt):
	reqtypes = reqif_get_requirement_type(reqif_reqs, rreq)
	for rt in reqtypes.attribute_definitions:
		if rt.long_name == dt:			
			return rt.identifier
	return None

# check if the capella data type exists
def reqif_capella_datatype_extist(reqif_reqs):

	for datatype in reqif_reqs.core_content.req_if_content.data_types:
		if datatype.long_name == CAPELLA_DELIM_DATATYPE_STR:
			return True
	return False


def reqif_get_capella_datatype_id(reqif_reqs):
	for datatype in reqif_reqs.core_content.req_if_content.data_types:
		if datatype.long_name == CAPELLA_DELIM_DATATYPE_STR:
			return datatype.identifier
	return None




# get list of all requirement type definitions
def reqif_get_requirement_type(reqif_reqs, rreq):

	reqobj = reqif_reqs.get_spec_object_by_ref(rreq.spec_object)

	for spectype in reqif_reqs.core_content.req_if_content.spec_types:
		# check that  spec_object is a requirement
		if spectype.identifier != reqobj.spec_object_type:
			continue
		else:
			logging.print_debug("spec type id found." + str(reqobj.spec_object_type) + " == " + str(spectype.identifier))
					
			if spectype.long_name != None \
				and (spectype.long_name == "Requirement Type" \
				or spectype.long_name == "REQUIREMENT") \
				or "REQUIREMENT" in spectype.long_name \
				:				
				return spectype

	return None			




def reqif_add_capella_types(reqif_reqs, rreq):

	reqobj = reqif_reqs.get_spec_object_by_ref(rreq.spec_object)

	# obtain text datatype id for new spec attribute
	cdatatypeid = reqif_get_capella_datatype_id(reqif_reqs)
	if cdatatypeid == None:
		# add datapyte
		cdatatypeid = reqif_gen_uid("cp")

		ndt = ReqIFDataTypeDefinitionString(
			is_self_closed = False,
			description = None,
			last_change = None,
			long_name = CAPELLA_DELIM_DATATYPE_STR,
			max_length = "32000",
			identifier = cdatatypeid
			)
		reqif_reqs.core_content.req_if_content.data_types.append(ndt)
	

	for spectype in reqif_reqs.core_content.req_if_content.spec_types:
		# check that  spec_object is a requirement
		if spectype.identifier != reqobj.spec_object_type:
			continue
		else:
			logging.print_debug("spec type id found." + str(reqobj.spec_object_type) + " == " + str(spectype.identifier))
					
			if spectype.long_name != None \
				and (spectype.long_name == "Requirement Type" \
				or spectype.long_name == "REQUIREMENT") \
				or "REQUIREMENT" in spectype.long_name \
				:				
				# TODO: SpecObjectAttributeType should match the overall attribute type in use (which might be XHTML in case of Doors)
				creqattrid = reqif_gen_uid("cp")
				spectype.attribute_definitions.append(
					SpecAttributeDefinition(
							xml_node = None,
							attribute_type = SpecObjectAttributeType.STRING,
							description = None,
							identifier = creqattrid,
							last_change = None,
							datatype_definition = cdatatypeid,
							long_name = "Capella.Req",
							editable = None,
							default_value = None,
							multi_valued = None
						)
					)
				callocsattrid = reqif_gen_uid("cp")
				spectype.attribute_definitions.append(
					SpecAttributeDefinition(
							xml_node = None,
							attribute_type = SpecObjectAttributeType.STRING,
							description = None,
							identifier = callocsattrid,
							last_change = None,
							datatype_definition = cdatatypeid,
							long_name = "Capella.Allocs",
							editable = None,
							default_value = None,
							multi_valued = None							
						)
					)	
	

	
if __name__ == '__main__':

	parser = argparse.ArgumentParser()

	parser.add_argument('--prjpath', type=str, default=_prjpath,required=False, help="relative or abs. path of poject folder (e.g. ./myproj)")
	parser.add_argument('--reqiffile', type=str, default=_reqiffile,required=False, help="the name of the reqif (with relative path to ./myproj)")
	parser.add_argument('--airdfile', type=str, default=_capella_aird_file, required=False, help="the name of the capella aird file to use (without path!)")
	parser.add_argument('--caphtml', type=str, default=_xhtml_doc_path, required=False, help="capella generated xhtml docu folder name (without path!)")
	args = parser.parse_args()
		
	prjpath = args.prjpath
	reqif_file = prjpath + "/" + args.reqiffile
	capella_aird_file = prjpath + "/" + args.airdfile
	xhtml_doc_path = prjpath + "/" + args.caphtml

	map_requirements(prjpath,reqif_file,capella_aird_file,xhtml_doc_path)