import gtk, gobject

from zeroinstall.injector import basedir
from zeroinstall.injector.iface_cache import iface_cache
from zeroinstall.injector import model
import properties
from treetips import TreeTips
from gui import policy
from zeroinstall import support
from logging import warn

def _stability(impl):
	assert impl
	if impl.user_stability is None:
		return impl.upstream_stability
	return _("%s (was %s)") % (impl.user_stability, impl.upstream_stability)

ICON_SIZE = 20.0
CELL_TEXT_INDENT = int(ICON_SIZE) + 4

class InterfaceTips(TreeTips):
	def get_tooltip_text(self, item):
		interface, model_column = item
		assert interface
		if model_column == InterfaceBrowser.INTERFACE_NAME:
			return _("Full name: %s") % interface.uri
		elif model_column == InterfaceBrowser.SUMMARY:
			if not interface.description:
				return None
			first_para = interface.description.split('\n\n', 1)[0]
			return first_para.replace('\n', ' ')

		impl = policy.implementation.get(interface, None)
		if not impl:
			return _("No suitable implementation was found. Check the "
				 "interface properties to find out why.")

		if model_column == InterfaceBrowser.VERSION:
			text = _("Currently preferred version: %s (%s)") % \
					(impl.get_version(), _stability(impl))
			old_impl = policy.original_implementation.get(interface, None)
			if old_impl is not None and old_impl is not impl:
				text += _('\nPreviously preferred version: %s (%s)') % \
					(old_impl.get_version(), _stability(old_impl))
			return text

		assert model_column == InterfaceBrowser.DOWNLOAD_SIZE

		if policy.get_cached(impl):
			return _("This version is already stored on your computer.")
		else:
			src = policy.get_best_source(impl)
			if not src:
				return _("No downloads available!")
			return _("Need to download %s (%s bytes)") % \
					(support.pretty_size(src.size), src.size)

tips = InterfaceTips()

class IconAndTextRenderer(gtk.GenericCellRenderer):
	__gproperties__ = {
		"image": (gobject.TYPE_OBJECT, "Image", "Image", gobject.PARAM_READWRITE),
		"text": (gobject.TYPE_STRING, "Text", "Text", "-", gobject.PARAM_READWRITE),
	}

	def do_set_property(self, prop, value):
		setattr(self, prop.name, value)

	def on_get_size(self, widget, cell_area, layout = None):
		if not layout:
			layout = widget.create_pango_layout(self.text)
		a, rect = layout.get_pixel_extents()

		pixmap_height = self.image.get_height()

		both_height = max(rect[1] + rect[3], pixmap_height)

		return (0, 0,
			rect[0] + rect[2] + CELL_TEXT_INDENT,
			both_height)

	def on_render(self, window, widget, background_area, cell_area, expose_area, flags):
		layout = widget.create_pango_layout(self.text)
		a, rect = layout.get_pixel_extents()

		if flags & gtk.CELL_RENDERER_SELECTED:
			state = gtk.STATE_SELECTED
		elif flags & gtk.CELL_RENDERER_PRELIT:
			state = gtk.STATE_PRELIGHT
		else:
			state = gtk.STATE_NORMAL

		image_y = int(0.5 * (cell_area.height - self.image.get_height()))
		window.draw_pixbuf(widget.style.white_gc, self.image, 0, 0,
				cell_area.x,
				cell_area.y + image_y)

		text_y = int(0.5 * (cell_area.height - (rect[1] + rect[3])))

		widget.style.paint_layout(window, state, True,
			expose_area, widget, "cellrenderertext",
			cell_area.x + CELL_TEXT_INDENT,
			cell_area.y + text_y,
			layout)

if gtk.pygtk_version < (2, 8, 0):
	# Note sure exactly which versions need this.
	# 2.8.0 gives a warning if you include it, though.
	gobject.type_register(IconAndTextRenderer)

