#!/usr/bin/env python

from __future__ import generators

import os.path
import over_phrases
import xwaves_lab
import xwaves_errs
import random
import die
import oxi_entity
import read_words


DT_LEFT = 0.100
DT_RBEFORE = 0.100
DT_RMIN = 0.200
PNP_W = 0.200
DEBUG = 0

import os
LEX = '%s/LEXICON.fiat' % os.environ['OXIDATA']

class accent(oxi_entity.entity):
	def __init__(self, utt_info, tstar, dt_left, dt_right, label):
		oxi_entity.entity.__init__(self, utt_info, tstar-dt_left, tstar+dt_right)
		self.label = label
		self.tc = tstar
		assert tstar>=self.t0 and tstar<=self.tf, "Sanity check"
		assert self.tf-self.t0 <= 5.0, "Sanity check: %s %s" % (str(self.tf), str(self.t0))

	def info(self):
		d = oxi_entity.entity.info(self)
		d['label'] = self.label
		d['tc'] = self.tc
		return d


class accent_MS(accent):
	__doc__ = """This handles the multi-speaker case where
		an accent has a speaker and a sex, even though
		the dialog has sex.1 sex.2 speaker.1 and speaker.2.
		"""
	def __init__(self, utt_info, tstar, dt_left, dt_right, label, owner):
		accent.__init__(self, utt_info, tstar,
				dt_left, dt_right,
				label)
		self.owner = owner
		info = accent.info(self)
		if int(info['nspeakers']) > 1:
			self.sex = None
			for i in range(int(info['nspeakers'])):
				if info['speaker.%d' % (i+1)] == owner:
					self.sex = info['sex.%d' % (i+1)]
			if self.sex is None:
				raise RuntimeError, "Can't find sex for %s" % owner
		else:
			self.sex = info['sex']


	def info(self):
		d = accent.info(self)
		d['speaker']  = self.owner
		d['sex'] = self.sex
		return d


class accents(oxi_entity.entity_list):
	def __init__(self, uttrec, suffix, ignore = [],
			trash=['#'], root=None, Valid=None):
		"""Constructs an accents object which has self.accents
		(a list of the accents in the file),
		and self.nm (the base name of the data file).
		Valid is a pointer to a module or object that decides
		whether a given data file is relevant to the analysis.
		"""

		oxi_entity.entity_list.__init__(self)
		self.info = uttrec

		if not Valid.generic(uttrec, suffix, 0):
			raise Valid.InvalidUtterance, suffix

		if root is None:
			nm = os.path.join( uttrec['d'], uttrec['f'] )
		else:
			nm = os.path.join( root, uttrec['d'], uttrec['f'] )

		markh, markd = xwaves_lab.read('%s.%s' % (nm, suffix), loose=0)
		markd = read_words.empty_to_star(markd)
		for i in range(len(markd)-1):
			t, lab = markd[i]
			tnext, labnext = markd[i+1]
			if not lab in ignore or labnext in trash or lab in trash:
				dtr = min(DT_RMIN, tnext-t-DT_RBEFORE)
				self.append( accent(uttrec, tstar=t,
								dt_left=DT_LEFT,
								dt_right=dtr,
								label=lab) )


def drop_trash(dl, droplist):
	o = []
	for (t, lab) in dl:
		if not lab in droplist:
			o.append( (t, lab) )
	return o


