"""
A Python module that enables posting notifications to the Growl daemon.
See <http://growl.info/> for more information.
"""
__version__ = "0.7" 
__author__ = "Mark Rowe <bdash@users.sourceforge.net>"
__copyright__ = "(C) 2003 Mark Rowe <bdash@users.sourceforge.net>. Released under the BSD license."
__contributors__ = ["Ingmar J Stein (Growl Team)", 
					"Rui Carmo (http://the.taoofmac.com)",
					"Jeremy Rossi <jeremy@jeremyrossi.com>",
					"Peter Hosey <http://boredzo.org/> (Growl Team)",
				   ]

import _growl
import types
import struct
import md5
import socket

GROWL_UDP_PORT=9887
GROWL_PROTOCOL_VERSION=1
GROWL_TYPE_REGISTRATION=0
GROWL_TYPE_NOTIFICATION=1

GROWL_APP_NAME="ApplicationName"
GROWL_APP_ICON="ApplicationIcon"
GROWL_NOTIFICATIONS_DEFAULT="DefaultNotifications"
GROWL_NOTIFICATIONS_ALL="AllNotifications"
GROWL_NOTIFICATIONS_USER_SET="AllowedUserNotifications"

GROWL_NOTIFICATION_NAME="NotificationName"
GROWL_NOTIFICATION_TITLE="NotificationTitle"
GROWL_NOTIFICATION_DESCRIPTION="NotificationDescription"
GROWL_NOTIFICATION_ICON="NotificationIcon"
GROWL_NOTIFICATION_APP_ICON="NotificationAppIcon"
GROWL_NOTIFICATION_PRIORITY="NotificationPriority"
		
GROWL_NOTIFICATION_STICKY="NotificationSticky"

GROWL_APP_REGISTRATION="GrowlApplicationRegistrationNotification"
GROWL_APP_REGISTRATION_CONF="GrowlApplicationRegistrationConfirmationNotification"
GROWL_NOTIFICATION="GrowlNotification"
GROWL_SHUTDOWN="GrowlShutdown"
GROWL_PING="Honey, Mind Taking Out The Trash"
GROWL_PONG="What Do You Want From Me, Woman"
GROWL_IS_READY="Lend Me Some Sugar; I Am Your Neighbor!"

	
growlPriority = {"Very Low":-2,"Moderate":-1,"Normal":0,"High":1,"Emergency":2}

class netgrowl:
	"""Builds a Growl Network Registration packet.
	   Defaults to emulating the command-line growlnotify utility."""

	__notAllowed__ = [GROWL_APP_ICON, GROWL_NOTIFICATION_ICON, GROWL_NOTIFICATION_APP_ICON]

	def __init__(self, hostname, password ):
		self.hostname = hostname
		self.password = password
		self.socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

	def send(self, data):
		self.socket.sendto(data, (self.hostname, GROWL_UDP_PORT))
		
	def PostNotification(self, userInfo):
		if userInfo.has_key(GROWL_NOTIFICATION_PRIORITY):
			priority = userInfo[GROWL_NOTIFICATION_PRIORITY]
		else:
			priority = 0
		if userInfo.has_key(GROWL_NOTIFICATION_STICKY):
			sticky = userInfo[GROWL_NOTIFICATION_STICKY]
		else:
			sticky = False
		data = self.encodeNotify(userInfo[GROWL_APP_NAME],
								 userInfo[GROWL_NOTIFICATION_NAME],
								 userInfo[GROWL_NOTIFICATION_TITLE],
								 userInfo[GROWL_NOTIFICATION_DESCRIPTION],
								 priority,
								 sticky)
		return self.send(data)

	def PostRegistration(self, userInfo):
		data = self.encodeRegistration(userInfo[GROWL_APP_NAME],
									   userInfo[GROWL_NOTIFICATIONS_ALL],
									   userInfo[GROWL_NOTIFICATIONS_DEFAULT])
		return self.send(data)

	def encodeRegistration(self, application, notifications, defaultNotifications):
		data = struct.pack("!BBH",
						   GROWL_PROTOCOL_VERSION,
						   GROWL_TYPE_REGISTRATION,
						   len(application) )
		data += struct.pack("BB",
							len(notifications),
							len(defaultNotifications) )
		data += application
		for i in notifications:
			encoded = i.encode("utf-8")
			data += struct.pack("!H", len(encoded))
			data += encoded
		for i in defaultNotifications:
			data += struct.pack("B", i)
		return self.encodePassword(data)

	def encodeNotify(self, application, notification, title, description,
					 priority = 0, sticky = False):

		application  = application.encode("utf-8")
		notification = notification.encode("utf-8")
		title		= title.encode("utf-8")
		description  = description.encode("utf-8")
		flags = (priority & 0x07) * 2
		if priority < 0: 
			flags |= 0x08
		if sticky: 
			flags = flags | 0x0001
		data = struct.pack("!BBHHHHH",
						   GROWL_PROTOCOL_VERSION,
						   GROWL_TYPE_NOTIFICATION,
						   flags,
						   len(notification),
						   len(title),
						   len(description),
						   len(application) )
		data += notification
		data += title
		data += description
		data += application
		return self.encodePassword(data)

	def encodePassword(self, data):
		checksum = md5.new()
		checksum.update(data)
		if self.password:
		   checksum.update(self.password)
		data += checksum.digest()
		return data

