import os
import re

class AnalyzerError(Exception):
	def __init__(self, message, module=''):
		self._message = message
		self._module = module
	def __str__(self):
		return ' %s: %s' %(self._module, self._message)
		
class FreqCmpAnalyzerError(AnalyzerError):
	def __init__(self, message):
		super(FreqCmpAnalyzerError, self).__init__(message, 'FreqCmpAnalyzer')
		
class FreqCmpAnalyzer(object):
	'''
	This analyzer is used to analyze test results generated by ITAF tool FREQCMP.
	For each error entry marked with '*', if the following criteria are met,
		err < 1 dB, for types other than median error
		err < 0.5 dB or err < 1 dB for thre >= 0.5 dB, for median error
		err - thre < 0.1 dB for thre < 10 dB
		err - thre < 1 dB for thre >= 10 dB
	the error is acceptable.
	If each error entry in each output signal segment (as found in dynamic test cases) 
	is acceptable, the failed case will be then "marked as passed".
	'''
	(IDLE, THRESHOLD, ERROR) = range(3)
	def __init__(self, log_dir):
		self._log_dir = log_dir
		self._log_file = os.path.join(log_dir, 'stderr.txt')
		with open (self._log_file, 'r') as f:
			self._log = f.readlines()
	
	def analyze(self):
		self._parse()
		if not len(self._thresholds) == len(self._errors):
			raise FreqCmpAnalyzerError('Unmatch thresholds and errors\nThresholds are:\n %r\nErrors are:\n %r\n' %(self._thresholds, self._errors))
		mark_as_pass = True
		report = []
		for seg, thre in enumerate(self._thresholds):
			err = self._errors[seg]
			if not set(thre.keys()) == set(err.keys()):
				raise FreqCmpAnalyzerError('Unmatch thresholds:\n%r\nAnd errors:\n%r\nfor segment: %d of output signal' %(thre, err, seg))
			report.append('Output signal segment %d\n' % seg)
			report.append('||Type\t||Threshold\t||Error\t||\t||\n')
			for type in err:
				acceptable = False
				if type != 'median':
					if err[type] < 1.0:
						acceptable = True
				else:
					if err[type] < 0.5 or (thre[type] >= 0.5 and err[type] < 1.0):
						acceptable = True
				if thre[type] < 10.0 and (err[type] - thre[type]) < 0.1:
					acceptable = True
				if thre[type] >= 10.0 and (err[type] - thre[type]) < 1.0:
					acceptable = True
				report.append('| %s\t| %f\t| %f\t| ' %(type, thre[type], err[type]))
				if not acceptable:
					mark_as_pass = False
					report.append('\t\t|\n')
				else:
					report.append('acceptable\t|\n')
		if mark_as_pass:
			report.append('Marked as PASSED.\n\n')
		else:
			report.append('Still Marked as FAILED.\n\n')			
		
		return (mark_as_pass, report)
	
	def _parse(self):
		self._thresholds = []
		self._errors = []
		state = FreqCmpAnalyzer.IDLE
		
		for line in self._log:
			if 'Test metrics:' in line:
				if not state == FreqCmpAnalyzer.IDLE:
					raise FreqCmpAnalyzerError("Entered Threshold Section without leaving another Section")
				state = FreqCmpAnalyzer.THRESHOLD
				seg_buf_thre = {}
				continue
				
			if 'Results:' in line:
				if not state == FreqCmpAnalyzer.THRESHOLD:
					raise FreqCmpAnalyzerError('Found end of Threshold Section without having entered this Section')
				state = FreqCmpAnalyzer.IDLE
				self._thresholds.append(seg_buf_thre)
				continue
				
			if 'Average error' in line:
				if not state == FreqCmpAnalyzer.IDLE:
					raise FreqCmpAnalyzerError("Entered Error Section without leaving another Section")
				state = FreqCmpAnalyzer.ERROR
				seg_buf_err = {}
				continue
				
			if "'*' indicates a test failure point" in line:
				if not state == FreqCmpAnalyzer.ERROR:
					raise FreqCmpAnalyzerError("Found end of Error Section without having entered this Section")
				state = FreqCmpAnalyzer.IDLE
				self._errors.append(seg_buf_err)
				continue				
				
			if state == FreqCmpAnalyzer.THRESHOLD:
				m = re.search("Largest\s+(\S+)\s+power error:\s+\*\s+(\S+)\s+dB", line)
				if m is not None:
					type, tolerance = m.groups()
					tolerance = float(tolerance)
					seg_buf_thre[type] = tolerance

				continue
				
			if state == FreqCmpAnalyzer.ERROR:
				m = re.search("Largest\s+(\S+)\s+power error:\s+\*\s+(\S+)\s+dB", line)
				if m is not None:
					type, error = m.groups()
					error = float(error)
					seg_buf_err[type] = error
				continue
			