class PNP(oxi_entity.entity_list):
	def __init__(self, uttrec, suffix, ignore = [],
			trash=['#'], pnpw=PNP_W, rseed=None,
			root=None, Valid=None):
		"""Constructs an accents object which has self.accents
		(a list of the accents in the file),
		and self.info
		Non-prominent locations are chosen randomly between
		prominent locations.   Only prominence marks
		are expected in the incoming data.
		"""

		if not Valid.generic(uttrec, suffix, 0):
			raise Valid.InvalidUtterance, suffix

		assert int(uttrec['nspeakers']) == 1
		oxi_entity.entity_list.__init__(self)
		self.info = uttrec
		rnd = random.Random(rseed)

		if root is None:
			nm = os.path.join( uttrec['d'], uttrec['f'] )
		else:
			nm = os.path.join( root, uttrec['d'], uttrec['f'] )

		markh, markd = xwaves_lab.read('%s.%s' % (nm, suffix), loose=0)
		if len(markd) == 0:	# Empty prominence file
			die.warn("Prominence file exists, but is empty: %s.%s." % (nm, suffix))
			return
		markd = read_words.empty_to_star(markd)
		for i in range(len(markd)):
			t, lab = markd[i]
			if not lab in ignore and not lab in trash:
				self.append( accent(uttrec, tstar=t,
								dt_left=pnpw,
								dt_right=pnpw,
								label=lab) )
		for i in range(len(markd)-1):
			t, lab = markd[i]
			if lab in ignore or lab in trash:
				continue
			tnext, labnext = markd[i+1]
			# if labnext in ignore or labnext in trash:
			if labnext in trash:	# We will put a 'n' in between 'P' and '%'
				continue
			ts0 = t + pnpw/2.0
			ts1 = tnext - pnpw/2.0
			self.append( accent(uttrec, tstar=rnd.uniform(ts0, ts1),
								dt_left=pnpw,
								dt_right=pnpw,
								label='n') )
		self.contents.sort()



def find_labels_in(t0, tf, labellist):
	o = []
	for (t, label) in labellist:
		if t>=t0 and t<=tf:
			o.append( (t, label) )
	return o


def extract_marked_syllables(labellist):
	o = []
	for (t, label) in labellist:
		if label.startswith('P') or label.startswith('n'):
			o.append( (t, label) )
	return o


class PNP2(oxi_entity.entity_list):
	def __init__(self, uttrec, suffix, ignore = [], trash=['#'],
			pnpw=PNP_W, root=None, Valid=None):
		"""Constructs an accents object which has self.accents
		(a list of the accents in the file),
		and self.info
		"""
		import read_lexicon

		if not Valid.generic(uttrec, suffix, 0):
			raise Valid.InvalidUtterance, suffix

		assert int(uttrec['nspeakers']) == 1, "Must have only one speaker: %s" % str(uttrec['nspeakers'])
		oxi_entity.entity_list.__init__(self)
		self.info = uttrec


		if root is None:
			nm = os.path.join( uttrec['d'], uttrec['f'] )
		else:
			nm = os.path.join( root, uttrec['d'], uttrec['f'] )

		die.note('name', nm)
		markh, markd = xwaves_lab.read('%s.%s' % (nm, suffix), loose=0)
		if len(markd) == 0:	# Empty prominence file
			die.warn("Prominence file exists, but is empty: %s.%s." % (nm, suffix))
			return
		markd = read_words.empty_to_star(markd)
		lexicon = read_lexicon.read(LEX)
		debug = []
		for tmp in over_phrases.words(uttrec, Valid=Valid):
			if tmp.word in trash or tmp.word in ignore:
				die.info("Ignoring bad word: %s" % tmp.word)
				continue
			debug.append( (tmp.t0, '[%s' % tmp.word) )
			debug.append( (tmp.tf, '%s]' % tmp.word) )
			die.note('t0', tmp.t0)
			die.note('tf', tmp.tf)
			die.note('word', tmp.word)
			if not lexicon.has_key(tmp.word):
				die.warn('Word not in Lexicon: %s' % tmp.word)
				continue
			nsyl = lexicon[tmp.word].nsyl
			marked = find_labels_in(tmp.t0, tmp.tf, extract_marked_syllables(markd))
			trouble = 0
			nprom = 0
			for (pt, pl) in marked:
				debug.append( (pt, pl) )
				debug.append( (pt-pnpw, '<%s' % pl) )
				debug.append( (pt+pnpw, '%s>' % pl) )
				self.append( accent(uttrec, tstar=pt,
								dt_left = pnpw,
								dt_right = pnpw,
								label = pl)
						)
				nprom += 1

			die.note('nprominent', nprom)
			if trouble:
				die.warn('No nonprominent spots for word=%s: trouble=%d' % (tmp.word, trouble))
			elif nsyl is None:
				die.warn('No nonprominent spots for word=%s: nsyl=None' % tmp.word)
			else:
				tmplist = []
				for i in range(nsyl):
					# Make a temporary list of the default places
					# for syllable centers.
					t = ((i+0.5)/float(nsyl)) * (tmp.tf-tmp.t0) + tmp.t0
					tmplist.append( accent(uttrec, tstar=t,
									dt_left = pnpw,
									dt_right = pnpw,
									label = 'n')
								)
				for (pt, pl) in marked:
					# Throw out the ones that are the closest match
					# to prominence marks.
					die.note('pt, pl', (pt, pl))
					dist = 1e30
					closest = None
					for (i, a) in enumerate(tmplist):
						if abs(a.tc - pt) < dist:
							dist = abs(a.tc - pt)
							closest = i
							die.note('close_word_ctr', a.tc)
					if closest is not None:
						assert dist < 4.0, "Too far!! Dist=%.3f" % dist
						debug.append( (tmplist[closest].tc, 'XDELD') )
						del tmplist[closest]
				for acc in tmplist:
					debug.append( (acc.tc, acc.label) )
				self.extend( tmplist )
				die.info('Extending word %s with %d nonprominent' % (tmp.word, len(tmplist)))
		self.contents.sort()
		debug.sort()
		if DEBUG:
			xwaves_lab.write(open('/tmp/%s.art.lab' % uttrec['f'], 'w'), {}, debug)
		


