#!/usr/bin/env python

from Tkinter import *
import sys
from pygale import *
import time, tkMessageBox, re, string
import Pmw

TOXIN_VERSION = 'Toxin/1.0'

def getinstance():
	import socket, os
	dom = pygale.gale_env.get('GALE_DOMAIN', 'unknown domain')
	host = socket.gethostname()
	user = pygale.gale_env.get('GALE_USER', 'unknown user')
	if sys.platform == 'win32':
		display = 'Windows'
	else:
		display = pygale.gale_env.get('DISPLAY', 'unknown display')
	pid = str(os.getpid())
	return '%(dom)s/%(host)s/%(user)s/%(display)s/%(pid)s' % locals()

class App:
	def __init__(self):
		# Use the Tk event loop except on Windows
		if sys.platform != 'win32':
			pygale.engine.engine = pygale.engine.TkEngine()

		# Initialize PyGale
		pygale.init()
		# Register shutdown handler
		sys.exitfunc = pygale.shutdown

		# Create a gale client connection
		self.galeconn = pygale.GaleClient()

		self.galeconn.set_onconnect(self.makesub)
		self.galeconn.connect(self.connected)

	def makesub(self, host):
		"subscribe to my personal category"
		if host is None:
			print 'Unable to connect to Gale server'
			sys.exit(0)
		print 'Connected to', host

		# Construct my user category
		mysub = pygale.gale_env.get('GALE_SUBS')
		if mysub is not None:
			sub = mysub
		else:
			sub = pygale.id_category(pygale.gale_user(), 'user', '')
		self.galeconn.sub_to(sub)
		print 'Subscribed to', sub

		# Set up a callback on new puffs
		self.galeconn.set_puff_callback(self.recvpuff)

	def connected(self, host):
		if host is None:
			print 'Unable to connect to a gale server'
			sys.exit()
		print 'Connected to', host

	def recvpuff(self, puff):
		PuffPop(puff, self.galeconn)

class PuffPop(Toplevel):
	def __init__(self, puff, galeconn):
		Toplevel.__init__(self)
		self.protocol('WM_DELETE_WINDOW', self.destroy)
		self.puff = puff
		self._create_widgets()
		self.galeconn = galeconn

	def _create_widgets(self):
		self.text = Pmw.ScrolledText(self, text_height=8)
		self.text.pack(expand=1, fill=BOTH)
		header = '%s on [%s]\n\n' %\
			(self.puff.get('message/sender', 'Anonymous'),
			self.puff.get_cat())
		self.title(header)

		self.text.insert('end', 'From: %s\n' %
			self.puff.get('message/sender', 'Anonymous'))
		if self.puff.get('id/time', None) is not None:
			timestr = 'Time: %s\n' % time.strftime("%l:%M %p %a %m/%d",
                         time.localtime(self.puff.get('id/time')))
			self.text.insert('end', timestr)
		bodytext = re.sub('\r\n', '\n', self.puff.get('message/body',
			''))
		self.text.insert('end', bodytext)

		replybut = Button(self, command=self.reply, text='Reply')
		replybut.pack(side=TOP, expand=1, fill=X)

		closebut = Button(self, command=self.destroy, text='Close')
		closebut.pack(side=TOP, expand=1, fill=X)
	
	def reply(self):
		ComposePop(self.galeconn, recp=self.puff.get_signer())