class InterfaceBrowser(gtk.ScrolledWindow):
	model = None
	root = None
	edit_properties = None
	cached_icon = None

	INTERFACE = 0
	INTERFACE_NAME = 1
	VERSION = 2
	SUMMARY = 3
	DOWNLOAD_SIZE = 4
	ICON = 5

	columns = [(_('Interface'), INTERFACE_NAME),
		   (_('Version'), VERSION),
		   (_('Fetch'), DOWNLOAD_SIZE),
		   (_('Description'), SUMMARY)]

	def __init__(self):
		gtk.ScrolledWindow.__init__(self)

		self.cached_icon = {}	# URI -> GdkPixbuf
		self.default_icon = self.style.lookup_icon_set(gtk.STOCK_EXECUTE).render_icon(self.style,
			gtk.TEXT_DIR_NONE, gtk.STATE_NORMAL, gtk.ICON_SIZE_SMALL_TOOLBAR, self, None)

		self.edit_properties = gtk.Action('edit_properties',
			  'Interface Properties...',
			  'Set which implementation of this interface to use.',
			  gtk.STOCK_PROPERTIES)
		self.edit_properties.set_property('sensitive', False)

		self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
		self.set_shadow_type(gtk.SHADOW_IN)

		self.model = gtk.TreeStore(object, str, str, str, str, gtk.gdk.Pixbuf)
		self.tree_view = tree_view = gtk.TreeView(self.model)

		column_objects = []

		text = gtk.CellRendererText()

		for name, model_column in self.columns:
			if model_column == InterfaceBrowser.INTERFACE_NAME:
				column = gtk.TreeViewColumn(name, IconAndTextRenderer(),
						text = model_column,
						image = InterfaceBrowser.ICON)
			else:
				column = gtk.TreeViewColumn(name, text, text = model_column)
			tree_view.append_column(column)
			column_objects.append(column)

		self.add(tree_view)
		tree_view.show()

		tree_view.set_enable_search(True)

		selection = tree_view.get_selection()

		def motion(tree_view, ev):
			if ev.window is not tree_view.get_bin_window():
				return False
			pos = tree_view.get_path_at_pos(int(ev.x), int(ev.y))
			if pos:
				path = pos[0]
				try:
					col_index = column_objects.index(pos[1])
				except ValueError:
					tips.hide()
				else:
					col = self.columns[col_index][1]
					row = self.model[path]
					item = (row[InterfaceBrowser.INTERFACE], col)
					if item != tips.item:
						tips.prime(tree_view, item)
			else:
				tips.hide()

		tree_view.connect('motion-notify-event', motion)
		tree_view.connect('leave-notify-event', lambda tv, ev: tips.hide())

		def sel_changed(sel):
			store, iter = sel.get_selected()
			self.edit_properties.set_property('sensitive', iter != None)
		selection.connect('changed', sel_changed)

		def button_press(tree_view, bev):
			if bev.button == 3 and bev.type == gtk.gdk.BUTTON_PRESS:
				pos = tree_view.get_path_at_pos(int(bev.x), int(bev.y))
				if not pos:
					return False
				path, col, x, y = pos
				selection.select_path(path)
				iface = self.model[path][InterfaceBrowser.INTERFACE]
				self.show_popup_menu(iface, bev)
				return True
			if bev.button != 1 or bev.type != gtk.gdk._2BUTTON_PRESS:
				return False
			pos = tree_view.get_path_at_pos(int(bev.x), int(bev.y))
			if not pos:
				return False
			path, col, x, y = pos
			properties.edit(self.model[path][InterfaceBrowser.INTERFACE])
		tree_view.connect('button-press-event', button_press)

		def edit_selected(action):
			store, iter = selection.get_selected()
			assert iter
			properties.edit(self.model[iter][InterfaceBrowser.INTERFACE])
		self.edit_properties.connect('activate', edit_selected)

		self.connect('destroy', lambda s: policy.watchers.remove(self.build_tree))
		policy.watchers.append(self.build_tree)

	def set_root(self, root):
		assert isinstance(root, model.Interface)
		self.root = root
		policy.recalculate()	# Calls build_tree
	
	def get_icon(self, iface):
		"""Get an icon for this interface. If the icon is in the cache, use that.
		If not, start a download. If we already started a download (successful or
		not) do nothing. Returns None if no icon is currently available."""
		try:
			return self.cached_icon[iface.uri]
		except KeyError:
			path = policy.get_icon_path(iface)
			if path:
				try:
					loader = gtk.gdk.PixbufLoader('png')
					try:
						loader.write(file(path).read())
					finally:
						loader.close()
					icon = loader.get_pixbuf()
				except Exception, ex:
					warn("Failed to load cached PNG icon: %s", ex)
					return None
				w = icon.get_width()
				h = icon.get_height()
				scale = max(w, h, 1) / ICON_SIZE
				icon = icon.scale_simple(int(w / scale),
							 int(h / scale),
							 gtk.gdk.INTERP_BILINEAR)
				self.cached_icon[iface.uri] = icon
				return icon

		return None

	def build_tree(self):
		if policy.original_implementation is None:
			policy.set_original_implementations()

		done = {}	# Detect cycles

		self.model.clear()
		parent = None
		def add_node(parent, iface):
			if iface in done:
				return
			done[iface] = True

			iter = self.model.append(parent)
			self.model[iter][InterfaceBrowser.INTERFACE] = iface
			self.model[iter][InterfaceBrowser.INTERFACE_NAME] = iface.get_name()
			self.model[iter][InterfaceBrowser.SUMMARY] = iface.summary
			self.model[iter][InterfaceBrowser.ICON] = self.get_icon(iface) or self.default_icon

			impl = policy.implementation.get(iface, None)
			if impl:
				old_impl = policy.original_implementation.get(iface, None)
				version_str = impl.get_version()
				if old_impl is not None and old_impl is not impl:
					version_str += " (was " + old_impl.get_version() + ")"
				self.model[iter][InterfaceBrowser.VERSION] = version_str

				if policy.get_cached(impl):
					if impl.id.startswith('/'):
						fetch = '(local)'
					elif impl.id.startswith('package:'):
						fetch = '(package)'
					else:
						fetch = '(cached)'
				else:
					src = policy.get_best_source(impl)
					if src:
						fetch = support.pretty_size(src.size)
					else:
						fetch = '(unavailable)'
				self.model[iter][InterfaceBrowser.DOWNLOAD_SIZE] = fetch
				if hasattr(impl, 'requires'):
					children = impl.requires
				else:
					children = impl.dependencies

				for child in children:
					if isinstance(child, model.InterfaceDependency):
						add_node(iter, iface_cache.get_interface(child.interface))
					else:
						child_iter = self.model.append(parent)
						self.model[child_iter][InterfaceBrowser.INTERFACE_NAME] = '?'
						self.model[child_iter][InterfaceBrowser.SUMMARY] = \
							'Unknown dependency type : %s' % child
						self.model[child_iter][InterfaceBrowser.ICON] = self.default_icon
			else:
				self.model[iter][InterfaceBrowser.VERSION] = '(choose)'
		add_node(None, self.root)
		self.tree_view.expand_all()

	def show_popup_menu(self, iface, bev):
		import bugs

		if properties.have_source_for(iface):
			def compile_cb():
				import compile
				compile.compile(iface)
		else:
			compile_cb = None

		menu = gtk.Menu()
		for label, cb in [(_('Show Feeds'), lambda: properties.edit(iface)),
				  (_('Show Versions'), lambda: properties.edit(iface, show_versions = True)),
				  (_('Report a Bug...'), lambda: bugs.report_bug(policy, iface)),
				  (_('Compile...'), compile_cb)]:
			item = gtk.MenuItem(label)
			if cb:
				item.connect('activate', lambda item, cb=cb: cb())
			else:
				item.set_sensitive(False)
			item.show()
			menu.append(item)
		menu.popup(None, None, None, bev.button, bev.time)


syntax highlighted by Code2HTML, v. 0.9.1