#!/usr/bin/python

# Copyright (C) 2008 Valmantas Paliksa <walmis at balticum-tv dot lt>
# Copyright (C) 2008 Tadas Dailyda <tadas at dailyda dot com>
#
# Licensed under the GNU General Public License Version 3
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# 

import sys
from subprocess import *
import os.path
import pynotify
import dbus
import dbus.glib
import gobject
import gtk
import gtk.gdk

#support running uninstalled
_dirname = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
if os.path.exists(os.path.join(_dirname,"ChangeLog")):
	sys.path.insert(0, _dirname)

from blueman.Constants import *

from blueman.Functions import *
from blueman.Functions import _

from blueman.main.applet.DbusService import DbusService
from blueman.main.Config import Config

import blueman.plugins.applet
from blueman.plugins.AppletPlugin import AppletPlugin

import blueman.bluez as Bluez

from blueman.main.SignalTracker import SignalTracker

class BluemanApplet(object):

	class Plugins(gobject.GObject):
		__gsignals__ = {
			'plugin-loaded' : (gobject.SIGNAL_NO_HOOKS, gobject.TYPE_NONE, (gobject.TYPE_STRING,)),
			'plugin-unloaded' : (gobject.SIGNAL_NO_HOOKS, gobject.TYPE_NONE, (gobject.TYPE_STRING,)),
		}
		
		def __init__(self, applet):
			gobject.GObject.__init__(self)
			self.__plugins = {}
			self.__classes = {}
			self.__deps = {}
			self.__loaded = []
			self.applet = applet
			self.__config = Config()

			if self.__config.props.applet_plugins == None:
				self.__config.props.applet_plugins = []					

			self.__config.connect('property-changed', self.on_property_changed)
		
		
		def on_property_changed(self, config, key, value):
			if key == "applet_plugins":
				if type(value) == list:
					for item in value:
						disable = item[0] == "!"
						if disable:
							item = item[1:]
						try:
							cls = self.__classes[item]
							if not cls.__unloadable__ and disable:
								print YELLOW("warning:"), item, "is not unloadable"
							elif item in self.__loaded and disable:
								self.Unload(item)
							elif item not in self.__loaded and not disable:
								self.Load(item)

						except KeyError:
							print YELLOW("warning:"), "Plugin %s not found" % item
							continue
							
		def GetClasses(self):
			return self.__classes
			
		def GetLoaded(self):
			return self.__loaded
			
		def GetDependencies(self):
			return self.__deps
			
		def Load(self, name=None):
			if name:
				self.__load_plugin(self.__classes[name])
				return 
				
			path = os.path.dirname(blueman.plugins.applet.__file__)
			plugins = []
			for root, dirs, files in os.walk(path):
				for f in files:
					if f.endswith(".py") and not (f.endswith(".pyc") or f.endswith("_.py")):
						plugins.append(f[0:-3])

			dprint(plugins)
			for plugin in plugins:
				try:
					__import__("blueman.plugins.applet.%s" % plugin, None, None, [])
				except ImportError, e:
					dprint("Unable to load %s plugin\n%s" % (plugin, e))
			
			
			for cls in AppletPlugin.__subclasses__():
				self.__classes[cls.__name__] = cls
				if not cls.__name__ in self.__deps:
					self.__deps[cls.__name__] = []
				
				for c in cls.__depends__:
					if not c in self.__deps:
						self.__deps[c] = []
					self.__deps[c].append(cls.__name__)
			
				
			c = self.__config.props.applet_plugins
			for name, cls in self.__classes.iteritems():
				for dep in self.__deps[name]:
					#plugins that are required by not unloadable plugins are not unloadable too
					if not self.__classes[dep].__unloadable__:
						cls.__unloadable__ = False				
				
				if (cls.__autoload__ or cls.__name__ in c) and not (cls.__unloadable__ and "!"+cls.__name__ in c):
					self.__load_plugin(cls)

		def __load_plugin(self, cls):
			if cls.__name__ in self.__loaded:
				return
				
			for dep in cls.__depends__:
				if not dep in self.__loaded:
					if not dep in self.__classes:
						raise "Could not satisfy dependency %s -> %s" % (cls.__name__, dep)
					self.__load_plugin(self.__classes[dep])		
			
			dprint("loading", cls)
			inst = cls(self.applet)
			try:
				inst._load(self.applet)
			except:
				dprint("Failed to load", cls.__name__)
			else:
				self.__plugins[cls.__name__] = inst
			
				self.__loaded.append(cls.__name__)
				self.emit("plugin-loaded", cls.__name__)
			
		def __getattr__(self, key):
			try:
				return self.__plugins[key]
			except:
				return self.__dict__[key]

		def Unload(self, name):
			if self.__classes[name].__unloadable__:
				for d in self.__deps[name]:
			 		self.Unload(d)
			 		
				if name in self.__loaded:
				 	dprint("Unloading %s" % name)
				 	try:
				 		inst = self.__plugins[name]
				 		inst._unload()
				 	except NotImplementedError:
				 		print "Plugin cannot be unloaded"
				 	else:
				 		self.__loaded.remove(name)
				 		del self.__plugins[name]
				 		self.emit("plugin-unloaded", name)
				 	
			else:
				raise Exception("Plugin %s is not unloadable" % name)
		
		def SetConfig(self, plugin, state):
			plugins = self.__config.props.applet_plugins 
			if plugin in plugins:
				plugins.remove(plugin)
			elif "!"+plugin in plugins:
				plugins.remove("!"+plugin)
				
			plugins.append("!"+plugin if not state else plugin)
			self.__config.props.applet_plugins = plugins
			
		def get_plugins(self):
			return self.__plugins
						
		#executes a function on all plugin instances
		def Run(self, function, *args, **kwargs):
			rets = []
			for inst in AppletPlugin.instances:
				ret = getattr(inst, function)(*args, **kwargs)
				rets.append(ret)
			
			return rets
					
	def __init__(self):
		setup_icon_path()
		if not pynotify.init("Blueman"):
			dprint("Error: Failed to init pynotify")
		
		check_single_instance("blueman-applet")


		self.Manager = None
		self.DbusSvc = DbusService(self)
		self.Signals = SignalTracker()
		
		self.Plugins = BluemanApplet.Plugins(self)
		self.Plugins.Load()
		

			
		self.Plugins.Run("on_plugins_loaded")
	

		self.bus = dbus.SystemBus()
		self.bus.watch_name_owner("org.bluez", self.on_dbus_name_owner_change)
		
		self.bus.add_signal_receiver(self.on_adapter_property_changed, "PropertyChanged", "org.bluez.Adapter", "org.bluez", path_keyword="path")


		gtk.main()
		
	
	def manager_init(self):
		try:

			self.Signals.DisconnectAll()
			self.Manager = Bluez.Manager("gobject")
			self.Plugins.Run("on_manager_state_changed", True)

			self.Signals.Handle("bluez", self.Manager, self.on_adapter_removed, "AdapterRemoved")
			self.Signals.Handle("bluez", self.Manager, self.on_adapter_added, "AdapterAdded")
		
		except dbus.exceptions.DBusException, e:
			dprint(e)
			self.manager_deinit()
			dprint("Bluez DBus API not available. Listening for DBus name ownership changes")
	
	def manager_deinit(self):
		self.Signals.DisconnectAll()
		self.Manager = None

		self.Plugins.Run("on_manager_state_changed", False)
	

		
	def on_dbus_name_owner_change(self, owner):
		dprint("org.bluez owner changed to", owner)
		if owner == "":
			self.manager_deinit()
		elif self.Manager == None:
			self.manager_init()
		
	def on_adapter_property_changed(self, key, value, path):
		self.Plugins.Run("on_adapter_property_changed", path, key, value)
	
	def on_adapter_added(self, path):
		dprint("Adapter added ", path)
		def on_activate():
			dprint("Adapter activated")
			self.Plugins.Run("on_adapter_added", path)

		adapter = Bluez.Adapter(path)
		wait_for_adapter(adapter, on_activate)
		
	def on_adapter_removed(self, path):
		dprint("Adapter removed ", path)
		self.Plugins.Run("on_adapter_removed", path)

BluemanApplet()