class ComposePop(Toplevel):
	def __init__(self, galeconn, recp=None):
		Toplevel.__init__(self)
		self.galeconn = galeconn
		self.puff = pygale.Puff()
		self.protocol('WM_DELETE_WINDOW', self.destroy)
		self.bad_ids = []
		self.recp_ids = []
		self.cat = []
		self.recp_names = []
		self._create_widgets(recp)

	def _create_widgets(self, recp=None):
		f = Frame(self)
		f.pack(side=TOP, expand=1, fill=BOTH)
		label = Label(f, text='To:')
		label.pack(side=LEFT, expand=0)
		self.recipient = Entry(f)
		self.recipient.pack(side=RIGHT, expand=1, fill=X)

		self.text = Pmw.ScrolledText(self, text_height=8)
		self.text.pack(expand=1, fill=BOTH)
		self.text.component('text').bind('<Control-Return>',
			self.send)
		self.text.component('text').bind('<Control-c>',
			lambda e, s=self: s.destroy())
		self.recipient.bind('<Control-Return>',
			self.send)
		self.recipient.bind('<Control-c>',
			lambda e, s=self: s.destroy())

		sendbut = Button(self, command=self.send,
			text='Send (Ctrl-Enter)')
		sendbut.pack(side=TOP, expand=1, fill=X)
		closebut = Button(self, command=self.destroy,
			text='Cancel (Ctrl-c)')
		closebut.pack(side=TOP, expand=1, fill=X)

		if recp:
			self.recipient.insert('end', recp)
			self.text.component('text').focus_set()
		else:
			self.recipient.focus_set()
	
	def send(self, *args):
		recplist = string.split(self.recipient.get())
		recplist = map(pygale.expand_id, recplist)
		if not recplist:
			m = tkMessageBox.showerror(
				title='No recipients',
				parent=self,
				message='At least one recipient must be specified.')
			return
		self.num_priv_recips = len(recplist)
		for p in recplist:
			pygale.lookup_id(p, lambda i, s=self, p=p:
				s.collect_ids(p, i))
	
	def collect_ids(self, req_id, id_info):
		id, fullname = id_info
		if id is None or fullname is None:
			self.bad_ids.append(req_id)
		else:
			self.cat.append(pygale.id_category(id, 'user', ''))
			self.recp_names.append(fullname)
			self.recp_ids.append(id)
		if len(self.recp_ids) + len(self.bad_ids) == self.num_priv_recips:
			# All ids collected
			self.puff.set_text('message/recipient',
							   string.join(self.recp_names, ','))
			self.send_puff()

	def send_puff(self):
		if self.bad_ids:
			if len(self.bad_ids) == 1:
				msg = 'Cannot find user ' + self.bad_ids[0]
			else:
				msg='Cannot find users:\n' + string.join(self.bad_ids, '\n')
			m = tkMessageBox.showerror(
				title='Error: recipient list',
				parent=self.galewin,
				message=msg)
			return
		self.puff.set_time('id/time', int(time.time()))
		self.puff.set_text('id/instance', getinstance())
		self.puff.set_text('message/sender', gale_env.get('GALE_FROM'))
		self.puff.set_cat(string.join(self.cat, ':'))
		self.puff.set_text('id/class', TOXIN_VERSION)
		self.puff.set_text('message/body',
			re.sub('\n', '\r\n', self.text.get('1.0', 'end-1c') + '\n'))
		out = self.puff.sign_message(pygale.gale_user())
		if out is None:
			# can't sign
			print "Can't sign puff"
			return
		out.encrypt_message(self.recp_ids, lambda puff, s=self:
			s.finish_send(puff))

	def finish_send(self, puff):
		# TODO: check for errors
		self.galeconn.transmit_puff(puff)
		self.destroy()

class MainWin(Frame):
	def __init__(self, galeconn, master=None):
		Frame.__init__(self, master)
		self.master.title('Toxin')
		self.pack(expand=1, fill=BOTH)
		self._create_widgets()
		self.galeconn = galeconn
	
	def _create_widgets(self):
		new = Button(self, command=self.newpuff, text='Send puff')
		new.pack(side=TOP, expand=1, fill=X)
		close = Button(self, command=self.confirmquit, text='Quit Toxin')
		close.pack(side=TOP, expand=1, fill=X)
	
	def confirmquit(self):
		m = tkMessageBox.askyesno(title='Really quit',
			message='Really leave Toxin?')
		if m:
			self.quit()
	
	def newpuff(self):
		ComposePop(self.galeconn)

def main():
	a = App()
	win = MainWin(a.galeconn)
	win.mainloop()

if __name__ == '__main__':
	main()

