#!C:/Python22 # <--Works only in Unix-like systems

# Written by Suzanne on 02/23/2003

# To load this module the first time, enter the following:
#       from pythonTest import *
# To load this module subsequent times, enter the following:
#       reload(pythonTest)
#       from pythonTest import *

# Useful Python commands
# - dir([module]) - list functions and other names this module contains
#     (either defined or as imports)
# - help([module]) or help([function]) - list help on function(s)
#     this module defines

from pySound3D import * # for spatial sound
from pyOpenAL import * # for spatial sound
from pyTTS import *    # for text-to-speech
import string
from string import *
import os, sys, time   # for system utils (not sure what these are for Windows)
from Tkinter import *  # for GUIs (used only as a test, _not_ for our system!)
#from Numarray import * # for 2d arrays
from status import *   # for status bar
import re		#for reg exps
from re import *

def tryBasicDataStructures():
	""" Try basic data structures: list, tuple, dictionary. """
	mylist = ['Python', ' ', 'is', ' ', 'a', ' ',
			 'really', ' ', 'cool', ' ', 'language']
	for s in mylist:
		print s
	
	mytuple = (1, 'potato', 2, 'potato', 3, mylist)
	for s in mytuple:
		print s
	
	s1, s2, s3, s4, s5, s6 = mytuple
	print s1, s2, s3, s4, s5, s6
	
	mydictionary = {'Suzanne': 709231255, 'Pythagorus': 339441655,
					'Sir Readalot': 101010101}
	for a,b in mydictionary.items():
		print a,b

def tryAltFunctionalTools():
	""" Try using the list comprehensions to sub for 'map', 'filter', and 
	'lambda' """
	strings = ['  hi ', '  there  ', '  everyone  ', '  a ', ' e ', 'i  ']
	print strings
	print 'applying alternative to map...'
	strings = [s.strip() for s in strings]
	print strings
	print 'applying alternative to filter...'
	strings = [s for s in strings if len(s)>=2]
	print strings

def tryFunctionalTools():
	""" Try using the functional programming tools 'map', 'filter', 'reduce', and
	'lambda' """
	strings = ['  hi ', '  there  ', '  everyone  ', '  a ', ' e ', 'i  ']
	print strings
	print 'applying map...'
	strings = map(string.strip, strings)
	print strings
	print 'applying filter...'
	strings = filter((lambda s : len(s)>=2), strings)
	print strings
	print 'applying reduce...'
	strings = reduce((lambda a,b : a + b), strings)
	print strings

def valueToString(i):
	""" Convert value i to a string and return it. """
	# Four different ways!
	#return repr(i)
	#return str(i) # truncates the most of all options
	return `i`
	#return object.__str__(i)

def makeWidget():
	""" Display a 'Hello, world' widget. """
	widget = Label(None, text='Hello, world')
	widget.pack()
	widget.mainloop()
		
################################################################################    
# String utility methods.

DELIMITERS = [' ','\n','\r','\t','!',',','?','.','"']
COMMENT_CHAR = '#'

def toStringList(stringList):
	s = ''
	for i in range(0,len(stringList)):
		s = s + stringList[i]
		if i < len(stringList)-1:
			s = s + ', '
	return s

def listToString(listIn):
	s = ''
	for i in range(len(listIn)):
		s = s + listIn[i]
	return s

def equalsIgnoreCase(string1, string2):
	return lower(string1)==lower(string2)
	
def removeItemsFromList(words, items=[' ','\n','\t','!',',','?','.','"']):
	wordsOut = []
	for i in range(0,len(words)):
		equal=0
		for j in range(0,len(items)):
			if (equalsIgnoreCase(words[i], items[j])):
				equal=1
				break
		if (equal==0):
			wordsOut.append(words[i])
	return wordsOut

def removeSpacesFromList(words):
	return removeItemsFromList(words, [' '])
	
def isSpace(word):
	for i in range(len(word)):
		if word[i] != ' ':
			return 0
	return 1

def isDelimiter(word, delimiters=DELIMITERS):
	for j in range(len(delimiters)):
		if lower(word) == lower(delimiters[j]):
			return 1
	return 0
	
###########
# TO TEST #
###########
def isComment(word):
	if (word[0:len(COMMENT_CHAR)] == COMMENT_CHAR):
		return 1
	return 0
	
###########
# TO TEST #
###########
def isHeader(word):
	if (word[0] == "<" and word[len(word)-1] == ">"):
		return 1
	return 0
	
###########
# TO TEST #
###########
def getHeader(word):
	if not isHeader(word):
		return 'NULL'
	else:
		return word[1:len(word)-1]
	
def shortenSpacesToOneSpace(words):
	newWords = []
	for i in range(len(words)):
		if (isSpace(words[i])==1 and (i==len(words)-1 or isSpace(words[i+1])==0)):
			newWords.append(' ')
		elif (isSpace(words[i])==0):
			newWords.append(words[i])
	return newWords
	
def removeLeadingSpacesFromList(words):
	newline = 0
	newWords = []
	for i in range(len(words)):
		if (newline==0 or isSpace(words[i])==0):
			newWords.append(words[i])
		if (newline==1):
			newline = 0
		word = words[i]
		if (len(word)>=1 and word[len(word)-1]=='\n'):
			newline = 1
	return newWords
	
def removeEmptyStringsFromList(words):
	return removeItemsFromList(words,[''])
	
def insertSpacesIntoLines(linesIn):
	""" Insert spaces between strings of list linesIn, and return the result. """
	linesOut = []
	for i in range(0,len(linesIn)):
		linesOut.append(insertSpacesIntoLine(linesIn[i]))
	return linesOut
	
