from wxPython.wx import *
from wxPython.xrc import *
from sabregl import *
from myrirc import Irc
from myrevents import *
from threading import Thread
from ConfigParser import SafeConfigParser
import time, sys, select, string, math, ogg.vorbis

class Audio(Thread):

        def __init__(self, file):
		Thread.__init__(self)
		self.running = True
		if sys.platform[:3] == 'win' or sys.platform[-3:] == 'win':
			global winsound
			import winsound
			self.win = True
		else:
			global ao
			import ao
			config = SafeConfigParser()
			config.read("myrmidia.conf")
			mixer = config.get("Audio", "mixer")
			self.dev = ao.AudioDevice(mixer)
			self.win = False
			
		self.file = file
		self.audiofile = ogg.vorbis.VorbisFile(file)
		
	def __del__(self):
		self.stop()
		
	def stop(self):
		self.running = False

	def run(self):
		while self.running:
			buf, bytes, bit = self.audiofile.read(1024)
			if bytes != 0:
				if self.win:
					winsound.PlaySound(buf, winsound.SND_NODEFAULT)
				else:
					self.dev.play(buf, bytes)
			else:
				self.audiofile = ogg.vorbis.VorbisFile(self.file) # repeat TODO: Find out how to seek in oggs
																																					
class Creature:
	'''Wrapper for creature data'''	
	def __init__(self, name, x, z, appearance=None):
		self.modelNo = None
		self.name = name
		self.dx = x
		self.dz = z
		self.x = x
		self.z = z
		self.interp = 0
		self.rot = 0
		self.appearance = appearance
		self.curanim = "I"

	def loadModel(self, appearance):
		self.modelNo = getfreeobject()
		self.appearance = appearance
		loadmd2(self.modelNo, self.appearance + ".md2")
		scale(self.modelNo, 0.07, 0.07, 0.07)
		rotate(self.modelNo, [270, 0, 90])
		zero(self.modelNo)
		speed(self.modelNo, 5)
		texture(self.modelNo, self.appearance + ".bmp")
		maketext(self.modelNo, 0, self.name)
		unfixtext(self.modelNo)
		scaletext(self.modelNo, [0.008, 0.008, 0.008])
		self.idle()
		self.display()

	def anim(self, anim):
		if anim !=self.curanim:
			self.curanim = anim
			if anim == "W":
				self.walk()
			if anim == "I":
				self.idle()
			if anim == "C":
				self.crawl()
	
	def idle(self):
		if self.modelNo:
			frames(self.modelNo, 0, 40)

	def crawl(self):
		if self.modelNo:
			frames(self.modelNo, 153, 160)
			
	def walk(self):
		pass

	def rotate(self, angle):
		if self.modelNo:
			self.rot -= math.radians(angle)
			rotate(self.modelNo, [0, 0, -angle])

	def addDest(self, x, z, rot, anim):
		pass

	def display(self):
		if self.modelNo:
			#Interpolate movement; TODO: Get speed from server
			#Make sure we aren't miles out
#			if self.x > (self.dx + 5) or self.x < (self.dx - 5) or self.z > (self.dz + 5) or self.z < (self.dz - 5):
#				self.x = self.dx
#				self.z = self.dz

			if self.interp >= 1:
				self.interp = 0
				self.x = self.dx
				self.z = self.dz
				print "Arrived!"
			
			if self.x != self.dx and self.z != self.dz:
				self.interp += 0.001
				self.ix = self.x + (self.dx - self.x) * self.interp
				self.iz = self.z + (self.dz - self.z) * self.interp
#				print "x: " + str(self.x) + ", dx:" + str(self.dx) + ", ix:" + str(self.ix) + ", interp: " + str(self.interp)
				y = height(0, [self.ix, self.iz])
				setpos(self.modelNo, [self.ix, y - 66.2, self.iz])
				settextpos(self.modelNo, [self.ix, y - 64.5, self.iz])