class SYC(oxi_entity.entity_list):
	def __init__(self, uttrec, suffix, ignore = [], trash=['#'],
			pnpw=PNP_W, root=None, Valid=None):
		"""Constructs an accents object which has self.accents
		(a list of the accents in the file),
		and self.nm (the base name of the data file).
		"""

		assert pnpw < 10.0, "Sanity check: pnpw=%s" % str(pnpw)
		oxi_entity.entity_list.__init__(self)
		self.info = uttrec

		if not Valid.generic(uttrec, suffix, 0):
			raise Valid.InvalidUtterance, suffix

		if root is None:
			nm = os.path.join( uttrec['d'], uttrec['f'])
		else:
			nm = os.path.join( root, uttrec['d'], uttrec['f'])

		die.note('name', nm)
		markh, markd = xwaves_lab.read('%s.%s' % (nm, suffix), loose=0)
		if len(markd) == 0:	# Empty prominence file
			die.warn("Syllable file exists, but is empty: %s.%s." % (nm, suffix))
			return
		assert int(uttrec['nspeakers']) == 1
		markd = read_words.empty_to_star(markd)
		for (pt, pl) in markd:
			if pl in trash or pl in ignore:
				die.warn('Dropping syllable %s at %.3f' % (pl, pt))
			else:
				self.append( accent(uttrec, tstar=pt,
							dt_left = pnpw,
							dt_right = pnpw,
							label = pl)
						)
		self.contents.sort()


class SyllableDetect(oxi_entity.entity_list):
	def __init__(self, uttrec, suffix, ignore = [], trash=['#'],
			pnpw=PNP_W, root=None, Valid=None):
		"""Constructs an accents object which contrasts
		syllables with the spaces between syllables.
		This does not include the edges of an utterance,
		only edges between syllables within the utterance.
		"""

		assert pnpw < 10.0, "Sanity check: pnpw=%s" % str(pnpw)
		oxi_entity.entity_list.__init__(self)
		self.info = uttrec

		if not Valid.generic(uttrec, suffix, 0):
			raise Valid.InvalidUtterance, suffix

		if root is None:
			nm = os.path.join( uttrec['d'], uttrec['f'])
		else:
			nm = os.path.join( root, uttrec['d'], uttrec['f'])

		die.note('name', nm)
		markh, markd = xwaves_lab.read('%s.%s' % (nm, suffix), loose=0)
		if len(markd) == 0:	# Empty prominence file
			die.warn("Syllable file exists, but is empty: %s.%s." % (nm, suffix))
			return
		assert int(uttrec['nspeakers']) == 1
		markd = read_words.empty_to_star(markd)
		last = (None, None)
		for (pt, pl) in markd:
			if pl in trash or pl in ignore:
				die.warn('Dropping syllable %s at %.3f' % (pl, pt))
			else:
				self.append( accent(uttrec, tstar=pt,
							dt_left = pnpw,
							dt_right = pnpw,
							label = pl)
						)
				if last != (None,None):
					self.append( accent(uttrec, tstar=0.5*(pt+last[0]),
							dt_left = pnpw,
							dt_right = pnpw,
							label = 'Edge')
						)
					
		self.contents.sort()