def insertSpacesIntoLine(lineIn):
	""" Insert spaces between strings of list lineIn, and return the result. """
	lineOut = []
	for i in range(0,len(lineIn)):
		lineOut.append(lineIn[i])
		lineOut.append(' ')
	return lineOut

def listToLowerCase(listIn):
	listOut = []
	for i in range(len(listIn)):
		listOut.append(lower(listIn[i]))
	return listOut
	
def findFirstDelimiter(line, delimiters):
	""" Return the index of the first delimiter that appears in line. """
	minIndex = len(line)
	tokenSize = 0
	lowerLine = lower(line)
	for i in range(0,len(delimiters)):
		index = find(lowerLine, delimiters[i])
		if -1 < index < minIndex:
			minIndex = index
			tokenSize = len(delimiters[i])
	return [minIndex, tokenSize]

def tokenizeLine(line, delimiters=DELIMITERS, returnTokens=1):
	""" Tokenize a line. """
	words = []
	while len(line) > 0:
		[tokenIndex, tokenSize] = findFirstDelimiter(line, delimiters)
		if tokenIndex < 0:
			words = words + [line]
			break
		if tokenSize >= 0:
			words = words + [line[0:tokenIndex]]
			if (returnTokens>=1):
				words = words + [line[tokenIndex:tokenIndex+tokenSize]]
		line = line[tokenIndex+tokenSize:len(line)]
	return words

def tokenizeLines(lines, delimiters=DELIMITERS, returnTokens=1):
	""" Tokenize a list of lines. """
	words = []
	for i in range(0,len(lines)):
		words = words + tokenizeLine(lines[i], delimiters, returnTokens)
	return words

def tokenizeFile(txtFile, delimiters=DELIMITERS, returnTokens=1):
	""" Tokenize the string contents of a TXT file. """
	return tokenizeLines((file(txtFile, 'r')).readlines(), delimiters, returnTokens)

def removeWordsFromWordList(listIn, listDictionary, showStatusBar=0):
	""" Remove words from listIn that appear in listDictionary, and return the result. """
	if (showStatusBar >=1):
		d = ThreadedStatusProgressDialog("Removing words...", "", len(listIn))
	listOut = []

	for i in range(0,len(listIn)):
		equal=0
		for j in range(0,len(listDictionary)):
			if (lower(listDictionary[j])==lower(listIn[i])):
				equal=1
				break
		if (equal==0):
			listOut.append(listIn[i])
		if (showStatusBar>=1):
			d.Tick()
	if (showStatusBar>=1):
		d.Close()
	return listOut

def dictTokenizer(line):
	""" Split a line into the word (one string) and its phonemes (another string). """
	words = []
	lowerLine = lower(line)
	index = find(lowerLine, ' ');
        words = [line[0:index], line[index+1:len(line)-1]]
        return words
	

def createDictionary(filename, showStatusBar=0):
	"""Create a hash table out of the phoneme list and return the results"""
	hash = {}

   
        file = open(filename, 'r')
        while 1:
		line = file.readline()
		if not line:
			break
		else:
			tokenizedline = dictTokenizer(line)
			tokenizedline[0] = lower(tokenizedline[0])

			#fixing up the phonemes so they sound better
			tokenizedline[1] = replace(tokenizedline[1], 'AA', 'AW')
			tokenizedline[1] = replace(tokenizedline[1], 'AE', 'A')
			tokenizedline[1] = replace(tokenizedline[1], 'AH', 'UH')
			tokenizedline[1] = replace(tokenizedline[1], 'AO', 'AW')
			tokenizedline[1] = replace(tokenizedline[1], 'AY', 'I')
			tokenizedline[1] = replace(tokenizedline[1], 'B', 'B')
			tokenizedline[1] = replace(tokenizedline[1], 'CH', 'CH')
			tokenizedline[1] = replace(tokenizedline[1], 'D', 'D')
			tokenizedline[1] = replace(tokenizedline[1], 'DH', 'DH')
			tokenizedline[1] = replace(tokenizedline[1], 'EH', 'EH')
			tokenizedline[1] = replace(tokenizedline[1], 'ER', 'ER')
			tokenizedline[1] = replace(tokenizedline[1], 'EY', 'E')
			tokenizedline[1] = replace(tokenizedline[1], 'F', 'F')
			tokenizedline[1] = replace(tokenizedline[1], 'G', 'G')
			tokenizedline[1] = replace(tokenizedline[1], 'HH', 'H')
			tokenizedline[1] = replace(tokenizedline[1], 'IH', 'IH')
			tokenizedline[1] = replace(tokenizedline[1], 'IY', 'E')
			tokenizedline[1] = replace(tokenizedline[1], 'JH', 'GEE')
			tokenizedline[1] = replace(tokenizedline[1], 'K', 'K')
			tokenizedline[1] = replace(tokenizedline[1], 'L', 'L')
			tokenizedline[1] = replace(tokenizedline[1], 'M', 'M')
			tokenizedline[1] = replace(tokenizedline[1], 'N', 'N')
			tokenizedline[1] = replace(tokenizedline[1], 'NG', 'NG')
			tokenizedline[1] = replace(tokenizedline[1], 'OW', 'O')
			tokenizedline[1] = replace(tokenizedline[1], 'OY', 'OY')
			tokenizedline[1] = replace(tokenizedline[1], 'P', 'P')
			tokenizedline[1] = replace(tokenizedline[1], 'R', 'R')
			tokenizedline[1] = replace(tokenizedline[1], 'S', 'S')
			tokenizedline[1] = replace(tokenizedline[1], 'SH', 'SH')
			tokenizedline[1] = replace(tokenizedline[1], 'T', 'T')
			tokenizedline[1] = replace(tokenizedline[1], 'TH', 'TH')
			tokenizedline[1] = replace(tokenizedline[1], 'UH', 'UH')
			tokenizedline[1] = replace(tokenizedline[1], 'UW', 'OO')
			tokenizedline[1] = replace(tokenizedline[1], 'V', 'V')
			tokenizedline[1] = replace(tokenizedline[1], 'W', 'W')
			tokenizedline[1] = replace(tokenizedline[1], 'Y', 'Y')
			tokenizedline[1] = replace(tokenizedline[1], 'Z', 'S')
			tokenizedline[1] = replace(tokenizedline[1], 'ZH', 'ZH')

			
			tokenizedline[1] = lower(tokenizedline[1])
			tokenizedline[1] = tokenizedline[1]+' '
			
			#long vowel pronounciation
			matchstr = re.compile(r"""(e|i|o)(\d*)(\s+)(\d*)(\w+)(\d*)(\s+)""",re.DOTALL|re.VERBOSE)
			tokenizedline[1] = matchstr.sub(r'\1\2\3\4\5 e \6\7', tokenizedline[1])
			
			#change w at beginning of word to 'wha' sound
			matchstr = re.compile(r"""\b(w)(\d*)(\s+)""",re.DOTALL|re.VERBOSE)
			tokenizedline[1] = matchstr.sub(r'\1h\2\3', tokenizedline[1])
			
			#mi is pronounced miles, change to 'my'
			if (replace(tokenizedline[1], ' ', '') == 'mi1'):
				tokenizedline[1] = 'my'
					
			hash[tokenizedline[0]] = tokenizedline[1]

	file.close()
	return hash