class ViewPort(Thread):
	'''3D display of game'''
	def __init__(self, sock, parent):
		Thread.__init__(self)
		self.sock = sock
		self.sock.setblocking(0) # Non-blocking
		self.parent = parent
		wxInitAllImageHandlers()
		config = SafeConfigParser()
		try:
			config.read("myrmidia.conf")
			width, height = config.get("Graphics", "resolution").split("x")
			height = int(height)
			width = int(width)
			depth = int(config.get("Graphics", "depth"))
		except:
			print "Meep"
			height, width = 800, 600
			depth = 16
			
		initscreen(width, height, depth)
		title("Myrmidia ('Phoenix')")
		framerate(100)
		keyrepeat()
		#Hard coded terrain for now; get from server in next release
		skybox("moon1.bmp", "moon2.bmp", "moon3.bmp", "moon4.bmp", "moon5.bmp")
		heightmap(0, "map2.raw", "grass.bmp", [-260, -68, -240])
		loadfont(0, "FreeSans.ttf")
		light(0, [0.1, 0, 0.1])
		fog(40, 60, 0.1, [0, 0, 0.01])
		self.creaturesNear = []
		self.running = 1

	def shutdown(self):
		self.running = 0

	def run(self):
		move = 0.01 #Get us to send position data when starting
		turn = 0
		sendMove = 5
		while self.running:
			
			key = getkey()
			if key == 273: move = 0.1
			if key == 274: move = -0.05
			if key == 275: turn = 0.02
			if key == 276: turn = -0.02
			if key == 27: #escape
				self.running = 0
			if move == 0 and justmoved:
				self.send("A|I")
				justmoved = False
			if move != 0 or turn != 0:
				if move != 0:	cameramove(move)
				if turn != 0: 	cameraturn(turn)
				cameraX, cameraY, cameraZ = camerapos()
				cameraHeight = height(0, [cameraX, cameraZ])
				setcamerapos([cameraX, cameraHeight - 65.5, cameraZ])
				#Only send every 5 moves, client interpolates between them
				if sendMove == 5:
					rot = str(round(getcamerarot(), 2))
					self.send("P|" + str(cameraX) + "|" + str(cameraZ) + "|" + rot + "|C") #Just send X & Z, client can calculate Y
					sendMove = 0
				else:
					sendMove += 1
				justmoved = True
				move = 0
				turn = 0

			for creature in self.creaturesNear:
				creature.display()
			
			sync()
			self.recv()
			time.sleep(0.01)
		self.sock.close()
		wxPostEvent(self.parent, QuitEvent())
		shutdown()

	#Handle message sends & recieves in non-blocking mode
	def send(self, msg, timeout=0):
		r, w, e = select.select([], [self.sock], [], timeout)
		if w:
			self.sock.send(msg + "\n")
		else:
			if timeout!=0:
				print "Timeout. Connection Lost."
				self.shutdown()

	def recv(self):
		r, w, e = select.select([self.sock], [], [], 0)
		if r:
			self.fsock = self.sock.makefile("rb") # buffered
			try:
				data = self.fsock.readline()
			except:
				return 1
			data = data.replace("\n", "")
			data = data.replace("\r" ,"")
			if data[:4] == "SAY|":
				wxPostEvent(self.parent, SayEvent(data[4:]))
			elif data[:2] == "C|":
				self.showCreature(data[2:])
			elif data[:2] == "D|":
				self.deleteCreature(data[2:])
			elif data[:5] == "USER|":
				self.creatureDetails(data[5:])
			elif data[:2] == "A|":
				self.creatureAnim(data[2:])

	def showCreature(self, data):
		try:
			name, x, z, rot, anim = string.split(data, "|")
		except:
			print "Malformed C message from server."
			return
		found = 0
		x = float(x)
		z = float(z)
		rot = float(rot)
		for creature in self.creaturesNear:
			#Update existing creature
			if name == creature.name:
				creature.addDest(x, z, rot, anim)
				found = 1
				break
		#Create new creature
		if found == 0:
			self.send("USER|" + name)
			wxPostEvent(self.parent, AddCreatureEvent(name))
			creature = Creature(name, x, z)
			self.creaturesNear.append(creature)

	def deleteCreature(self, name):
		for creature in self.creaturesNear:
			if name == creature.name:
				wxPostEvent(self.parent, DelCreatureEvent(name))
				self.creaturesNear.remove(creature)

	def creatureDetails(self, data):
		try:
			name, appearance = string.split(data, "|")
		except:
			print "Malformed USER message from server."
			return
		for creature in self.creaturesNear:
			if name == creature.name:
				creature.appearance = appearance
				if not creature.modelNo:
					creature.loadModel(appearance)
				break

	def creatureAnim(self, data):
		try:
			name, anim = string.split(data, "|")
		except:
			print "Malformed A messagr from server."
			return
		for creature in self.creaturesNear:
			if name == creature.name:
				creature.anim(anim)
				break


