import gobject
import gconf

## Please take note of this important restriction: all keys in the
## gconf dir being monitored must have a corresponding schema default,
## or else this might break


class GConfSync(gobject.GObject):
    __gsignals__ = {
        'changed': (gobject.SIGNAL_RUN_LAST|gobject.SIGNAL_DETAILED,
                    None, # return type
                    (str, )) # parameter types
    }

    def __init__(self, prefix, enums={}):
        gobject.GObject.__init__(self)
        self.prefix = prefix
        gc = gconf.client_get_default()
        keys = dict() # mapping key -> type
        for entry in gc.all_entries(prefix):
            assert entry.key[:len(prefix)] == prefix
            keyname = entry.key[len(prefix):].lstrip('/')
            keys[keyname] = entry.value.type
        self.valid_keys = keys
        self.__key_just_been_set = None
        self.enums_mapping = enums
        reverse_map = dict()
        for key in enums:
            reverse_map[key] = dict(zip(enums[key].values(), enums[key].keys()))
        self.enums_reverse_mapping = reverse_map
        gc.add_dir(prefix, gconf.CLIENT_PRELOAD_ONELEVEL)
        gc.notify_add(prefix, self.__gconfclientnotifyfunc)

    def __gconfclientnotifyfunc(self, unused_gc, unused_cnxn_id, entry, unused_user_data):
        assert entry.key[:len(self.prefix)] == self.prefix
        keyname = entry.key[len(self.prefix):].lstrip('/')
        if keyname and keyname != self.__key_just_been_set:
            self.emit("changed::" + keyname, keyname)
        self.__key_just_been_set = None
        
    def __value_to_python(value):
        if value.type == gconf.VALUE_BOOL:
            if value.get_bool():
                return True
            else:
                return False
        elif value.type == gconf.VALUE_FLOAT:
            return value.get_float()
        elif value.type == gconf.VALUE_INT:
            return value.get_int()
        elif value.type == gconf.VALUE_INVALID:
            raise ValueError, "GConfValue of invalid type"
        elif value.type == gconf.VALUE_LIST:
            return map(GConfSync.__value_to_python, value.get_list())
        elif value.type == gconf.VALUE_PAIR:
            raise NotImplementedError
        elif value.type == gconf.VALUE_SCHEMA:
            raise NotImplementedError
        elif value.type == gconf.VALUE_STRING:
            return value.get_string()
        else:
            return None
    
    __value_to_python = staticmethod(__value_to_python)

    def __gconf_get(self, key):
        prefix = self.prefix
        val = gconf.client_get_default().get("/".join((prefix, key)))
        val = self.__value_to_python(val)
        if key in self.enums_mapping:
            val = self.enums_mapping[key][val]
        return val

    def __gconf_set(self, key, val):
        prefix = self.prefix
        gc = gconf.client_get_default()
        value_type = self.valid_keys[key]
        self.__key_just_been_set = key
        if key in self.enums_reverse_mapping:
            val = self.enums_reverse_mapping[key][val]
        
        key = '/'.join((prefix, key))

        if value_type == gconf.VALUE_BOOL:
            gc.set_bool(key, val)
        elif value_type == gconf.VALUE_FLOAT:
            gc.set_float(key, val)
        elif value_type == gconf.VALUE_INT:
            gc.set_int(key, val)
        elif value_type == gconf.VALUE_INVALID:
            raise ValueError, "GConfValue of invalid type"
        elif value_type == gconf.VALUE_LIST:
            raise NotImplementedError
        elif value_type == gconf.VALUE_PAIR:
            raise NotImplementedError
        elif value_type == gconf.VALUE_SCHEMA:
            raise NotImplementedError
        elif value_type == gconf.VALUE_STRING:
            gc.set_string(key, val)

    def __getitem__(self, key):
        return self.__gconf_get(key)

    def __setitem__(self, key, val):
        self.__gconf_set(key, val)

    def parse_string(self, key, val):
        value_type = self.valid_keys[key]

        if value_type == gconf.VALUE_BOOL:
            if val.lower() in ('true', 'enable', 'on'):
                return True
            elif val.lower() in ('false', 'disable', 'off'):
                return False
            else:
                raise ValueError, "unable to parse '%s' as boolean" % val 
        elif value_type == gconf.VALUE_STRING:
            if key in self.enums_mapping:
                return self.enums_mapping[key][val]
            else:
                return val
        elif value_type == gconf.VALUE_FLOAT:
            return float(val)
        elif value_type == gconf.VALUE_INT:
            return int(val)
        elif value_type == gconf.VALUE_INVALID:
            raise ValueError, "GConfValue of invalid type"
        elif value_type == gconf.VALUE_LIST:
            raise NotImplementedError, "parsing list gconf values not implemented"
        elif value_type == gconf.VALUE_PAIR:
            raise NotImplementedError, "parsing pair gconf values not implemented"
        elif value_type == gconf.VALUE_SCHEMA:
            raise NotImplementedError, "parsing schema gconf values not implemented"
        raise NotImplementedError, "parsing <unknown> gconf values not implemented"

if gobject.pygtk_version < (2,8):
    gobject.type_register(GConfSync)


if __name__ == '__main__':
    import gtk, pango
    prefs = GConfSync("/apps/gnome-osd",
                      dict(osd_halignment=dict(left=pango.ALIGN_LEFT,
                                               right=pango.ALIGN_RIGHT,
                                               center=pango.ALIGN_CENTER)))
    
    def osd_font_changed_cb(obj, key):
        print "'%s' changed" % key
    def osd_halignment_changed_cb(obj, key):
        print "'%s' changed" % key
        print "new value is ", getattr(prefs, key)
        
    prefs.connect("changed::osd_font", osd_font_changed_cb)
    prefs.connect("changed::osd_halignment", osd_halignment_changed_cb)

    print "osd font: ", prefs["osd_font"]
    prefs["osd_font"] = "Foo bar"
    print "osd font (after set): ", prefs['osd_font']

    print "osd_halignment is:", prefs['osd_halignment']
    print "setting osd_halignment = pango.ALIGN_LEFT"
    prefs['osd_halignment'] = pango.ALIGN_LEFT
    print "osd_halignment is:", prefs['osd_halignment']
    
    gtk.main()