def changeWordsToPhonemes(listIn, hash, pronounceable):
	""" Changes each word in listIn into its phoneme representation,
	  and returns the result """
	listOut = []
	
	#get the phonemes in the word
	for i in range(0,len(listIn)):
		word = listIn[i]
		word = lower(word)
		if hash.has_key(word):
			result = hash[word]
			
			#removing lexical emphasis, spaces between phonemes
			if (pronounceable):
				result = replace(result, '0', '')
				result = replace(result, '1', '')
				result = replace(result, '2', '')
				result = replace(result, ' ', '')
				
			listOut.append(result)
		else:
			listOut.append(word)
			
	return listOut


def dropPhonemes(listIn, hash):
	""" Drops all unemphasized (lexical stress = 0) phonemes """
	listOut = changeWordsToPhonemes(listIn, hash, 0)
	count_change = 0
	count_words = 0
	
	print 'BEFORE DROP:'
	print listOut
	
	
		
	for i in range(0, len(listOut)):
		#remove indications of lexical stress
		listOut[i] = replace(listOut[i], '1', '')
		listOut[i] = replace(listOut[i], '2', '')
		
		#count how many words are modified
		if ( (isDelimiter(listOut[i]) == 0) and (listOut[i] != '') ):
			count_words = count_words + 1 
		
		#drop syllables with no stress and the phoneme before them 
		matchstr= re.compile(r"""(\w+)(\d*)(\s+)(\w+)(0)(\s+)""",re.DOTALL|re.VERBOSE)
		temp = matchstr.sub(r'', listOut[i])
		if (temp != listOut[i]):
			count_change = count_change+1
		listOut[i] = temp
		
		#drop syllables with no stress at beginning of word
		matchstr= re.compile(r"""(\w+)(0)(\s+)""",re.DOTALL|re.VERBOSE)
		temp = matchstr.sub(r'', listOut[i])
		if (temp != listOut[i]):
			count_change = count_change+1
		listOut[i] = temp
	
		if (listOut[i] != ' '):
			listOut[i] = replace(listOut[i], ' ', '')
		
	print 'AFTER DROP:'
	print listOut
	
	print 'Number of changed words:  '+`count_change`
	print 'Total number of words:  '+`count_words`
	return listOut		

