# Copyright (C) 2006, Red Hat, Inc.
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

import dbus, dbus.glib, gobject
from sugar import util

class ObjectCache(object):
    def __init__(self):
        self._cache = {}

    def get(self, object_path):
        try:
            return self._cache[object_path]
        except KeyError:
            return None

    def add(self, obj):
        op = obj.object_path()
        if not self._cache.has_key(op):
            self._cache[op] = obj

    def remove(self, object_path):
        try:
            del self._cache[object_path]
        except IndexError:
            pass


DS_DBUS_SERVICE = "org.laptop.sugar.DataStore"
DS_DBUS_INTERFACE = "org.laptop.sugar.DataStore"
DS_DBUS_PATH = "/org/laptop/sugar/DataStore"

class DSObject(gobject.GObject):

    __gsignals__ = {
        'updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
                    ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT]))
    }

    _DS_OBJECT_DBUS_INTERFACE = "org.laptop.sugar.DataStore.Object"

    def __init__(self, bus, new_obj_cb, del_obj_cb, object_path):
        gobject.GObject.__init__(self)
        self._object_path = object_path
        self._ps_new_object = new_obj_cb
        self._ps_del_object = del_obj_cb
        bobj = bus.get_object(DS_DBUS_SERVICE, object_path)
        self._dsobj = dbus.Interface(bobj, self._DS_OBJECT_DBUS_INTERFACE)
        self._dsobj.connect_to_signal('Updated', self._updated_cb)
        self._data = None
        self._data_needs_update = True
        self._properties = None
        self._deleted = False

    def object_path(self):
        return self._object_path

    def uid(self):
        if not self._properties:
            self._properties = self._dsobj.get_properties([])
        return self._properties['uid']

    def _emit_updated_signal(self, data, prop_dict, deleted):
        self.emit('updated', data, prop_dict, deleted)
        return False

    def _update_internal_properties(self, prop_dict):
        did_update = False
        for (key, value) in prop_dict.items():
            if not len(value):
                if self._properties.has_key(ley):
                    did_update = True
                    del self._properties[key]
            else:
                if self._properties.has_key(key):
                    if self._properties[key] != value:
                        did_update = True
                        self._properties[key] = value
                else:
                    did_update = True
                    self._properties[key] = value
        return did_update

    def _updated_cb(self, data=False, prop_dict={}, deleted=False):
        if self._update_internal_properties(prop_dict):
            gobject.idle_add(self._emit_updated_signal, data, prop_dict, deleted)
        self._deleted = deleted

    def get_data(self):
        if self._data_needs_update:
            data = self._dsobj.get_data()
            self._data = ""
            for c in data:
                self._data += chr(c)
        return self._data

    def set_data(self, data):
        old_data = self._data
        self._data = data
        try:
            self._dsobj.set_data(dbus.ByteArray(data))
            del old_data
        except dbus.DBusException, e:
            self._data = old_data
            raise e

    def set_properties(self, prop_dict):
        old_props = self._properties
        self._update_internal_properties(prop_dict)
        try:
            self._dsobj.set_properties(prop_dict)
            del old_props
        except dbus.DBusException, e:
            self._properties = old_props
            raise e

    def get_properties(self, prop_list=[]):
        if not self._properties:
            self._properties = self._dsobj.get_properties(prop_list)
        return self._properties

class DataStore(gobject.GObject):

    _DS_DBUS_OBJECT_PATH = DS_DBUS_PATH + "/Object/"

    def __init__(self):
        gobject.GObject.__init__(self)
        self._objcache = ObjectCache()
        self._bus = dbus.SessionBus()
        self._ds = dbus.Interface(self._bus.get_object(DS_DBUS_SERVICE,
                DS_DBUS_PATH), DS_DBUS_INTERFACE)

    def _new_object(self, object_path):
        obj = self._objcache.get(object_path)
        if obj:
            return obj

        if object_path.startswith(self._DS_DBUS_OBJECT_PATH):
            obj = DSObject(self._bus, self._new_object,
                    self._del_object, object_path)
        else:
            raise RuntimeError("Unknown object type")
        self._objcache.add(obj)
        return obj

    def _del_object(self, object_path):
        # FIXME
        pass

    def get(self, uid=None, activity_id=None):
        if not activity_id and not uid:
            raise ValueError("At least one of activity_id or uid must be specified")
        if activity_id and uid:
            raise ValueError("Only one of activity_id or uid can be specified")
        if activity_id:
            if not util.validate_activity_id(activity_id):
                raise ValueError("activity_id must be valid")
            return self._new_object(self._ds.getActivityObject(activity_id))
        elif uid:
            if not len(uid):
                raise ValueError("uid must be valid")
            return self._new_object(self._ds.get(int(uid)))
        raise RuntimeError("At least one of activity_id or uid must be specified")

    def create(self, data, prop_dict={}, activity_id=None):
        if activity_id and not util.validate_activity_id(activity_id):
            raise ValueError("activity_id must be valid")
        if not activity_id:
            activity_id = ""
        op = self._ds.create(dbus.ByteArray(data), dbus.Dictionary(prop_dict), activity_id)
        return self._new_object(op)

    def delete(self, obj):
        op = obj.object_path()
        obj = self._objcache.get(op)
        if not obj:
            raise RuntimeError("Object not found.")
        self._ds.delete(op)

    def find(self, prop_dict):
        ops = self._ds.find(dbus.Dictionary(prop_dict))
        objs = []
        for op in ops:
            objs.append(self._new_object(op))
        return objs

_ds = None
def get_instance():
    global _ds
    if not _ds:
        _ds = DataStore()
    return _ds