class Game(wxApp):
	'''GUI elements (chat pain and tool box)'''
	def __init__(self, sock, name, parent):
		self.sock = sock
		self.name = name
		self.parent = parent
		wxApp.__init__(self)
		
	def OnInit(self):
		self.res = wxXmlResource("interface.xml")
		wxInitAllImageHandlers()
		self.initFrames()
		self.initEvents()
		self.viewport = ViewPort(self.sock, self)
		self.viewport.start()
		self.irc = Irc(self, "irc.hethane.se", 6667, ['#myrmidia'], self.name)
		self.irc.start()
		config = SafeConfigParser()
		config.read("myrmidia.conf")
		if str.lower(config.get("Audio", "enabled")) == "true":
			self.audio = Audio("guitar.ogg")
			self.audio.start()
		else:
			self.audio = None
		return True

	def initFrames(self):
		self.chat = self.res.LoadFrame(None, "frmChat")
		self.chat.Show()
		self.tools = self.res.LoadFrame(None, "frmTools")
		self.tools.Show()
		lstPeople = XRCCTRL(self.tools, "lstPeople")
		lstPeople.InsertStringItem(0, self.name)

	def initEvents(self):
		EVT_TEXT_ENTER(self, XRCID("txtChat"), self.say)
		EVT_NOTEBOOK_PAGE_CHANGED(self, XRCID("ntbChat"), self.pageChanged)
		#Events passed from the 3d thread
		EVT_SAY(self, self.chatIn)
		EVT_QUIT(self, self.quit)
		EVT_ADDCREATURE(self, self.addCreature)
		EVT_DELCREATURE(self, self.delCreature)

	def say(self, event):
		txtChat = XRCCTRL(self.chat, "txtChat")
		text = txtChat.GetValue()
		txtChat.SetValue("")
		notebook = XRCCTRL(self.chat, "ntbChat")
		channel = notebook.GetPageText(notebook.GetSelection())
		channel = string.upper(channel)
		if channel == "IRC":
			output = XRCCTRL(self.chat, "txtIRC")
			output.AppendText("<" + self.name + "> " + text)
			self.irc.say("#myrmidia", text + "\n")
		else:
			self.viewport.send("SAY|" + channel + "|" + text)

	def pageChanged(self, event):
		notebook = XRCCTRL(self.chat, "ntbChat")
		txtChat = XRCCTRL(self.chat, "txtChat")
		txtChat.SetFocus()
		if notebook.GetPageText(notebook.GetSelection()) == "Server":
			txtChat.Disable()
		else:
			txtChat.Enable()

	def quit(self, event):
		if self.audio:
			self.audio.stop()
		self.irc.shutdown()
		self.chat.Close()
		self.tools.Close()
		self.parent.quit(event)
			
	def chatIn(self, event):
		data = event.data
		try:
			user, channel, message = string.split(data, "|", 2)
		except:
			print "Malformed SAY message from server."
			return
		if channel == "GLOBAL": #Needs making more general (rename interface.xml elements to match caps, auto select txt* field for output)
			output = XRCCTRL(self.chat, "txtGlobal")
		if channel == "LOCAL":
			output = XRCCTRL(self.chat, "txtLocal")
		if channel == "IRC":
			output = XRCCTRL(self.chat, "txtIRC")
		if channel == "SERVER":
			output = XRCCTRL(self.chat, "txtServer")
		output.AppendText("<" + user + "> " + message + "\n")
		
	def addCreature(self, event):
		name = event.data
		lstPeople = XRCCTRL(self.tools, "lstPeople")
		lstPeople.InsertStringItem(0, name)

	def delCreature(self, event):
		name = event.data
		lstPeople = XRCCTRL(self.tools, "lstPeople")
		item = lstPeople.FindItem(-1, name)
		lstPeople.DeleteItem(item)