def blendPhonemes(listIn, hash):
	""" Removes spaces between words where the ending of one word and the beginning
	    of another word share the same phonemes"""
	listOut = changeWordsToPhonemes(listIn, hash, 0)
	count_change = 0
	count_words = 1
	
	print 'BEFORE BLEND:'
	print listOut

	for i in range(0, len(listOut)-1):
		
		#count the number of words being processed	
		if ( (isDelimiter(listOut[i]) == 0) and (listOut[i] != '') ):
			count_words = count_words + 1 
		
	
		#find current word, skipping delimiters
		currentword = []
		currentwordindex = -1
		nextwordindex = -1
		
		j = i
		while ( (j < len(listOut)) and (isDelimiter(listOut[j])) ):
			j = j + 1
		
		if (j >= len(listOut)):
			currentword = ' '
		else:
			currentword = tokenizeLine(listOut[j], ' ')
			currentwordindex = j
			
		if (currentword == []):
			currentword = ' '
		
		#find next word, skipping delimiters
		nextword = []
		j = j+1
		while ( (j < len(listOut)) and (isDelimiter(listOut[j])) ):
			j = j + 1
	
		if (j >= len(listOut)):
			nextword = ' '
		else:
			nextword = tokenizeLine(listOut[j], ' ')
			nextwordindex = j
			
		if (nextword == []):
			nextword = ' '
			
		#get rid of spaces and empty string
		word1 = []
		for j in range(0, len(currentword)):
			if ( (currentword[j] != ' ') and (currentword[j] != '') ):
				 word1.append(currentword[j])
		
		word2 = []
		for j in range(0, len(nextword)):
			if ( (nextword[j] != ' ') and (nextword[j] != '') ):
				 word2.append(nextword[j])
	
		#get last 2 phonemes of word1
		phon1 = '' 	#2nd to last phoneme
		phon2 = ''		#last phoneme
		if (len(word1) > 0):
			if (len(word1) >= 2):
				phon1 = word1[len(word1)-2]
			phon2 = word1[len(word1)-1]
		
		#get first 2 phonemes of word2
		phon3 = '' 		#1st phoneme
		phon4 = ''		#2nd phoneme
		if (len(word2) > 0):
			if (len(word2) >= 2):
				phon4 = word2[1]
			phon3 = word2[0]
		
		#combine words
		if (word1 != [] and word2 != []):
			if ( ((phon1 == phon3) or (phon1 == phon4)) or
			     ((phon2 == phon3) or (phon3 == phon4)) ):
			     
			     print 'Blending...'
			     print currentword
			     print nextword
			   		
			     listOut[currentwordindex] = listOut[currentwordindex] + listOut[nextwordindex]
			     listOut[nextwordindex] = ''
			     
			     count_change = count_change + 1 	
					 		 
		
		if (listOut[i] != ' '):
			listOut[i] = replace(listOut[i], ' ', '')
		
		#get rid of lexical stress indicators	
		listOut[i] = replace(listOut[i], '0', '')
		listOut[i] = replace(listOut[i], '1', '')
		listOut[i] = replace(listOut[i], '2', '')
		
	print 'AFTER BLEND:'
	print listOut
	
	print 'Number of changed words:  '+`count_change`
	print 'Total number of words:  '+`count_words`
	
	return listOut		

	

def removeWordsFromLineList(listIn, listDictionary):
	""" Remove words from listIn that appear in listDictionary, and return the result. """
	return removeWordsFromWordList(tokenizeLines(
		listIn, DELIMITERS), listDictionary)
	
def cleanupWordList(words):
	return shortenSpacesToOneSpace(removeLeadingSpacesFromList(
		removeEmptyStringsFromList(words)))
		
def removeWordsFromWordFile(fileIn, fileOut, fileDictionary):
	""" Remove words from fileIn that appear in fileDictionary, and write to fileOut. e.g., fileDictionary could hold common words. """
	f = file(fileOut, 'w')
	lines = tokenizeLines(file(fileIn, 'r').readlines(), DELIMITERS, 1)
	dictionary = tokenizeLines(file(fileDictionary, 'r').readlines(), [' '], 0)
	f.writelines(cleanupWordList(removeWordsFromWordList(lines, dictionary)))
	f.close()
	
################################################################################
	
class TTW(pyTTS):
	""" Text-to-WAV class. """

	def __init__(self, txtFile='', option='single'):
		pyTTS.__init__(self)
		self.setTXTFile(txtFile)
		self.setVoice('MSSam')
		#
		# *** TO DO ***
		# USE CLASS CONSTANTS (IF THEY EXIST)
		#
		if (self.txtFile != ''):
			if (option == 'single'):
				self.speakToWAV()
			elif (option == 'multiple'):
				self.speakLinesToWAVs()
			else:
				self.speakToWAV()
	
	def getTXTFile(self):
		return self.txtFile
				
	def setTXTFile(self, txtFile):
		self.txtFile = txtFile
		self.wavFile = self.txtFile[0:find(self.txtFile,'.')] + '.wav'
		
	def writeToTXTFile(self):
		(file(fname, 'w')).writelines(['THERE was an old sow with three little '
		+ 'pigs, and as she had not enough to keep them, she sent them out to '
		+ 'seek their fortune. '
		+ 'The first that went off met a man with a bundle of straw.'])
	
	def writeToTXTFile(self, string):
		(file(fname, 'w')).writelines(string)   

	def getTXTFileString(self):
		return (file(self.txtFile, 'r')).readlines()

	def getTXTFileStringList(self):
		""" Convert contents of the file named fname to a list of strings. """
		return tokenizeFile(self.txtFile, [' '])
		
	def speakToWAV(self):
		""" Speak contents of the file named self.txtFile to a WAV file
		of the same name. """
		self.SpeakToWave(self.wavFile,
					tokenizeLines(open(self.txtFile, 'r').readlines(),
					['\n','\r'], 0))
		
	def speakLinesToWAVs(self):
		""" Speak each line of the file named fname to a separate WAV file
		of the same name. """
		lines = (file(self.txtFile, 'r')).readlines()
		wavPre = self.txtFile[0:find(self.txtFile,'.')]
		i = 0
		for line in lines:
			self.SpeakToWave(wavPre + `i` + '.wav', tokenizeLine(line,
			['\n','\r'], 0))
			i = i + 1

	def speakFromWAV(self):
		""" Speak contents of the WAV file of the same name as the self.txtFile. """
		wavFile = self.txtFile[0:find(self.txtFile,'.')] + '.wav'
		self.SpeakFromWave(wavFile)
		
	def speakFromTXT(self):
		""" Speak contents of the file named fname. """
		# Option 1: Read all lines at once.
		self.Speak(tokenizeLines(open(self.txtFile, 'r').readlines(), ['\n']))
	
		# Option 2: Read one line at a time.
		#map(self.Speak(), open(self.txtFile, 'r').readlines())
	
		# Option 3: Read all lines at once.
		#f = file(self.txtFile, 'r')
		#s = f.readlines()
		#f.close()
		#(self.Speak(s)
		
	def getVoices(self):
		return self.GetVoices()
	
	def setVoice(self, name):
		""" Set the voice to that corresponding to name. """
		voices = self.getVoices()
		pyTTS.SetVoice(self, voices[name])

