from NCMapData import *
from Earcon import *
from pyTTS import *
from pyIFC import *
from math import *
import cPickle, time, os, os.path, win32api
import threading

#------------------------------------------------------------------------------------#

class NCRenderer:
	#-----------------------------------------------
	#initialize all of the necessary rendering libraries
	#-----------------------------------------------
	def __init__(self, trans_file, joy_file, mouse_file, cache_path, pool_size, hwnd):
		#load the transformer data
		f = open(trans_file, 'rb')
		self.trans = cPickle.load(f)
		f.close()
		
		self.last_oid = None
		
		#initialize components
		self.InitializeTTS()
		self.InitializeCache(cache_path)
		self.InitializeEarconPool(pool_size)
		self.InitializeTactileDevice(joy_file, mouse_file, hwnd)


	#-----------------------------------------------
	#clean up the generated files
	#TODO:cannot access files in use
	#-----------------------------------------------		
	def __del__(self):
		#clean up the earcons using the files first
		self.pool.Delete()
		del self.pool
	
	#-----------------------------------------------
	#intiailize the text to speech engine
	#-----------------------------------------------	
	def InitializeTTS(self):
		#create a text to speech object
		self.tts = pyTTS()
		#get the available voices
		self.voices = self.tts.GetVoices()
		
	#-----------------------------------------------
	#intialize the folder that will act as a wave file cache
	#-----------------------------------------------
	def InitializeCache(self, cache_path):
		#make a directory if it's not already there
		try:
			os.mkdir(cache_path)
		except:
			#clean up the file cache
			files = os.listdir(cache_path)
			for f in files:
				os.remove(cache_path + '\\' + f)
		
		self.cache_path = cache_path
				
	#-----------------------------------------------
	#intialize the earcon manager
	#-----------------------------------------------
	def InitializeEarconPool(self, size):	
		#make a pool of earcons
		self.pool = EarconPool(size,self.exponential)		
	
	#-----------------------------------------------
	#intialize the tactile feedback device if it's available
	#-----------------------------------------------
	def InitializeTactileDevice(self, joy_file, mouse_file, hwnd):
		#get a handle for the instance of the module
		hinst = win32api.GetModuleHandle(None)

		#create an instance of the device attached to the system
		self.dev = CImmDevice_CreateDevice(hinst, hwnd)

		if self.dev != None:
 			self.tact = pyImmProject()
 			if self.dev.GetDeviceType() == IMM_DEVICETYPE_DIRECTINPUT:
 				self.tact.OpenFile(joy_file, self.dev)
 			else:
 				self.tact.OpenFile(mouse_file, self.dev)
	
	#-----------------------------------------------
	#speak info about a place by cycling through what's available
	#-----------------------------------------------	
	def RenderPointInfo(self, md, offset):
		data = md.GetData()
		mapping = self.trans[md.GetType()]

		try:
			#figure out the appropriate offset
			offset = offset % len(mapping['info'])
		
			#get the text to speak
			text = mapping['info'][offset] % data
			
			#set the proper voice
			self.tts.SetVoice(self.voices[mapping['voice']])
			
			#speak the info
			self.tts.Speak(text, tts_async, tts_purge_before_speak)
			
		except:
			pass
			
	def StopVoice(self):
		self.tts.Stop()
	
	#-----------------------------------------------
	#call out the names of locations within a given area
	#-----------------------------------------------	
	def RenderAreaCallouts(self, mds):
		stamp = time.time()
		owner = 'callouts'
		callouts = {}
	
		#go through all the ids encountered
		for key in mds:
			md = mds[key]
			soid = md.GetSharedOID()
			
			#make sure this callout isn't already generated on disk
			if not callouts.has_key(soid):
				data = md.GetData()
				
				#figure out the mapping between the data and what
				#should be spoken for each item if it's available
				mapping = self.trans[md.GetType()]
				try:
					text = mapping['callout'] % data
				except:
					continue
				
				#speak the appropriate text to disk if it's not already there
				file = self.cache_path + '\\' + str(soid) + '.wav'
				if not os.path.isfile(file):
					#set the proper voice and make the file
					self.tts.SetVoice(self.voices[mapping['voice']])
					self.tts.SpeakToWave(file, text)
	
				#make an appropriate earcon
				x,y = md.GetPos()
				c = self.pool.New(file, stamp, owner)
				if c != None:
					c.SetLooping(False)
					c.SetPosition(x,y,0.0)
					callouts[soid] = [md,c]

		#go through and play all of the callouts in a separate thread
		#	so that the main thread does not freeze
		calls = callouts.values()
		calls.sort(self.clockSort)
		e = []
		for c in calls:
			c[1].SetGain(3.0)
			e.append(c[1])
		self.pool.PlayListAsync(e, 1.0, stamp+1, owner)

	#-----------------------------------------------
	#render earcons for places of interest using spatial sound
	#manage earcon objects that come into and pass out of view
	#-----------------------------------------------
	def RenderAreaEarcons(self, mds):
		stamp = time.time()
		owner = 'earcons'
		
		#go through all the ids encountered
		for key in mds:
			md = mds[key]
			soid = md.GetSharedOID()
			x,y = md.GetPos()
			
			e = self.pool.FindID(soid)
			#if this is a new object, just add it to the list
			if e == None :
				file = self.trans[md.GetType()]['earcon']
				if file != None:
					e = self.pool.New(file, stamp, owner, soid)
					#make sure the earcon was allocated
					if e != None:
						e.SetPosition(x,y,0.0)
						e.SetLooping(True)
						e.Play()
					
			#if this is an existing object, update its position and stamp
			else:
				e.SetStamp(stamp)
				e.SetPosition(x,y,0.0)

		#clean up any unused objects
		self.pool.Clean(stamp, owner)
		
	#-----------------------------------------------
	#render texture based on the object directly under the cursor
	#-----------------------------------------------
	def RenderPointTexture(self, md):
		effect = None
		
		#quit immediately if there's no tactile device
		if self.dev == None:
			return
			
		#if we're over nothing now, play a default effect
		if md == None:
			if self.last_oid != None:
				self.last_oid = None
				effect = self.trans[None]['texture']
		#see if we've moved to a different region
		elif self.last_oid != md.GetOID():
			self.last_oid = md.GetOID()
			effect = self.trans[md.GetType()]['texture']
		
		#play the effect
		if effect != None:
			self.tact.Stop()
			self.tact.Start(effect,IMM_EFFECT_DONT_CHANGE)

	#-----------------------------------------------
	#sort cities based on distances and directions
	#-----------------------------------------------
	def clockSort(self, obj1, obj2):
		md1 = obj1[0]
		md2 = obj2[0]
		x1,y1 = md1.GetPos()
		x2,y2 = md2.GetPos()
		r1 = md1.GetDist()
		r2 = md2.GetDist()

		if(x1 == 0 and y1 == 0):
			return -1

		if(x2 == 0 and y2 == 0):
			return 1

		t1 = atan(float(y1)/(float(x1)+0.00000001))
		c1 = pi/2 - acos(float(x1)/(float(r1)+0.00000001))
		t2 = atan(float(y2)/(float(x2)+0.00000001))
		c2 = pi/2 - acos(float(x2)/(float(r2)+0.00000001))

		if(self.quadrantAngle(c1,t1) < self.quadrantAngle(c2,t2)):
			return -1
		elif(self.quadrantAngle(c1,t1) > self.quadrantAngle(c2,t2)):
			return 1
		else:
			return 0

	#-----------------------------------------------
	#returns an angle based on a cosine and a tangent
	#calculation so that callouts can be ordered by
	#a scalar value
	#-----------------------------------------------
	def quadrantAngle(self, c, t):
		if(c > 0 and t > 0):
			return t
		if(c < 0 and t < 0):
			return pi+t
		if(c < 0 and t > 0):
			return t+pi
		if(c > 0 and t < 0):
			return pi*2+t
		if(c == 0 and t > 0):
			return pi/2
		if(c == 0 and t < 0):
			return 3*pi/2
			
	#-----------------------------------------------
	#an exponential decay function for the sound
	#-----------------------------------------------	
	def exponential(self,x,y,z):
		sx = sy = sz = 1
		if x < 0:
			sx = -1
		if y < 0:
			sy = -1
		if z < 0:
			sz = -1
			
		xexp = sx*(20*exp(0.05*abs(x*0.5))-20)
		yexp = sy*(20*exp(0.05*abs(y*0.5))-20)
		zexp = sz*(20*exp(0.05*abs(z*0.5))-20)
		
		return xexp, yexp, zexp