#
# Pyne msgviewbox module.
#
# The main window is 0wned by a 'user' object,
# which contains all the smelly data.
#

import utils
from utils import _u
import gtk
import gobject
import pango
import re
import ptk.misc_widgets
import cStringIO

import string
from viewattach import *
from pyneheaders import *
import time
import pynei18n

# Messages with stuff in the form '(1/5)' at the end of the subject
# may be spanned attachment messages (dunno the technical term).
# It is possible there aren't at the end... need to find last instance of this actually
RE_PART_NUM = re.compile(r"[\(\{\[]\d+/\d+[\)\}\]]")
RE_URL      = re.compile(r"((http://\w+)|(www))(\.\w+)+(\/\S+)+")

class search_box(gtk.Window):
	def __init__(self, msg_view, user, parent_win):
		self.user = user
		self.msg_view = msg_view
		self.parent_win = parent_win
		# create pretty find boxy thing
		gtk.Window.__init__(self)
		self.set_transient_for(parent_win)
		self.set_title(_("Pyne - Find in message"))
		self.connect("delete_event", self.close)
		box = gtk.VBox()
		self.add(box)
		box.show()

		def _key_press(w, event):
			# If the user hits return do a search
			if event.string == '\r':
				self.do_find()

		# Find: [ something ]   text entry
		box1 = gtk.HBox()
		box.pack_start(box1, expand=False)
		box1.set_border_width(5)
		box1.show()
		label = gtk.Label(_("Find: "))
		box1.pack_start(label, expand=False)
		label.show()
		self.find_entry = gtk.Entry()
		self.find_entry.connect("key_press_event", _key_press)
		box1.pack_start(self.find_entry)
		self.find_entry.show()

		# position of find in body text.
		self.searchpos = 0

		# Settings
		box2 = gtk.HBox()
		box2.set_border_width(5)
		box.pack_start(box2, expand=False)
		box2.show()
		self.case_sensitive_cb = gtk.CheckButton(_("Case Sensitive"))
		box2.pack_start(self.case_sensitive_cb, expand=False)
		self.case_sensitive_cb.show()

		# Button box
		buttonbox = ptk.misc_widgets.MyButtonBox()
		box.pack_start(buttonbox, expand=False)
		buttonbox.show()

		# Buttons [cont]
		button = gtk.Button(stock="gtk-go-back")
		button.connect("clicked", self.do_find, 1)
		button.show()
		buttonbox.pack_end(button, expand=False)

		button = gtk.Button(stock="gtk-go-forward")
		button.connect("clicked", self.do_find, 0)
		button.show()
		buttonbox.pack_end(button, expand=False)

		button = gtk.Button(stock="gtk-close")
		button.connect("clicked", self.close)
		button.show()
		buttonbox.pack_end(button, expand=False)

		self.show()

	def close(self, _a=None, _b=None):
		self.parent_win.subwindows.remove(self)
		self.destroy()

	def do_find(self, _b=None, backwards=0):
		user = self.user
			
		text_buffer = self.msg_view.body_textview.get_buffer()
		msg_body = text_buffer.get_slice(text_buffer.get_start_iter(), text_buffer.get_end_iter(), 0)
		
		find_me = self.find_entry.get_text()

		if not self.case_sensitive_cb.get_active():
			# Mangle everything to lowercase if case insensitive search desired
			msg_body = msg_body.lower()
			find_me = find_me.lower()

		# forwards or backwards search
		if backwards:
			start = msg_body[:self.searchpos-1].rfind(find_me)
			self.searchpos = start + 1
		else:
			start = string.find(msg_body[self.searchpos:], find_me)
			self.searchpos = self.searchpos + start + 1

		if start != -1:
			#body_obj.select_region(self.searchpos - 1, self.searchpos+len(find_me)-1)
			text_buffer.move_mark_by_name("insert", text_buffer.get_iter_at_offset(self.searchpos-1))
			text_buffer.move_mark_by_name("selection_bound", text_buffer.get_iter_at_offset(self.searchpos+len(find_me)-1))
		
			self.msg_view.body_textview.scroll_to_iter( text_buffer.get_iter_at_offset(self.searchpos-1), 0.0 )
		else:
			# hit end of file with no find
			self.searchpos = 0