################################################################################            
							
class TTW3d(TTW): #pySoundListener):
	""" Text-to-WAV class with spatial sound. """
	
	def __init__(self, txtFile='', option='single'):
		TTW.__init__(self, txtFile, option)
		#pySoundListener.__init__(self,1)
		
	def alSpeakFromTXT(self, numWordsPerLine, numLinesPerPage):
		from time import sleep
		self.speakToWAV()
		s = self.New(self.wavFile)
		self.looping = False
		left = -20
		right = 20
		top = -20
		bottom = 20
		dx = (right - left)/numWordsPerLine
		dy = (bottom - top)/numLinesPerPage
		pos = [right, bottom, 0]
		for i in range(5): # TO DO: TEST WHETHER REACHED END OF .WAV FILE
			pos[1] = bottom
			for lines in range(numLinesPerPage):
				pos[1] = pos[1] - dy
				pos[0] = right
				for words in range(numWordsPerLine):
					pos[0] = pos[0] - dx
					s.setPosition(pos)
					s.Play()
					sleep(0.25)
					s.Pause()
		self.Delete()
		
	def alSpeakFromWAV(self):
		""" Use OpenAL to speak from a WAV file """
		#
		# *** TO DO ***
		# FIGURE OUT HOW TO MAKE THIS SOUND 3D (E.G., POSITION, VELOCITY)
		#
		alutInit(sys.argv)
		(data, format, freq, loop) = alutWAVFileToString(
								self.txtFile[0:find(self.txtFile,'.')] + '.wav')
		buff = alGenBuffers(1)
		alBufferData(buff[0], format, data, freq)
		src = alGenSources(1)

		alSourcef(src[0], AL_PITCH, 1.0)
		alSourcef(src[0], AL_GAIN, 1.0)
		alSource3f(src[0], AL_POSITION, 0.0, 0.0, 0.0)
		alSource3f(src[0], AL_VELOCITY,  0.0, 0.0, 0.0) # Doppler effect
		alSourcei(src[0], AL_BUFFER, buff[0])
		#alSourcei(src[0], AL_LOOPING, AL_TRUE)
		alSourcei(src[0], AL_LOOPING, AL_FALSE)
		
		alListenerfv(AL_POSITION, [0,0,0])
		alListenerfv(AL_VELOCITY, [0,0,0])
		alListenerfv(AL_ORIENTATION, [0,0,1, 0,1,0]) # look, up

		alSourcePlay(src[0])

		# Wait (in a GUI this wouldn't be necessary).
		time.sleep(10)

		# Stop the sound.
		alSourceStop(src[0])

		alDeleteSources(src)
		alDeleteBuffers(buff)

		# Shut down the library
		# alutExit() # This produces an exception; Pete and I don't know why.

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

class Node:
	def __init__(self, data='NULL'):
		self.data = data
		self.before = self
		self.after = self
		
	def getData(self):
		return self.data
		
	def setData(self, data):
		self.data = data
	
	def toString(self):
		return self.data
		
	def toStringDetailed(self):
		s = '(' + self.before.data + ', ' + self.data + ', '
		s = s + self.after.data + ')'
		return s
		
	def clone(self):
		return Node(data)
		
NULL_NODE = Node()

################################################################################
# head <--> node1 <--> node2 <--...--> tail
#
# before <--[-o node o-]--> after
#
class CDLinkedList:
	""" Circular, doubly linked list class. """
	def __init__(self):
		self.head = NULL_NODE
		self.current = self.head
		self.size = 0
		
	def resetCurrent(self):
		self.current = self.head
		return self.current
		
	def forwardCurrent(self, numSteps=1):
		for i in range(0,numSteps):
			self.current = self.current.after
		return self.current
		
	def backwardCurrent(self, numSteps=1):
		for i in range(0,numSteps):
			current = current.before
		return self.current
		
	def getSize(self):
		return self.size
		
	def getHead(self):
		return self.head
		
	def getTail(self):
		return self.head.before
		
	def getCurrent(self):
		return self.current
		
	def getAfter(self):
		return self.forwardCurrent(1)
		
	def getBefore(self):
		return self.backwardCurrent(1)

	def insertAfter(self, nodeInList, nodeToInsert):
		if (self.size == 0):
			self.head = nodeToInsert
			self.head.after = nodeToInsert
			self.head.before = nodeToInsert
			self.current = nodeToInsert
		else:
			nodeToInsert.before = nodeInList
			nodeToInsert.after = nodeInList.after
			nodeInList.after = nodeToInsert
			(nodeToInsert.after).before = nodeToInsert
			
		self.size = self.size + 1
		
	def insertBefore(self, nodeInList, nodeToInsert):
		if (self.size == 0):
			self.head = nodeToInsert
			self.head.after = nodeToInsert
			self.head.before = nodeToInsert
			self.current = nodeToInsert
		else:
			if (self.head == nodeInList):
				self.head = nodeToInsert
			if (self.current == nodeInList):
				self.current = nodeToInsert
				
			nodeToInsert.after = nodeInList
			nodeToInsert.before = nodeInList.before
			nodeInList.before = nodeToInsert
			(nodeToInsert.before).after = nodeToInsert
			
		self.size = self.size + 1
	
	def append(self, nodeToAppend):
		self.insertAfter(self.getTail(), nodeToAppend)
		
	def prepend(self, nodeToPrepend):
		self.insertBefore(self.getHead(), nodeToPrepend)
		
	def removeTail(self):
		if (self.size == 0):
			return
		elif (self.size == 1):
			self.head = NULL_NODE
			self.current = self.head
		else:
			tail = self.getTail()
			if (self.current == tail):
				self.current = tail.before
			newTail = tail.before
			newTail.after = self.head
			(self.head).before = newTail
			
		self.size = self.size - 1
		
	def removeHead(self):
		if (self.size == 0):
			return
		elif (self.size == 1):
			self.head = NULL_NODE
			self.current = self.head
		else:
			tail = self.getTail()
			if (self.current == self.head):
				self.current = (self.head).after
			newHead = (self.head).after
			newHead.before = tail
			self.head = newHead
			tail.after = newHead
			
		self.size = self.size - 1
		
	def toString(self):
		""" Convert to basic string representation. """
		s = ''
		size = self.getSize()
		if (size == 0):
			return s
		self.current = self.head
		for i in range(0,size):
			s = s + self.current.toString()
			if (i <= size-2):
				s = s + ', '
			self.current = self.current.after
		self.current = self.head
		return s
		
	def toStringDetailed(self):
		""" Convert to detailed string representation (before, current, after) for each node. Great for debugging. """
		s = ''
		size = self.getSize()
		if (size == 0):
			return s
		self.current = self.head
		for i in range(0,size):
			s = s + self.current.toStringDetailed()
			if (i <= size-2):
				s = s + ', '
			self.current = self.current.after
		self.current = self.head
		return s
		
	def clone(self):
		cdList = CDLinkedList()
		self.resetCurrent()
		for i in range(0,self.size()):
			cdList.append(current.clone())
			self.current = self.current.after
		return cdList

