#!/usr/bin/env python

# This file is part of Window-Switch.
# Copyright (c) 2009-2013 Antoine Martin <antoine@nagafix.co.uk>
# Window-Switch is released under the terms of the GNU GPL v3

import pygtk
pygtk.require("2.0")
import gtk
import gobject
import sys
import os

from winswitch.util.simple_logger import Logger, msig
logger = Logger("notification_util", log_colour=Logger.HIGHLIGHTED_BROWN)

from winswitch.consts import APPLICATION_NAME, NOTIFY_ERROR, NOTIFY_INFO, NOTIFY_MESSAGE, NOTIFY_AUTH_ERROR, NOTIFY_RETRY
from winswitch.globals import WIN32, OSX
from winswitch.util.common import no_newlines, escape_newlines
from winswitch.ui import icons

PREFER_DBUS = True

hasappindicator	= False
hasgrowl		= False
hasdbusnotify	= False
haspynotify		= False


def init_appindicator():
	try:
		import appindicator
		assert appindicator
		global hasappindicator
		hasappindicator=True
	except ImportError, e:
		logger.slog("no appindicator: %s" % e)

def init_growl():
	try:
		import Growl.GrowlNotifier		#@UnresolvedImport
		assert Growl.GrowlNotifier
		global hasgrowl
		hasgrowl=True
	except ImportError, e:
		logger.slog("no growl: %s" % e)

def init_dbus():
	try:
		import dbus.glib
		assert dbus.glib
		bus = dbus.SessionBus()
		obj = bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications')
		global dbusnotify
		dbusnotify = dbus.Interface(obj, 'org.freedesktop.Notifications')
		global hasdbusnotify
		hasdbusnotify = True
	except Exception, e:
		logger.slog("no dbus-notify: %s" % e)

def init_pynotify():
	try:
		import pynotify					#@UnresolvedImport
		pynotify.init(APPLICATION_NAME)
		global haspynotify
		haspynotify = True
	except Exception, e:
		logger.slog("no pynotify: %s" % e)

if sys.platform.startswith("darwin"):
	init_growl()
elif os.name=="posix":
	init_appindicator()
if not WIN32 and not OSX:
	if PREFER_DBUS:
		init_dbus()
	if not hasdbusnotify:
		init_pynotify()
	if not haspynotify and not PREFER_DBUS:
		init_dbus()


notification_util = None
def get_notification_util():
	global notification_util
	if not notification_util:
		notification_util = NotificationUtil()
	return	notification_util