class _ImageHook(type):
	def __getattribute__(self, attr):
		global Image
		if Image is self:
			from _growlImage import Image

		return getattr(Image, attr)

class Image(object):
	__metaclass__ = _ImageHook

class _RawImage(object):
	def __init__(self, data):  self.rawImageData = data

class GrowlNotifier(object):
	"""
	A class that abstracts the process of registering and posting
	notifications to the Growl daemon.

	You can either pass `applicationName', `notifications',
	`defaultNotifications' and `applicationIcon' to the constructor
	or you may define them as class-level variables in a sub-class.

	`defaultNotifications' is optional, and defaults to the value of
	`notifications'.  `applicationIcon' is also optional but defaults
	to a pointless icon so is better to be specified.
	"""

	applicationName = 'GrowlNotifier'
	notifications = []
	defaultNotifications = []
	applicationIcon = None
	_notifyMethod = _growl

	def __init__(self, applicationName=None, notifications=None, defaultNotifications=None, applicationIcon=None, hostname=None, password=None):
		if applicationName:
			self.applicationName = applicationName
		assert(self.applicationName, 'An application name is required.')

		if notifications:
			self.notifications = list(notifications)
		assert(self.notifications, 'A sequence of one or more notification names is required.')

		if defaultNotifications is not None:
			self.defaultNotifications = list(defaultNotifications)
		elif not self.defaultNotifications:
			self.defaultNotifications = list(self.notifications)

		if applicationIcon is not None:
			self.applicationIcon = self._checkIcon(applicationIcon)
		elif self.applicationIcon is not None:
			self.applicationIcon = self._checkIcon(self.applicationIcon)

		if hostname is not None and password is not None:
			self._notifyMethod = netgrowl(hostname, password)
		elif hostname is not None or password is not None:
			raise KeyError, "Hostname and Password are both required for a network notification"

	def _checkIcon(self, data):
		if isinstance(data, str):
			return _RawImage(data)
		else:
			return data

	def register(self):
		if self.applicationIcon is not None:
			self.applicationIcon = self._checkIcon(self.applicationIcon)

		regInfo = {GROWL_APP_NAME: self.applicationName,
				   GROWL_NOTIFICATIONS_ALL: self.notifications,
				   GROWL_NOTIFICATIONS_DEFAULT: self.defaultNotifications,
				   GROWL_APP_ICON:self.applicationIcon,
				  }
		self._notifyMethod.PostRegistration(regInfo)

	def notify(self, noteType, title, description, icon=None, sticky=False, priority=None):
		assert noteType in self.notifications
		notifyInfo = {GROWL_NOTIFICATION_NAME: noteType,
					  GROWL_APP_NAME: self.applicationName,
					  GROWL_NOTIFICATION_TITLE: title,
					  GROWL_NOTIFICATION_DESCRIPTION: description,
					 }
		if sticky:
			notifyInfo[GROWL_NOTIFICATION_STICKY] = 1

		if priority is not None:
			notifyInfo[GROWL_NOTIFICATION_PRIORITY] = priority

		if icon:
			notifyInfo[GROWL_NOTIFICATION_ICON] = self._checkIcon(icon)

		self._notifyMethod.PostNotification(notifyInfo)