################################################################################
# This set of classes builds on CDDoublyLinkedList (circular, doub linked list)
# for building multiple resolutions of a document.
#
NULL_LIST = CDLinkedList()

class Document(CDLinkedList):
	def __init__(self, words=''):
		CDLinkedList.__init__(self)
		self.initLinkedList(words)
	
	def initLinkedList(self, words):
		for i in range(0,len(words)):
			self.append(Node(words[i]))
		
class SegRes:
	def __init__(self, res=0, size=0, nodePtr=NULL_NODE):
		self.res = res
		self.size = size
		self.nodePtr = nodePtr
		
class SegResNode(Node):
	def __init__(self, res=0, size=0, nodePtr=NULL_NODE):
		Node.__init__(self, SegRes(res, size, nodePtr))
		
	def getRes(self):
		return self.data.res
		
	def getSize(self):
		return self.data.size
		
	def getNodePtr(self):
		return self.data.nodePtr
		
	def getData(self):
		return self.data.nodePtr.getData()
		
	def setRes(self, res):
		self.data.res = res
		
	def setSize(self, size):
		self.data.size = size
	
	def setNodePtr(self, nodePtr):
		self.data.nodePtr = nodePtr
		
	def setData(self, data):
		self.data.nodePtr.setData(data)
		
	def clone(self):
		return SegResNode(self.getRes(), self.getSize(), self.nodePtr.clone())
		
NULL_DOCUMENT = Document()
		