class msg_view_box(gtk.Notebook):
	"""
	Message viewing object.
	"""
	def __init__(self, user, parent_win):
		self.parent_win = parent_win
		gtk.Notebook.__init__(self)
		self.set_tab_pos(user.tab_pos)

		self.user = user
		# Tag table for body & source
		self.tag_table = gtk.TextTagTable()

		# Body text
		self.body_label = gtk.Label(_("Body"))

		swin = gtk.ScrolledWindow()
		swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
		swin.set_shadow_type(gtk.SHADOW_IN)
		
		self.body_textview = gtk.TextView()
		self.body_textview.set_buffer(gtk.TextBuffer(self.tag_table))
		self.body_textview.set_editable(False)
		self.body_textview.set_wrap_mode (gtk.WRAP_WORD)
		self.body_textview.modify_font ( pango.FontDescription (user.bodyfont) )
		self.body_textview.show()

		self.append_page(swin, self.body_label)
		swin.add(self.body_textview)
		swin.show()

		# Message source
		self.source_label = gtk.Label(_("Source"))

		swin = gtk.ScrolledWindow()
		swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
		swin.set_shadow_type(gtk.SHADOW_IN)
		
		self.source_textview = gtk.TextView()
		self.source_textview.set_buffer(gtk.TextBuffer(self.tag_table))
		self.source_textview.set_editable(False)
		self.source_textview.modify_font ( pango.FontDescription (user.bodyfont) )
		self.source_textview.show()

		self.append_page(swin, self.source_label)
		swin.add(self.source_textview)
		swin.show()

		# Some text tags
		self.tag_table.add(gtk.TextTag("header"))
		self.tag_table.add(gtk.TextTag("quote"))
		self.tag_table.add(gtk.TextTag("url"))

		# attachments
		box = gtk.VBox()
		self.attachments_label = gtk.Label(_("Attachments"))
		self.append_page(box, self.attachments_label)
		box.show()

		cell = gtk.CellRendererText()
		self.attachment_list = gtk.TreeView()
		
		col = gtk.TreeViewColumn(_("Type"), cell, text=0)
		col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
		self.attachment_list.append_column(col)
		col = gtk.TreeViewColumn(_("Name"), cell, text=1)
		col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
		self.attachment_list.append_column(col)
		col = gtk.TreeViewColumn(_("Size"), cell, text=2)
		col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
		self.attachment_list.append_column(col)
		
		model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_INT)
		self.attachment_list.set_model(model)
		
		self.selected_attachment = None
		
		swin = gtk.ScrolledWindow()
		swin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
		swin.set_shadow_type(gtk.SHADOW_IN)
		box.pack_start(swin)
		swin.show()
		swin.add(self.attachment_list)
		self.attachment_list.show()

		buttonbox = ptk.misc_widgets.MyButtonBox()
		buttonbox.set_border_width(5)
		box.pack_start(buttonbox, expand=False)
		buttonbox.show()

		def _on_click(widget, event):
			x, y = event.x, event.y
			# Path of clicked on item
			selected = widget.get_path_at_pos(int(x),int(y))
			if selected == None:
				return
			model = self.attachment_list.get_model()
			# Get tree iter from path
			iter = model.get_iter(selected[0])
			# Invisible field gives attachment index
			self.selected_attachment = model.get_value(iter, 3)

			if event.type == gtk.gdk._2BUTTON_PRESS:
				self.v_attach(widget)
		
		# View attachment on click
		self.attachment_list.connect("button_press_event", _on_click)

		button = gtk.Button(_("Save"))
		button.connect("clicked", self.s_attach)
		buttonbox.pack_end(button, expand=False)
		button.show()

		button = gtk.Button(_("View"))
		button.connect("clicked", self.v_attach)
		buttonbox.pack_end(button, expand=False)
		button.show()
		
		# last shown message
		self.last_message = None

	def update_settings (self):
		"""
		User preferences have changed (fonts, etc).
		"""
		self.source_textview.modify_font ( pango.FontDescription (self.user.bodyfont) )
		self.body_textview.modify_font ( pango.FontDescription (self.user.bodyfont) )
		if self.__dict__.has_key ("header_tag"):
			self.header_tag.set_property ("foreground", self.user.col_header)
		if self.__dict__.has_key ("quote_tag"):
			self.quote_tag.set_property ("foreground", self.user.col_quote)
		if self.__dict__.has_key ("url_tag"):
			self.url_tag.set_property ("foreground", self.user.col_quote)

	def join_spanned_attachment(self):
		def get_subj_end(subj):
			# Last match is the one we desire
			end = RE_PART_NUM.findall(subj)[-1]
			end = re.findall("\d+/\d+", end)[-1]
			part1, part2 = end.split("/")
			part1 = int(part1)
			part2 = int(part2)
			return part1, part2
		
		errors = ""
		# get (1/2) end bit
		num, total = get_subj_end(self.last_message.headers["subject"])

		if total == 1:
			# Only one message in series
			return
		if num > 1:
			print "Select first message in series to join parts."
			return
		# Find the start of the subject then...
		pos = 0
		while 1:
			# find last match of (2/3) type thing
			match = RE_PART_NUM.search(self.last_message.headers["subject"][pos:])
			if match == None:
				break
			pos = pos + match.span()[0] + 1
		seriesubj = self.last_message.headers["subject"][:pos-1]
		print "Common series subject is '%s'" % seriesubj
		# Go through messages in this folder looking for ones
		# with correct start

		# Dictionary of messages in series.
		# key = index. data = msg_id
		in_series = { 1: self.last_message.headers["message-id"] }
		
		for id in self.folder.messages:
			msg = self.folder.load_header(id)
			if msg == None:
				continue
			if msg[HEAD_SUBJECT].find(seriesubj) == 0:
				# Yay! it is in the series
				# XXX need to check if still correct form
				print "Found ", msg[HEAD_SUBJECT]
				num, _poo = get_subj_end(msg[HEAD_SUBJECT])
				# But body isn't downloaded...
				if msg[HEAD_OPTS] & MSG_NO_BODY:
					print _("Body not downloaded on part %d") % num
					errors = errors + "\n" + _("Body not downloaded on part %d") % num
					continue
				in_series[num] = msg[HEAD_MESSAGE_ID]
		print in_series
		
		# Sometimes part zero is text bullshit and has no attachment. Give them if so
		if len(self.last_message.parts_header) < 2:
			self.last_message.parts_header.append({})
		if len(self.last_message.parts_text) < 2:
			self.last_message.parts_text.append("")
		
		# Add their attachments
		nicked_header = 0
		big_attach = cStringIO.StringIO()
		for i in range(0, total+1):
			if not in_series.has_key(i):
				print _("Warning: Missing message number %d") % i
				if i != 0:
					# Don't add missing part zero to errors. it is normal
					errors = errors + "\n" + _("Warning: Missing message number %d") % i				
				continue
			msg = self.folder.load_article( in_series[i] )
			
			if len(msg.parts_text) < 2:
				print _("Warning: Message number %d has no attachment") % i
				if i != 0:
					# This is common on part zero...
					errors = errors + "\n" + _("Warning: Message number %d has no attachment") % i
				continue
			# set attachment header to that of first message in series
			# (so we get correct mime type)
			if i==0 or (i==1 and nicked_header==0):
				self.last_message.parts_header[1] = msg.parts_header[1]
				nicked_header = 1
			
			big_attach.write(msg.parts_text[1])
		# save it to first message, confusingly called 'last_message'...
		big_attach.seek(0)
		self.last_message.parts_text[1] = big_attach.read()
	
		self.update_attachments()
		
		# Give the user any error messages.
		if errors:
			ptk.misc_widgets.InfoBox (_("Errors joining message parts"), _("There were some errors joining this split attachment:")+errors, gtk.STOCK_OK)
		
	def s_attach(self, _w=None):
		"""
		Get filename to save attachment.
		"""
		# Nothing selected
		if self.selected_attachment == None:
			return

		def _do_it(filename, msg=self.last_message, selected=self.selected_attachment):
			if filename:
				msg.save_attachment(selected, filename)

		# Open file selection box
		fsel = ptk.misc_widgets.FileSelectorBox(self.user,
			_("Save Attachment"), 
			self.last_message.get_attachment_info(self.selected_attachment)[1],
			_do_it, modal=True)
		fsel.set_transient_for(self.parent_win)
		fsel.show()

	def v_attach(self, _w=None):
		"""
		View attachment.
		"""
		# Nothing selected
		if self.selected_attachment == None:
			return

		# Show attachment
		view_attachment(self.last_message, self.selected_attachment, self.user, self.parent_win)
		
	def rot13_redisplay(self, user):
		"""
		Redisplay the current message with rot13alised body.
		"""
		if self.last_message == None:
			return
		self.last_message.parts_text[0] = self.last_message.parts_text[0].encode('rot13')
		self.dump_msg(user, self.last_message, self.folder)

	def clear(self):
		"""
		Clear current display.
		"""
		self.last_message = None
		
		textbuffer = self.body_textview.get_buffer()
		textbuffer.delete(textbuffer.get_start_iter(), textbuffer.get_end_iter())

		textbuffer = self.source_textview.get_buffer()
		textbuffer.delete(textbuffer.get_start_iter(), textbuffer.get_end_iter())

		self.attachment_list.clear()
		self.source_label.set_text(_("Source"))
		self.attachments_label.set_text(_("Attachments"))

	def dump_msg(self, user, message, folder):
		"""
		Dump the given message.
		"""
		self.last_message = message
		self.folder = folder
		############ Message body
		# Delete prev message
		body_textbuffer = self.body_textview.get_buffer()
		body_textbuffer.delete(body_textbuffer.get_start_iter(), body_textbuffer.get_end_iter())
		
		header_tag = self.tag_table.lookup("header")
		header_tag.set_property("foreground", user.col_header)
		self.header_tag = header_tag
		
		url_tag = self.tag_table.lookup ("url")
		url_tag.set_property("underline", pango.UNDERLINE_SINGLE)
		url_tag.set_property("foreground", user.col_quote)
		self.url_tag = url_tag
		# i like the new text widgets :-)
		def _some(tag, view, event, iter):
			if event.type != gtk.gdk.BUTTON_RELEASE:
				print event.type
				return
			print event.button
			iter.backward_to_tag_toggle (url_tag)
			# backward_to_tag_toggle should probably return a tag, rather that
			# operate on our existing tag. we do this to get an end tag for it to mutilate: XXX
			end_iter = body_textbuffer.get_iter_at_offset(iter.get_offset())
			end_iter.forward_to_tag_toggle (url_tag)
			url = iter.get_text(end_iter)
			if not RE_URL.match (url):
				# for some reason sometimes we register the event twice,
				# and the 2nd time the position is zero... fucky XXX XXX
				return
			f = open("debug.log", "a")
			f.write ("URL IS:::::::\n")
			f.write ("(iter pos = %d)\n" % iter.get_offset())
			f.write (url+"\n")
			f.close()
			if url:
				user.open_url (url)
		url_tag.connect("event", _some)
		
		quote_tag = self.tag_table.lookup("quote")
		quote_tag.set_property("foreground", user.col_quote)
		self.quote_tag = quote_tag

		# ^^^ If we don't set this the crash in message source dumping
		# mentioned below doesn't happen.

		# Some message info
		if message.headers.has_key("to"):
			body_textbuffer.insert(body_textbuffer.get_end_iter(), _("To")+": "+_u(message.headers["to"])+"\n")
		if message.headers.has_key("newsgroups"):
			body_textbuffer.insert(body_textbuffer.get_end_iter(), _("To")+": "+_u(message.headers["newsgroups"])+"\n")
		body_textbuffer.insert(body_textbuffer.get_end_iter(), _("From")+": "+_u(message.headers["from"])+"\n")
		try:
			if message.headers["reply-to"] != message.headers["from"]:
				body_textbuffer.insert(body_textbuffer.get_end_iter(), _("Reply-To")+": "+_u(message.headers["reply-to"])+"\n")
		except KeyError:
			pass
		body_textbuffer.insert(body_textbuffer.get_end_iter(), _("Subject")+": "+_u(message.headers["subject"])+"\n")
		body_textbuffer.insert(body_textbuffer.get_end_iter(), _("Date")+": "+time.strftime("%d %b %Y, %H:%M:%S", time.localtime(message.date))+"\n")
		# Attachment
		if len(message.parts_text) > 1:
			x = len(message.parts_text)
			a = _("Attachments")+": ("
			for y in range(1, x):
				a = a + message.get_attachment_info(y)[1]+", "
			a = a[:-2]+")"
			body_textbuffer.insert(body_textbuffer.get_end_iter(), _u(a)+"\n\n")
		else:
			body_textbuffer.insert(body_textbuffer.get_end_iter(), "\n")
		
		# Header colours
		body_textbuffer.apply_tag(header_tag, body_textbuffer.get_start_iter(), body_textbuffer.get_end_iter())

		# dump all ASCII parts
		for i in range(0, len(message.parts_header)):
			content_type = message.get_attachment_info(i)[0]
			if content_type[:4] == "text/plain" or i == 0:
				# Split body text into lines
				msg = message.external_parser(user, i)
				msg = string.split(msg, "\n")
				if i != 0:
					# extra header for parts beyond the 1st
					body_textbuffer.insert_with_tags(body_textbuffer.get_end_iter(), _("\n\nText part %d") % (i+1,)+"\n\n", header_tag)
				# Dump it
				for x in range (0, len(msg)):
					match = RE_URL.search(msg[x])
					if match:
						# Line with an url in it...
						url_s, url_e = match.span ()
						if msg[x][:1] == '>':
							body_textbuffer.insert_with_tags(body_textbuffer.get_end_iter(), _u(msg[x][:url_s]), quote_tag)
							body_textbuffer.insert_with_tags(body_textbuffer.get_end_iter(), _u(msg[x][url_s:url_e]), url_tag)
							body_textbuffer.insert_with_tags(body_textbuffer.get_end_iter(), _u(msg[x][url_e:])+"\n", quote_tag)
						else:
							body_textbuffer.insert(body_textbuffer.get_end_iter(), _u(msg[x][:url_s]))
							body_textbuffer.insert_with_tags(body_textbuffer.get_end_iter(), _u(msg[x][url_s:url_e]), url_tag)
							body_textbuffer.insert(body_textbuffer.get_end_iter(), _u(msg[x][url_e:])+"\n")
					else:
						# Quoted ([:1] == [0] w/o raising exception on "")
						if msg[x][:1] == '>':
							body_textbuffer.insert_with_tags(body_textbuffer.get_end_iter(), _u(msg[x])+"\n", quote_tag)
							continue
						# Normal
						else:
							body_textbuffer.insert(body_textbuffer.get_end_iter(), _u(msg[x])+"\n")
							continue

		# No body text downloaded. tell the user
		if (message.opts & MSG_NO_BODY):
			#self.body_text.insert(font, col_header, None, \
			pos = body_textbuffer.get_char_count()
			body_textbuffer.insert(body_textbuffer.get_end_iter(), \
				_("Body text not downloaded. Right click on message and select 'download body'.\n"))

		# Update label
		#self.body_label.set_text("Subject: "+message.headers["subject"])

		############## Message SOURCE
		# Delete prev message
		source_textbuffer = self.source_textview.get_buffer()
		source_textbuffer.delete(source_textbuffer.get_start_iter(), source_textbuffer.get_end_iter())

		# Do it quickly. There could be huge attachments
		x = string.find(message.body, "\n\n")
		if x != -1:
			# Make headers red
			source_textbuffer.insert_with_tags(source_textbuffer.get_end_iter(), _u(message.body[0:x]), header_tag)
			source_textbuffer.insert(source_textbuffer.get_end_iter(), _u(message.body[x:]))
		else:
			source_textbuffer.insert(source_textbuffer.get_end_iter(), _u(message.body))
		
		# Update label
		self.source_label.set_text(_("Source")+" ("+str(len(message.body)/1024)+"Kb)")

		# spanned attachment maybe
		if RE_PART_NUM.search(self.last_message.headers["subject"]):
			self.join_spanned_attachment()
		self.update_attachments()

	def update_attachments(self):
		# Attachment list
		self.selected_attachment = None
		model = self.attachment_list.get_model()
		model.clear()

		for x in xrange(0, len(self.last_message.parts_text)):
			a = self.last_message.get_attachment_info(x)

			iter = model.append()
			model.set(iter, 0, a[0], 1, a[1], 2, a[2], 3, x)

		self.attachments_label.set_text(_("Attachments")+" ("+str(len(self.last_message.parts_text)-1)+")")
		self.attachment_list.set_model(model)

	def search_message(self, user, parent_win):
		return search_box(self, user, parent_win)



syntax highlighted by Code2HTML, v. 0.9.1