class NotificationUtil:

	def __init__(self):
		Logger(self, log_colour=Logger.HIGHLIGHTED_BLUE)
		#notifications
		self.notifications = []
		self.notification_stack = None		#for gtk mode (no pynotify available / switched off)
		self.prefer_growl = True
		self.growl = None

	def close(self):
		#cleanup:
		self.close_notifications()

	#****************************************************************
	# Notification util
	def safe_notify(self, tray, title, message, delay=None, callback=None, notification_type=None):
		""" This method can be called from threads as it will use idle_add if needed to execute from the UI thread """
		self.sdebug(None, tray, no_newlines(title), no_newlines(message), delay, callback, notification_type)
		gobject.idle_add(self.notify, tray, title, message, delay, callback, notification_type)

	def notify(self, tray, title, message, delay=None, callback=None, notification_type=None):
		global haspynotify, hasdbusnotify, hasgrowl
		if notification_type==NOTIFY_ERROR:
			delay = delay or 10
			icon_name = "winswitch_warning"
		elif notification_type==NOTIFY_RETRY:
			icon_name = "retry"
		elif notification_type==NOTIFY_AUTH_ERROR:
			delay = delay or 10
			icon_name = "passport"
		elif notification_type==NOTIFY_INFO:
			icon_name = "information"
		elif notification_type==NOTIFY_MESSAGE:
			icon_name = "message"
		else:
			icon_name = "winswitch_logo"
		if delay is None:
			delay = 5
		icon = icons.get(icon_name)
		self.sdebug("hasdbusnotify=%s, haspynotify=%s, icon_name=%s, prefer_growl=%s, hasgrowl=%s, PREFER_DBUS=%s, platform=%s" % (hasdbusnotify, haspynotify, icon_name, self.prefer_growl, hasgrowl, PREFER_DBUS, sys.platform), tray, escape_newlines(title),escape_newlines(message),delay,callback,icon_name)
		if self.prefer_growl and hasgrowl:
			self.growlnotify(title, message, isSticky=(notification_type==NOTIFY_AUTH_ERROR))
			return	False

		if hasdbusnotify:
			self.dbusnotify(title, message, delay, callback, icon)
			return False

		if haspynotify:
			try:
				import glib
				try:
					self.pynotify(tray, title, message, delay, callback, icon)
					return False
				except glib.GError, e:
					self.serror("code=%s, domain=%s, message=%s" % (e.code, e.domain, e.message), tray, title, message, delay, callback, icon)
					if e.message and e.message.find("org.freedesktop.DBus.Error.ServiceUnknown")>=0:
						#GError: GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.Notifications was not provided by any .service files
						self.serror("disabling pynotify as there does not seem to be a notification daemon running", tray, title, message, delay, callback, icon)
						haspynotify = False
			except Exception, e:
				self.serror("error type: %s" % e.__class__)
				self.serror("error: %s" % dir(e))
				self.serr("py_notify failed (is the notification daemon running?), will use fallback code..", e, tray, title, message, delay, callback, icon)
		if tray:
			self.sdebug("hwnd=%s" % tray.getHWND(), tray, escape_newlines(title),escape_newlines(message),delay,callback,icon_name)

		if WIN32 and tray and tray.getHWND() is not None:			#prefer win32notify on win32, but only if we already use native tray widget which provides the hwnd
			try:
				self.win32notify(tray, title, message, delay, callback)
				return	False
			except Exception, e:
				self.serr("win32notify failed", e, tray, title, message, delay, callback, icon)
		try:
			self.gtkpopupnotify(tray, title, message, delay, callback, icon)
		except Exception, e:
			self.serr("gtkpopupnotify failed - all options exhausted!", e, tray, title, message, delay, callback, icon)
		return	False

	def growlnotify(self, title, message, isSticky=False):
		import Growl.GrowlNotifier		#@UnresolvedImport
		class gNotifier(Growl.GrowlNotifier):
			applicationName = APPLICATION_NAME
			notifications = ['highlight']
			#applicationIcon = Growl.Image.imageWithIconForApplication("iChat")
			app_icon = icons.get("winswitch")
			applicationIcon = Growl.Image.imageFromPath(app_icon.full_path_to_icon_file)
		if not self.growl:
			self.growl = gNotifier()
			self.growl.register()
		self.growl.notify('highlight', title, message, "", isSticky)


	def win32notify(self, tray, title, message, delay, callback=None):
		hwnd = tray.getHWND()
		sig = msig(hwnd, no_newlines(title), no_newlines(message), delay, callback)
		if not hwnd or hwnd<=0:
			self.error(sig+" cannot find our window's hwnd: %s" % hwnd)
			return
		self.debug(sig+" hwnd=%s" % hwnd)
		from winswitch.ui.win32_balloon import notify
		if callback:
			label, cb = callback
			message += "\nClick to %s" % label
			def balloon_clicked(*args):
				self.slog("label=%s" % label, *args)
				cb()
			tray.balloon_click_callback = balloon_clicked
		notify(hwnd, title, message, delay or 10)

	def gtkpopupnotify(self, tray, title, message, delay, callback, icon):
		self.slog(None, tray, no_newlines(title),no_newlines(message), delay, callback)
		from winswitch.ui.gtkPopupNotify import NotificationStack
		if not self.notification_stack:
			self.notification_stack = NotificationStack(timeout=6)

		#self.notification_stack.show_timeout = False
		self.notification_stack.edge_offset_x = 20
		xy = self.get_notify_xy(tray)
		if xy:
			(x, y) = xy
			self.notification_stack.x = x
			self.notification_stack.y = y
		popup = self.notification_stack.new_popup(title=title, message=message, callback=callback, image=icon)
		try:
			from winswitch.ui.ui_util import get_ui_util
			def close_popup(*args):
				self.notification_stack.destroy_popup_cb(popup)
			get_ui_util().add_close_accel(popup, close_popup)
		except Exception, e:
			self.serror("failed to add keyboard shortcuts to gtk notification: %s" % e)

	def dbusnotify(self, title, message, delay, callback, icon):
		global dbusnotify
		def cbReply(*args):
			self.slog(None, *args)
			return False
		def cbError(*args):
			self.slog(None, *args)
			return False
		iconpath = ""
		if icon and hasattr(icon, "full_path_to_icon_file"):
			iconpath = icon.full_path_to_icon_file
		dbusnotify.Notify(APPLICATION_NAME, 0, iconpath, title, message, [], [], delay,
             reply_handler = cbReply,
             error_handler = cbError)

	def pynotify(self, tray, title, message, delay, callback, icon):
		self.sdebug(None, tray, no_newlines(title), no_newlines(message), delay, callback, icon)
		#Find out position on screen
		import pynotify
		n = pynotify.Notification(title, message)
		n.set_icon_from_pixbuf(icon)
		n.set_urgency(pynotify.URGENCY_LOW)
		n.set_timeout(delay*1000)
		n.set_category("presence.online")
		xy = self.get_notify_xy(tray)
		if xy:
			(x, y) = xy
			n.set_hint("x", x)
			n.set_hint("y", y)
		if callback and not hasappindicator:
			# appindicator means that this notification would
			# show up as a really ugly alert box dialog
			label, cb = callback
			n.add_action("default", label, cb)
		self.show_notification(n, delay)

	def show_notification(self, n, delay):
		self.notifications.append(n)
		n.show()
		# We need to keep track of them or the callback does not fire! (garbage collected?)
		gobject.timeout_add(1000*delay, self.delete_notification, n)			#delete reference after use
		return False

	def get_notify_xy(self, tray):
		geom = None
		if tray and tray.get_geometry:
			geom = tray.get_geometry()
		if not geom:
			if sys.platform.startswith("darwin"):		#on OSX: default to top-right corner
				display = gtk.gdk.display_get_default()
				screen = display.get_default_screen()
				return	(screen.get_width()-200, 32)
			return	None
		(screen, rect, orientation) = geom
		if orientation == gtk.ORIENTATION_VERTICAL:
			x = rect.x + rect.width
			y = rect.y + rect.height/2
		else:
			x = rect.x + rect.width/2
			y = rect.y + rect.height
		return (x, y)

	def delete_notification(self, notification):
		if notification in self.notifications:
			self.notifications.remove(notification)
		return	False

	def close_notifications(self):
		_prev = self.notifications
		self.notifications = []
		for notification in _prev:
			try:
				notification.close()
			except:
				pass