class LowResDocument(CDLinkedList):
	def __init__(self, doc=NULL_DOCUMENT, segResList=NULL_LIST):
		self.segResList = CDLinkedList()
		self.doc = doc # Original, high-res document.
		self.segResList = segResList
		self.LOD = 1 # Highest amount of detail == Lowest LOD
		self.reset()
		
	def setLOD(self, LOD):
		if LOD >= 1:
			self.LOD = LOD
			
	def incrementLOD(self):
		self.LOD = self.LOD + 1
		
	def decrementLOD(self):
		if LOD >= 2:
			self.LOD = self.LOD - 1
		
	def setSegRes(self, res, size):
		if (res <= 0 or size <= 0 or res > size):
			print '** Error at LowResDocument.setSegRes()'
		self.segResList = CDLinkedList()
		curNodePtr = self.doc.resetCurrent()
		j = 0
		for i in range(0,self.doc.getSize()):
			if (j>=self.doc.getSize()):
				break
			self.segResList.append(SegResNode(res, size, curNodePtr))
			curNodePtr = self.doc.forwardCurrent(size)
			j = j+size
	
	def getOriginalSize(self):
		return self.doc.getSize()
		
	def getLowResSize(self):
		sum = 0
		current = self.segResList.resetCurrent()
		for i in range(0,self.segResList.getSize()):
			sum = sum + current.getRes()
			current = self.segResList.forwardCurrent()
		return sum
		
	def reset(self):
		self.curResNodePtr = self.segResList.resetCurrent()
		self.curDocNodePtr = self.doc.resetCurrent()
		
	def toString(self, showStatusBar=0):
		""" Convert to string representation at current LOD. """
		#
		# BUG: Counts delimiters as part of the text being skipped.
		#
		s = ''
		
		if (showStatusBar>=1):
			d = ThreadedStatusProgressDialog(
				"Removing words...", "", self.doc.getSize())

		self.curResNodePtr = self.segResList.resetCurrent()
		self.curDocNodePtr = self.doc.resetCurrent()
				
		for i in range(0,self.segResList.getSize()):
			for j in range(0,self.curResNodePtr.getRes()):
				t = self.curDocNodePtr.toString()
				# Don't count spaces or delimiters as part of the text
				# being skipped.
				# Render sequential spaces as a single space,
				# render all return/newline characters, and
				# render the 1st of any other type of delimiter.
				sawFirst = 0
				while isDelimiter(self.curDocNodePtr.toString()):
					if isSpace(t):
						if i%self.LOD == 0: # Implements LOD
							s = s + ' '
						while isSpace(t):
							if self.doc.current != self.doc.getTail():
								self.curDocNodePtr = self.doc.forwardCurrent(1)
								t = self.curDocNodePtr.toString()
								if (showStatusBar>=1):
									d.Tick()
							else:
								if (showStatusBar>=1):
									d.Close()
								return s
					else:
						if t=='\r' or t=='\n':
							if i%self.LOD == 0: # Implements LOD
								s = s + t
						else:
							if sawFirst==0:
								if i%self.LOD == 0: # Implements LOD
									s = s + t
								sawFirst=1

						if self.doc.current != self.doc.getTail():
							self.curDocNodePtr = self.doc.forwardCurrent(1)
							t = self.curDocNodePtr.toString()
							if (showStatusBar>=1):
								d.Tick()
						else:
							if (showStatusBar>=1):
								d.Close()
							return s

				if i%self.LOD == 0: # Implements LOD									
					s = s + self.curDocNodePtr.toString()
				if self.doc.current != self.doc.getTail():
					self.curDocNodePtr = self.doc.forwardCurrent(1)
					t = self.curDocNodePtr.toString()
					if (showStatusBar>=1):
						d.Tick()
					
			for k in range(
				self.curResNodePtr.getRes(), self.curResNodePtr.getSize()):
				sawFirst = 0
				while isDelimiter(self.curDocNodePtr.toString()):
					t = self.curDocNodePtr.toString()
					if (showStatusBar>=1):
						d.Tick()
					if isSpace(t):
						if i%self.LOD == 0: # Implements LOD
							s = s + ' '
						while isSpace(t):
							if self.doc.current != self.doc.getTail():
								self.curDocNodePtr = self.doc.forwardCurrent(1)
								t = self.curDocNodePtr.toString()
								if (showStatusBar>=1):
									d.Tick()
							else:
								if (showStatusBar>=1):
									d.Close()
								return s
					else:
						if t=='\r' or t=='\n':
							if i%self.LOD == 0: # Implements LOD
								s = s + t
						else:
							if sawFirst==0:
								if i%self.LOD == 0: # Implements LOD
									s = s + t
								sawFirst=1

						if self.doc.current != self.doc.getTail():
							self.curDocNodePtr = self.doc.forwardCurrent(1)
							t = self.curDocNodePtr.toString()
							if (showStatusBar>=1):
								d.Tick()
						else:
							if (showStatusBar>=1):
								d.Close()
							return s
							
				if self.doc.current != self.doc.getTail():
					self.curDocNodePtr = self.doc.forwardCurrent(1)
					t = self.curDocNodePtr.toString()
					if (showStatusBar>=1):
						d.Tick()
				else:
					if (showStatusBar>=1):
						d.Close()
					return s
		if (showStatusBar>=1):
			d.Close()
		return s

class LowResDocumentNode(Node):
	def __init__(self, segResList=NULL_LIST):
		Node.__init__(self, segResList)
		
	def toString(self, showStatusBar=0):
		LowResDocument.toString(self, showStatusBar)

################################################################################
	
class TTW3dL(TTW3d, LowResDocument):
	def __init__(self, txtFile='', option='single', segResList=NULL_LIST):
		TTW3d.__init__(self, txtFile, option)
		doc = NULL_DOCUMENT
		if (txtFile != ''):
			doc = Document(file(txtFile,'r').readlines())
		LowResDocument.__init__(self, doc, segResList)
		
	def speakLOD(self):
		""" Speak the current LOD. """
		#
		# BUG: Counts delimiters as part of the text being skipped.
		#
		self.curResNodePtr = self.segResList.resetCurrent()
		self.curDocNodePtr = self.doc.resetCurrent()
				
		for i in range(0,self.segResList.getSize()):
			for j in range(0,self.curResNodePtr.getRes()):
				t = self.curDocNodePtr.toString()
				# Don't count spaces or delimiters as part of the text
				# being skipped.
				# Render sequential spaces as a single space,
				# render all return/newline characters, and
				# render the 1st of any other type of delimiter.
				sawFirst = 0
				while isDelimiter(self.curDocNodePtr.toString()):
					if isSpace(t):
						if i%self.LOD == 0: # Implements LOD
							self.Speak(' ')
						while isSpace(t):
							if self.doc.current != self.doc.getTail():
								self.curDocNodePtr = self.doc.forwardCurrent(1)
								t = self.curDocNodePtr.toString()
							else:
								return
					else:
						if t!='\r' or t!='\n':
							if sawFirst==0:
								if i%self.LOD == 0: # Implements LOD
									self.Speak(t)
								sawFirst=1

						if self.doc.current != self.doc.getTail():
							self.curDocNodePtr = self.doc.forwardCurrent(1)
							t = self.curDocNodePtr.toString()
						else:
							return

################################################################################
	
