
import os, sys, re, logging
from YANGO import HouseKeeping

#==================================================================
def GenConstFile(IOMapList, Format="ucf", FilePath="./Constraint.ucf", ArchiName=""):
	"""
	Create the specified file path and write constraints instruction according to IOMap elements list.
	"""
	print(("FilePath:", FilePath))
	logging.info("Generate constraint file.")
	with open(FilePath, 'w+') as CFile:
		CFile.write("#"*70)
		CFile.write('\n'+"# CONSTRAINT FILE FOR ARCHITECTURE "+ArchiName)
		CFile.write('\n'+"#")
		CFile.write('\n'+"# {0}".format("This file has been automatically generated by YANGO, the NoC generation tool of ADACSYS - Advanced Acceleration Systems."))
		CFile.write('\n'+"#"*70+'\n')
		for IOMapping in IOMapList:
			CFile.write(IOMapping.FormatedConstraints(Format="ucf"))
		CFile.write('\n'+"#"*70)
		
	return
	
#==================================================================
# Parse user constraints file and return dictionnary of IO types.
#==================================================================
def PortPads(ConstraintsFile):
	"""
	Return dictionnary of IO types.
	Each IO type contain a list of Pads.
	"""
	# Test argument -------------------------------------------
	if not os.path.isfile(ConstraintsFile): 
		logging.error("Constraints file '{0}' isn't a regular file: parse aborted.".format(ConstraintsFile))
		return {}
	# Process file --------------------------------------------
	PortDict = {}
	with open(ConstraintsFile, "r") as CFile:
		Key="?"
		for Line in CFile.readlines():
			# First parse Key for dictionnary (PortName)
			Matched = re.match("^(#)+\s*(=)+(>)+\s*(?P<PortName>[a-zA-Z0-9_?]\w*)\s*$", Line)
			if Matched: 
				Key = Matched.group('PortName')
			else:
				if Key not in PortDict: PortDict[Key]= [] # Create new empty list of IO Pads
				# Now Parse IO line
				Matched = re.match(
'^\s*NET\s*\"?(?P<PadName>[a-zA-Z0-9_]\w*)\"?\s+LOC\s*=\s*\"?(?P<PadID>[a-zA-Z0-9_]\w*)\"?\s*;\s*#+\s*(?P<Comment>.*)$', Line)
				if Matched: 
					PadName = HouseKeeping.Normalize(Matched.group('PadName'))
					PadID   = HouseKeeping.Normalize(Matched.group("PadID"))
					Comment = HouseKeeping.Normalize(Matched.group("Comment").strip('\n \r'))
					Comment=Comment.replace(',','')
					PortDict[Key].append(IOMap(Signal=None, Pad=PadID, Alias=PadName, Comment=Comment))
	return PortDict	
	
#==================================================================
# Parse user constraints file and return dictionnary of IO types.
#==================================================================
		
#======================================================================
class IOMap:
	"""
	IOMap is an object that link a signal with a FPGA IO pad.
	"""
	#----------------------------------------------------------------------
	def __init__(self, Signal=None, Pad=None, Alias="", Comment=""):
		"""
		Initialize attributes.
		"""
		self.Signal  = Signal # a signal object
		self.Pad     = Pad
		self.Alias   = str(Alias)
		self.Comment = str(Comment)
		self.IsClock = False
		self.ClkParam={"Frequency":None}
		
	#----------------------------------------------------------------------
	def IsAssociated(self):
		"""
		Return True if Signal and Pad are specified.
		"""
		return True if self.Signal!=None and self.Pad!=None else False
		
	#----------------------------------------------------------------------
	def FormatedConstraints(self, Format="ucf"):
		"""
		Return formated string representation for this IO constraint.
		Connect signals to pads in a constraints file.
		Return text (str) with syntaxe of specified 'Format'.
		"""
		if Format.lower()=="ucf":
			if self.Pad!=None:
				if self.IsClock:
					SpecName="{0}_CLK".format(self.Signal.Name)
					ClockComments="\n# Clock configuration for {0}".format(self.Signal.Name)
					ClockParams='\nNET "{0}" TNM_NET = "{1}";'.format(self.Signal.Name, SpecName)
					ClockParams+='\nTIMESPEC "TS_{0}"  = PERIOD "{1}" {2}MHz HIGH 50 %;'.format(SpecName, SpecName, self.ClkParam["Frequency"])
					return '\nNET "{0}" LOC="{1}";\t# {2}'.format(self.Signal.OrthoName(), self.Pad, self.Alias+' | '+ self.Comment)+ClockComments+ClockParams
				else:
					return '\nNET "{0}" LOC="{1}";\t# {2}'.format(self.Signal.OrthoName(), self.Pad, self.Alias+' | '+ self.Comment)
			else: 
				return '\n#NET "{0}" LOC="?";\t# {2}'.format(self.Signal.OrthoName(), self.Pad, self.Alias+' | '+ self.Comment)	
				
	#----------------------------------------------------------------------
	def __repr__(self):
		if self.Pad!=None:
			if self.IsClock: 
				return "ClockMap[{0}=>{1}({2})<{3}>]".format(self.Signal.Name if self.Signal!=None else "?" , self.Pad, self.Alias, self.ClkParam["Frequency"])
			else: 
				return "IOMap[{0}=>{1}({2})]".format(self.Signal.Name if self.Signal!=None else "?" , self.Pad, self.Alias)
		else:
			return "IOMap[{0}=>?]".format(self.Signal.Name if self.Signal!=None else "?") 
		
	#----------------------------------------------------------------------
	def __str__(self):
		if self.Pad!=None:
			if self.Signal!=None:
				return "{0}=>{1}({2})".format(self.Signal.Name, self.Pad, self.Alias)
			else:
				return "{0}({1}), {2}".format(self.Pad, self.Alias, self.Comment)
		else:
			return "{0}=>?".format(self.Signal.Name if self.Signal!=None else "?") 
		
#==================================================================
# Main tests.
#==================================================================
if __name__ == "__main__":
	print("#=================================================")
	print("#=        TEST PARSE OF CONSTRAINTS FILE         =")
	print("#=================================================")
	print((PortPads("./ML605.ucf")))
	print("#=================================================")
	print("#=                 END OF TEST                   =")
	print("#=================================================")
	
	