class fakefr:
	def __init__(self, uttrec):
		self.speaker = uttrec['speaker']
	def region_floor_owner(self, t1, t2):
		return self.speaker


class Nuclear(oxi_entity.entity_list):
	def __init__(self, uttrec, suffix, speaker,
			ignore = [], trash=['#'],
			before=DT_LEFT, root=None, Valid=None):
		"""Constructs an accents object which has self.accents
		(a list of the accents in the file),
		and self.nm (the base name of the data file).
		This finds nuclear accents (the last accent before the
		final boundary tone) and defines a region that
		starts a little before the final accent and
		extends to the end of the intonational phrase.
		"""

		import oxi_jc

		oxi_entity.entity_list.__init__(self)
		self.info = uttrec

		if not Valid.generic(uttrec, suffix, 0):
			raise Valid.InvalidUtterance, suffix

		if int(uttrec['nspeakers']) > 1:
			try:
				fr = oxi_jc.floorregions_wd(uttrec, Valid, root=root)
			except Valid.InvalidUtterance:
				pass
			except xwaves_errs.NoSuchFileError:
				pass
			if fr is None:
				die.warn('No .wd[12] data: trying .syn')
				fr = oxi_jc.floorregions_syn(uttrec, Valid, root=root)
			die.info('Floorregions: %s' % str(fr))
		else:
			fr = fakefr(uttrec)
	
		if root is None:
			nm = os.path.join( uttrec['d'], uttrec['f'])
		else:
			nm = os.path.join( root, uttrec['d'], uttrec['f'])

		die.note('name', nm)
		markh, markd = xwaves_lab.read('%s.%s' % (nm, suffix), loose=0)
		if len(markd) == 0:	# Empty prominence file
			die.warn("Syllable file exists, but is empty: %s.%s." % (nm, suffix))
			return
		markd = read_words.empty_to_star(markd)
		lastt = None
		lastl = None
		for (pt, pl) in markd:
			if pl in trash or pl in ignore:
				die.warn('Dropping syllable %s at %.3f: trash/ignore' % (pl, pt))
			elif lastl in trash or lastl in ignore:
				die.warn('Dropping syllable %s at %.3f last trash/ignore' % (pl, pt))
			elif lastl is not None and lastl.endswith('%'):
				die.info('%s at %.3f: follows end mark--no accents in phrase, no pause before.' % (pl, pt))
				pass
			elif lastl is not None and (lastl.startswith('%') or lastl.startswith('*')):
				die.info('%s at %.3f: follows beginning mark--no accents in phrase.' % (pl, pt))
				pass
			elif pl.endswith('%') and lastt is not None:
				owner = fr.region_floor_owner(lastt-before, pt)
				die.info('Owner=%s, speaker=%s' % (str(owner), str(speaker)))
				if owner is not None and owner==speaker:
					self.append( accent_MS(uttrec, tstar=lastt,
							dt_left = before,
							dt_right = pt-lastt,
							label = '%s,%s' % (lastl,pl),
							owner=owner)
							)
					die.info('Acceptable Nuclear Accent: %s %s at %.3f %.3f' %
							(lastl, pl, lastt, pt))
				else:
					die.warn('No owner, wrong owner, or overlapping owners: %s %.3f %s %s' % ( pl, pt, str(owner), speaker))
			elif pl.endswith('%') and lastt is None:
				die.warn('%% with no last: %s at %.3f' % (pl, pt))
			else:
				die.info('Saw %s at %.3f' % (pl, pt))
			lastt = pt
			lastl = pl
		self.contents.sort()