class TTW3dL(TTW3d, LowResDocument):
	def __init__(self, txtFile='', option='single', segResList=NULL_LIST):
		TTW3d.__init__(self, txtFile, option)
		doc = NULL_DOCUMENT
		if (txtFile != ''):
			doc = Document(file(txtFile,'r').readlines())
		LowResDocument.__init__(self, doc, segResList)
		
	def speakLOD(self):
		""" Speak the current LOD. """
		#
		# BUG: Counts delimiters as part of the text being skipped.
		#
		self.curResNodePtr = self.segResList.resetCurrent()
		self.curDocNodePtr = self.doc.resetCurrent()
				
		for i in range(0,self.segResList.getSize()):
			for j in range(0,self.curResNodePtr.getRes()):
				t = self.curDocNodePtr.toString()
				# Don't count spaces or delimiters as part of the text
				# being skipped.
				# Render sequential spaces as a single space,
				# render all return/newline characters, and
				# render the 1st of any other type of delimiter.
				sawFirst = 0
				while isDelimiter(self.curDocNodePtr.toString()):
					if isSpace(t):
						if i%self.LOD == 0: # Implements LOD
							self.Speak(' ')
						while isSpace(t):
							if self.doc.current != self.doc.getTail():
								self.curDocNodePtr = self.doc.forwardCurrent(1)
								t = self.curDocNodePtr.toString()
							else:
								return
					else:
						if t!='\r' or t!='\n':
							if sawFirst==0:
								if i%self.LOD == 0: # Implements LOD
									self.Speak(t)
								sawFirst=1

						if self.doc.current != self.doc.getTail():
							self.curDocNodePtr = self.doc.forwardCurrent(1)
							t = self.curDocNodePtr.toString()
						else:
							return

				if i%self.LOD == 0: # Implements LOD									
					self.Speak(self.curDocNodePtr.toString())
				if self.doc.current != self.doc.getTail():
					self.curDocNodePtr = self.doc.forwardCurrent(1)
					t = self.curDocNodePtr.toString()
					
			for k in range(
				self.curResNodePtr.getRes(), self.curResNodePtr.getSize()):
				sawFirst = 0
				while isDelimiter(self.curDocNodePtr.toString()):
					t = self.curDocNodePtr.toString()
					if isSpace(t):
						if i%self.LOD == 0: # Implements LOD
							self.Speak(' ')
						while isSpace(t):
							if self.doc.current != self.doc.getTail():
								self.curDocNodePtr = self.doc.forwardCurrent(1)
								t = self.curDocNodePtr.toString()
							else:
								return
					else:
						if t=='\r' or t=='\n':
							if i%self.LOD == 0: # Implements LOD
								self.Speak(t)
						else:
							if sawFirst==0:
								if i%self.LOD == 0: # Implements LOD
									self.Speak(t)
								sawFirst=1

				if i%self.LOD == 0: # Implements LOD									
					self.Speak(self.curDocNodePtr.toString())
				if self.doc.current != self.doc.getTail():
					self.curDocNodePtr = self.doc.forwardCurrent(1)
					t = self.curDocNodePtr.toString()

			for k in range(
				self.curResNodePtr.getRes(), self.curResNodePtr.getSize()):
				sawFirst = 0
				while isDelimiter(self.curDocNodePtr.toString()):
					t = self.curDocNodePtr.toString()
					if isSpace(t):
						if i%self.LOD == 0: # Implements LOD
							self.Speak(' ')
						while isSpace(t):
							if self.doc.current != self.doc.getTail():
								self.curDocNodePtr = self.doc.forwardCurrent(1)
								t = self.curDocNodePtr.toString()
							else:
								return
					else:
						if t=='\r' or t=='\n':
							if i%self.LOD == 0: # Implements LOD
								self.Speak(t)
						else:
							if sawFirst==0:
								if i%self.LOD == 0: # Implements LOD
									self.Speak(t)
								sawFirst=1

						if self.doc.current != self.doc.getTail():
							self.curDocNodePtr = self.doc.forwardCurrent(1)
							t = self.curDocNodePtr.toString()
						else:
							return
							
				if self.doc.current != self.doc.getTail():
					self.curDocNodePtr = self.doc.forwardCurrent(1)
					t = self.curDocNodePtr.toString()
				else:
					return
################################################################################
		
if __name__ == '__main__':
	numWordsPerLine = 5
	numLinesPerPage = 5
	(TTW3d('BoyBathing.txt')).alSpeakFromTXT(numWordsPerLine, numLinesPerPage)

	""" # BEGIN COMMENT
	(TTW3d('testSpeak.txt', 'single')).alSpeakFromWAV()
	(TTW3d('testSpeak.txt', 'multiple')).alSpeakFromWAV()
	
	p = pyTTS()
	v = p.GetVoices()
	p.SetVoice(v['MSMary'])
	p.Speak('Hello, Suzanne. How are you today?')
	
	p = pyTTW('testSpeak.txt')
	p.setVoice('mary')
	p.speakFromTXT()
	p.SetVoice(v['MSMary'])
	p.SpeakToWave('testSpeak.wav', 'Hello, Suzanne. How are you today?')
	p.SpeakFromWave('testSpeak.wav')
	
	p = TTW3d('testSpeak.txt')
	#p.setVoice('mary') # bug (see 'to do')
	p.alSpeakToWAV()
	p.alSpeakFromWAV()
	
	s = ['hello', 'world', 'this', 'is', 'a', 'test']
	words = ['is', 'the', 'a']
	print(removeWordsFromList(s, words))
	
	removeWordsFromFile('testSpeak.txt', 'testSpeak_commonRemoved.txt', 'commonWords.txt')
	
	doc = Document('Crab.txt')
	print doc.toString()
	lowResDoc = LowResDocument(doc)
	lowResDoc.setSegRes(2,5)
	print lowResDoc.toString()
	""" # END COMMENT
