#include "bz.h"
#include "e_mod_main.h"

static Eldbus_Object *objman_obj = NULL;
static Eldbus_Proxy *objman_proxy = NULL;
static Eldbus_Signal_Handler *sig_ifadd = NULL;
static Eldbus_Signal_Handler *sig_ifdel = NULL;
static Eldbus_Pending *pend_getobj = NULL;
static Eina_Hash *obj_table = NULL;
static void (*fn_obj_add) (Obj *o) = NULL;

/*
static void
cb_obj_prop_mandata(void *data, const void *key, Eldbus_Message_Iter *var)
{
   Obj *o = data;
   unsigned short *skey = key;

   printf("    M KEY %x\n", (int)*skey);
}
*/

static void
cb_obj_prop_entry(void *data, const void *key, Eldbus_Message_Iter *var)
{
   Obj *o = data;
   const char *skey = key;

   if (!strcmp(skey, "Paired"))
     {
        Eina_Bool val = EINA_FALSE;
        if (eldbus_message_iter_arguments_get(var, "b", &val))
          o->paired = val;
     }
   if (!strcmp(skey, "Connected"))
     {
        Eina_Bool val = EINA_FALSE;
        if (eldbus_message_iter_arguments_get(var, "b", &val))
          o->connected = val;
     }
   if (!strcmp(skey, "Trusted"))
     {
        Eina_Bool val = EINA_FALSE;
        if (eldbus_message_iter_arguments_get(var, "b", &val))
          o->trusted = val;
     }
   if (!strcmp(skey, "Blocked"))
     {
        Eina_Bool val = EINA_FALSE;
        if (eldbus_message_iter_arguments_get(var, "b", &val))
          o->blocked = val;
     }
   if (!strcmp(skey, "LegacyPairing"))
     {
        Eina_Bool val = EINA_FALSE;
        if (eldbus_message_iter_arguments_get(var, "b", &val))
          o->legacy_pairing = val;
     }
   if (!strcmp(skey, "ServicesResolved"))
     {
        Eina_Bool val = EINA_FALSE;
        if (eldbus_message_iter_arguments_get(var, "b", &val))
          o->services_resolved = val;
     }
   if (!strcmp(skey, "Address"))
     {
        const char *val = NULL;
        if (eldbus_message_iter_arguments_get(var, "s", &val))
          o->address = eina_stringshare_add(val);
     }
   if (!strcmp(skey, "AddressType"))
     {
        const char *val = NULL;
        if (eldbus_message_iter_arguments_get(var, "s", &val))
          o->address_type = eina_stringshare_add(val);
     }
   if (!strcmp(skey, "Name"))
     {
        const char *val = NULL;
        if (eldbus_message_iter_arguments_get(var, "s", &val))
          o->name = eina_stringshare_add(val);
     }
   if (!strcmp(skey, "Icon"))
     {
        const char *val = NULL;
        if (eldbus_message_iter_arguments_get(var, "s", &val))
          o->icon = eina_stringshare_add(val);
     }
   if (!strcmp(skey, "Alias"))
     {
        const char *val = NULL;
        if (eldbus_message_iter_arguments_get(var, "s", &val))
          o->alias = eina_stringshare_add(val);
     }
   if (!strcmp(skey, "Modalias"))
     {
        const char *val = NULL;
        if (eldbus_message_iter_arguments_get(var, "s", &val))
          o->modalias = eina_stringshare_add(val);
     }
   if (!strcmp(skey, "Adapter"))
     {
        const char *val = NULL;
        if (eldbus_message_iter_arguments_get(var, "o", &val))
          o->adapter = eina_stringshare_add(val);
     }
   if (!strcmp(skey, "Class"))
     {
        unsigned int val = 0;
        if (eldbus_message_iter_arguments_get(var, "u", &val))
          o->klass = val;
     }
   if (!strcmp(skey, "Appearance"))
     {
        unsigned short val = 0;
        if (eldbus_message_iter_arguments_get(var, "q", &val))
          o->appearance = val;
     }
   if (!strcmp(skey, "RSSI"))
     {
        short val = 0;
        if (eldbus_message_iter_arguments_get(var, "n", &val))
          o->rssi = val;
     }
   if (!strcmp(skey, "TxPower"))
     {
        unsigned short val = 0;
        if (eldbus_message_iter_arguments_get(var, "n", &val))
          o->txpower = val;
     }
   if (!strcmp(skey, "UUIDs"))
     {
        Eldbus_Message_Iter *array = NULL;

        if (eldbus_message_iter_arguments_get(var, "as", &array))
          {
             const char *val = NULL;

             while (eldbus_message_iter_get_and_next(array, 's', &val))
               {
                  if (!o->uuids) o->uuids = eina_array_new(1);
                  eina_array_push(o->uuids, eina_stringshare_add(val));
               }
          }
     }
   if (!strcmp(skey, "Discoverable"))
     {
        Eina_Bool val = EINA_FALSE;
        if (eldbus_message_iter_arguments_get(var, "b", &val))
          o->discoverable = val;
     }
   if (!strcmp(skey, "Discovering"))
     {
        Eina_Bool val = EINA_FALSE;
        if (eldbus_message_iter_arguments_get(var, "b", &val))
          o->discovering = val;
     }
   if (!strcmp(skey, "Pairable"))
     {
        Eina_Bool val = EINA_FALSE;
        if (eldbus_message_iter_arguments_get(var, "b", &val))
          o->pairable = val;
     }
   if (!strcmp(skey, "Powered"))
     {
        Eina_Bool val = EINA_FALSE;
        if (eldbus_message_iter_arguments_get(var, "b", &val))
          o->powered = val;
     }
   if (!strcmp(skey, "DiscoverableTimeout"))
     {
        unsigned int val = 0;
        if (eldbus_message_iter_arguments_get(var, "u", &val))
          o->discoverable_timeout = val;
     }
   if (!strcmp(skey, "PairableTimeout"))
     {
        unsigned int val = 0;
        if (eldbus_message_iter_arguments_get(var, "u", &val))
          o->pairable_timeout = val;
     }
   // dict ManufacturerData [readonly, optional]
   //  Manufacturer specific advertisement data. Keys are
   //  16 bits Manufacturer ID followed by its byte array
   //  value.
/*
   if (!strcmp(skey, "ManufacturerData"))
     {
        Eldbus_Message_Iter *array = NULL;

        if (eldbus_message_iter_arguments_get(var, "a{qv}", &array))
          eldbus_message_iter_dict_iterate(array, "qv", cb_obj_prop_mandata, o);
     }
 */
   // dict ServiceData [readonly, optional]
   //  Service advertisement data. Keys are the UUIDs in
   //  string format followed by its byte array value.
   //
   // array{byte} AdvertisingFlags [readonly, experimental]
   //  The Advertising Data Flags of the remote device.
   //
   // dict AdvertisingData [readonly, experimental]
   //  The Advertising Data of the remote device. Keys are
   //  are 8 bits AD Type followed by data as byte array.
}

static void
_obj_clear(Obj *o)
{
   o->paired = EINA_FALSE;
   o->connected = EINA_FALSE;
   o->trusted = EINA_FALSE;
   o->blocked = EINA_FALSE;
   o->legacy_pairing = EINA_FALSE;
   o->services_resolved = EINA_FALSE;
   eina_stringshare_del(o->address);
   o->address = NULL;
   eina_stringshare_del(o->address_type);
   o->address_type = NULL;
   eina_stringshare_del(o->name);
   o->name = NULL;
   eina_stringshare_del(o->icon);
   o->icon = NULL;
   eina_stringshare_del(o->alias);
   o->alias = NULL;
   eina_stringshare_del(o->adapter);
   o->adapter = NULL;
   eina_stringshare_del(o->modalias);
   o->modalias = NULL;
   eina_stringshare_del(o->modalias);
   o->modalias = NULL;
   o->klass = 0;
   o->appearance = 0;
   o->txpower = 0;
   o->rssi = 0;
   if (o->uuids)
     {
        const char *val;

        while ((val = eina_array_pop(o->uuids)))
          eina_stringshare_del(val);
        eina_array_free(o->uuids);
        o->uuids = NULL;
     }
}

#define ERR_PRINT(str) \
   do { const char *name, *text; \
      if (eldbus_message_error_get(msg, &name, &text)) { \
         printf("Error: %s.\n %s:\n %s\n", str, name, text); \
         return; \
      } \
   } while(0)


static void
cb_obj_prop(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending EINA_UNUSED)
{
   Obj *o = data;
   Eldbus_Message_Iter *array;

   if (eldbus_message_error_get(msg, NULL, NULL)) return;
   _obj_clear(o);
   if (eldbus_message_arguments_get(msg, "a{sv}", &array))
     eldbus_message_iter_dict_iterate(array, "sv", cb_obj_prop_entry, o);
   bz_obj_ref(o);
   if ((o->powered) && (o->path))
     {
        const char *s = strrchr(o->path, '/');

        if (s) ebluez5_rfkill_unblock(s + 1);
     }
   if (!o->add_called)
     {
        o->add_called = EINA_TRUE;
        if (fn_obj_add) fn_obj_add(o);
     }
   if (o->fn_change) o->fn_change(o);
   bz_obj_unref(o);
}

static void
cb_obj_prop_changed(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED)
{
   Obj *o = data;
   if (!o->proxy) return;
   eldbus_proxy_property_get_all(o->proxy, cb_obj_prop, o);
}

//static void
//cb_obj_discovery_filter(void *data EINA_UNUSED, const Eldbus_Message *msg, Eldbus_Pending *pending EINA_UNUSED)
//{
//   ERR_PRINT("Discovery Filter Clear");
//}

Obj *
bz_obj_add(const char *path)
{
   Obj *o = calloc(1, sizeof(Obj));
   o->ref = 1;
   o->path = eina_stringshare_add(path);
   o->obj = eldbus_object_get(bz_conn, "org.bluez", o->path);
   o->type = BZ_OBJ_UNKNOWN;
   o->in_table = EINA_TRUE;
   eina_hash_add(obj_table, o->path, o);
   if (!strcmp(o->path, "/org/bluez"))
     {
        o->proxy = eldbus_proxy_get(o->obj, "org.bluez.AgentManager1");
        o->type = BZ_OBJ_BLUEZ;
        o->add_called = EINA_TRUE;
        bz_obj_ref(o);
        if (fn_obj_add) fn_obj_add(o);
        bz_obj_unref(o);
        goto done;
     }
   // all devices are /org/bluez/XXX/dev_XXX so look for /dev_
   else if (strstr(o->path, "/dev_"))
     {
        o->proxy = eldbus_proxy_get(o->obj, "org.bluez.Device1");
        o->type = BZ_OBJ_DEVICE;
        if (o->proxy)
          {
             eldbus_proxy_property_get_all(o->proxy, cb_obj_prop, o);
             o->prop_proxy = eldbus_proxy_get(o->obj,
                                              "org.freedesktop.DBus.Properties");
             if (o->prop_proxy)
               o->prop_sig = eldbus_proxy_signal_handler_add(o->prop_proxy,
                                                             "PropertiesChanged",
                                                             cb_obj_prop_changed, o);
          }
        goto done;
     }
   // all dadapters begin with /org/bluez/
   else if (!strncmp(o->path, "/org/bluez/", 11))
     {
        o->proxy = eldbus_proxy_get(o->obj, "org.bluez.Adapter1");
        o->type = BZ_OBJ_ADAPTER;
        if (o->proxy)
          {
             eldbus_proxy_property_get_all(o->proxy, cb_obj_prop, o);
             o->prop_proxy = eldbus_proxy_get(o->obj,
                                              "org.freedesktop.DBus.Properties");
             if (o->prop_proxy)
               o->prop_sig = eldbus_proxy_signal_handler_add(o->prop_proxy,
                                                             "PropertiesChanged",
                                                             cb_obj_prop_changed, o);
             // disable the filter for discovery later
             // XXX: this doesn't seem to exist on the bluez daemons I see
             // so don't do this to avoid error noise and it's useless it seems
             // eldbus_proxy_call
             //  (o->proxy, "SetDiscoveryFilter", cb_obj_discovery_filter, o, -1, "");
          }
        goto done;
     }
done:
   return o;
}

Obj *
bz_obj_find(const char *path)
{
   return eina_hash_find(obj_table, path);
}

static void
cb_power_on(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED)
{
   ERR_PRINT("Power On");
}

void
bz_obj_power_on(Obj *o)
{
   Eina_Bool val = EINA_TRUE;
   if (!o->proxy) return;
   eldbus_proxy_property_set
     (o->proxy, "Powered", "b", (void *)(uintptr_t)val, cb_power_on, o);
}

static void
cb_power_off(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED)
{
   ERR_PRINT("Power Off");
}

void
bz_obj_power_off(Obj *o)
{
   Eina_Bool val = EINA_FALSE;
   if (!o->proxy) return;
   eldbus_proxy_property_set
     (o->proxy, "Powered", "b", (void *)(uintptr_t)val, cb_power_off, o);
}

static void
cb_discoverable(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED)
{
   ERR_PRINT("Discoverable");
}

void
bz_obj_discoverable(Obj *o)
{
   Eina_Bool val = EINA_TRUE;
   if (!o->proxy) return;
   eldbus_proxy_property_set
     (o->proxy, "Discoverable", "b", (void *)(uintptr_t)val, cb_discoverable, o);
}

static void
cb_undiscoverable(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED)
{
   ERR_PRINT("Undiscoverable");
}

void
bz_obj_undiscoverable(Obj *o)
{
   Eina_Bool val = EINA_FALSE;
   if (!o->proxy) return;
   eldbus_proxy_property_set
     (o->proxy, "Discoverable", "b", (void *)(uintptr_t)val, cb_undiscoverable, o);
}

static void
cb_pairable(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED)
{
   ERR_PRINT("Pairable");
}

void
bz_obj_pairable(Obj *o)
{
   Eina_Bool val = EINA_TRUE;
   if (!o->proxy) return;
   eldbus_proxy_property_set
     (o->proxy, "Pairable", "b", (void *)(uintptr_t)val, cb_pairable, o);
}

static void
cb_unpairable(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED)
{
   ERR_PRINT("Unpairable");
}

void
bz_obj_unpairable(Obj *o)
{
   Eina_Bool val = EINA_FALSE;
   if (!o->proxy) return;
   eldbus_proxy_property_set
     (o->proxy, "Pairable", "b", (void *)(uintptr_t)val, cb_unpairable, o);
}

static void
cb_trust(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED)
{
   ERR_PRINT("Trust");
}

void
bz_obj_trust(Obj *o)
{
   Eina_Bool val = EINA_TRUE;
   if (!o->proxy) return;
   eldbus_proxy_property_set
     (o->proxy, "Trusted", "b", (void *)(uintptr_t)val, cb_trust, o);
}

static void
cb_distrust(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED)
{
   ERR_PRINT("Distrust");
}

void
bz_obj_distrust(Obj *o)
{
   Eina_Bool val = EINA_FALSE;
   if (!o->proxy) return;
   eldbus_proxy_property_set
     (o->proxy, "Trusted", "b", (void *)(uintptr_t)val, cb_distrust, o);
}

static void
cb_pair(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED)
{
   ERR_PRINT("Pair");
}

void
bz_obj_pair(Obj *o)
{
   if (!o->proxy) return;
   eldbus_proxy_call(o->proxy, "Pair", cb_pair, o, -1, "");
}

static void
cb_pair_cancel(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED)
{
   ERR_PRINT("Pair Cancel");
}

void
bz_obj_pair_cancel(Obj *o)
{
   if (!o->proxy) return;
   eldbus_proxy_call
     (o->proxy, "CancelPairing", cb_pair_cancel, o, -1, "");
}

static void
cb_connect(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED)
{
   ERR_PRINT("Connect");
}

void
bz_obj_connect(Obj *o)
{
   if (!o->proxy) return;
   eldbus_proxy_call
     (o->proxy, "Connect", cb_connect, o, -1, "");
}

static void
cb_disconnect(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED)
{
   ERR_PRINT("Disconnect");
}

void
bz_obj_disconnect(Obj *o)
{
   if (!o->proxy) return;
   eldbus_proxy_call
     (o->proxy, "Disconnect", cb_disconnect, o, -1, "");
}

static void
_cb_l2ping(void *data, const char *params)
{
   Obj *o = data;
   char addr[256];
   int timeout = 0;

   if (sscanf(params, "%255s %i", addr, &timeout) == 2)
     {
        if (!strcmp(o->address, addr))
          {
             if (o->ping_busy)
               {
                  e_system_handler_del("l2ping-ping", _cb_l2ping, o);
                  o->ping_busy = EINA_FALSE;
               }
             if (timeout >= 0)
               {
                  if (!o->ping_ok)
                    {
                       printf("@@@PING SUCCEED\n");
                       o->ping_ok = EINA_TRUE;
                       if (o->fn_change) o->fn_change(o);
                    }
               }
             else
               {
                  if (o->ping_ok)
                    {
                       printf("@@@PING FAIL\n");
                       o->ping_ok = EINA_FALSE;
                       if (o->fn_change) o->fn_change(o);
                    }
               }
          }
     }
}

static int
ping_powersave_timeout_get(void)
{
   int timeout = 10;
   E_Powersave_Mode pm = e_powersave_mode_get();

   if      (pm <= E_POWERSAVE_MODE_LOW)     timeout = 5;
   else if (pm <= E_POWERSAVE_MODE_MEDIUM)  timeout = 8;
   else if (pm <= E_POWERSAVE_MODE_HIGH)    timeout = 12;
   else if (pm <= E_POWERSAVE_MODE_EXTREME) timeout = 30;
   return timeout;
}

static void
ping_do(Obj *o)
{
   int timeout = 1000 * ping_powersave_timeout_get();
   if (o->ping_busy)
     e_system_handler_del("l2ping-ping", _cb_l2ping, o);
   o->ping_busy = EINA_TRUE;
   e_system_handler_add("l2ping-ping", _cb_l2ping, o);
   e_system_send("l2ping-ping", "%s %i", o->address, timeout);
   printf("@@@ run new ping %s %i\n", o->address, timeout);
}

static Eina_Bool cb_ping_timer(void *data);

static void
ping_schedule(Obj *o)
{
   double timeout = ping_powersave_timeout_get() + 1.0;

   if (o->ping_timer) ecore_timer_del(o->ping_timer);
   o->ping_timer = ecore_timer_add(timeout, cb_ping_timer, o);
}

static Eina_Bool
cb_ping_timer(void *data)
{
   Obj *o = data;

   printf("@@@ ping timer %s\n", o->address);
   if (o->ping_busy)
     {
        o->ping_busy = EINA_FALSE;
        e_system_handler_del("l2ping-ping", _cb_l2ping, o);
        if (o->ping_ok)
          {
             o->ping_ok = EINA_FALSE;
             if (o->fn_change) o->fn_change(o);
          }
     }
   ping_do(o);
   ping_schedule(o);
   return EINA_TRUE;
}

void
bz_obj_ping_begin(Obj *o)
{
   if (o->ping_timer) return;
   if (o->ping_busy)
     {
        o->ping_busy = EINA_FALSE;
        e_system_handler_del("l2ping-ping", _cb_l2ping, o);
     }
   ping_do(o);
   ping_schedule(o);
 }

void
bz_obj_ping_end(Obj *o)
{
   if (o->ping_timer)
     {
        ecore_timer_del(o->ping_timer);
        o->ping_timer = NULL;
     }
   if (o->ping_busy)
     {
        o->ping_busy = EINA_FALSE;
        e_system_handler_del("l2ping-ping", _cb_l2ping, o);
     }
   if (o->ping_ok)
     {
        printf("@@@PING END %s\n", o->address);
        o->ping_ok = EINA_FALSE;
        if (o->fn_change) o->fn_change(o);
     }
}

static void
cb_remove(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED)
{
   ERR_PRINT("Remove");
}

/*
void
bz_obj_profile_connect(Obj *o, const char *uuid)
{
   if (!o->proxy) return;
   eldbus_proxy_call(o->proxy, "ConnectProfile", NULL, NULL, -1, "s", uuid);
}

void
bz_obj_profile_disconnect(Obj *o, const char *uuid)
{
   if (!o->proxy) return;
   eldbus_proxy_call(o->proxy, "DisconnectProfile", NULL, NULL, -1, "s", uuid);
}
*/

void
bz_obj_remove(Obj *o)
{
   if (o->adapter)
     {
        Obj *adapter = bz_obj_find(o->adapter);
        if (adapter)
          {
             if (!adapter->proxy) return;
             eldbus_proxy_call(adapter->proxy, "RemoveDevice",
                               cb_remove, adapter, -1,
                               "o", o->path);
          }
     }
}

void
bz_obj_ref(Obj *o)
{
   o->ref++;
}

void
bz_obj_unref(Obj *o)
{
   o->ref--;
   if (o->ref > 0) return;
   if (o->in_table)
     {
        o->in_table = EINA_FALSE;
        eina_hash_del(obj_table, o->path, o);
     }
   if (o->ping_busy)
     {
        o->ping_busy = EINA_FALSE;
        e_system_handler_del("l2ping-ping", _cb_l2ping, o);
     }
   _obj_clear(o);
   if (o->prop_sig)
     {
        eldbus_signal_handler_del(o->prop_sig);
        o->prop_sig = NULL;
     }
   if (o->path)
     {
        eina_stringshare_del(o->path);
        o->path = NULL;
     }
   if (o->agent_request)
     {
        eina_stringshare_del(o->agent_request);
        o->agent_request = NULL;
     }
   if (o->agent_msg_err)
     {
        bz_agent_msg_drop(o->agent_msg_err);
        o->agent_msg_err = NULL;
     }
   if (o->agent_msg_ok)
     {
        bz_agent_msg_drop(o->agent_msg_ok);
        o->agent_msg_ok = NULL;
     }
   if (o->ping_timer)
     {
        ecore_timer_del(o->ping_timer);
        o->ping_timer = NULL;
     }
   if (o->proxy)
     {
        eldbus_proxy_unref(o->proxy);
        o->proxy = NULL;
     }
   if (o->prop_proxy)
     {
        eldbus_proxy_unref(o->prop_proxy);
        o->prop_proxy = NULL;
     }
   if (o->obj)
     {
        eldbus_object_unref(o->obj);
        o->obj = NULL;
     }
   free(o);
}

static void
cb_discovery_start(void *data EINA_UNUSED, const Eldbus_Message *msg, Eldbus_Pending *pending EINA_UNUSED)
{
   ERR_PRINT("Discovery Start");
}

void
bz_obj_discover_start(Obj *o)
{
   if (!o->proxy) return;
   eldbus_proxy_call
     (o->proxy, "StartDiscovery", cb_discovery_start, o, -1, "");
}

static void
cb_discovery_stop(void *data EINA_UNUSED, const Eldbus_Message *msg, Eldbus_Pending *pending EINA_UNUSED)
{
   ERR_PRINT("Discovery Stop");
}

void
bz_obj_discover_stop(Obj *o)
{
   if (!o->proxy) return;
   eldbus_proxy_call
     (o->proxy, "StopDiscovery", cb_discovery_stop, o, -1, "");
}

void
bz_obj_agent_request(Obj *o, const char *req, void (*fn) (Eldbus_Message *msg, const char *str), Eldbus_Message *msg_ok, Eldbus_Message *msg_err)
{
   if (o->agent_msg_ok) bz_agent_msg_drop(o->agent_msg_ok);
   if (o->agent_msg_err) bz_agent_msg_reply(o->agent_msg_err);
   o->agent_msg_ok = msg_ok;
   o->agent_msg_err = msg_err;
   o->agent_entry_fn = fn;
   o->agent_alert = EINA_TRUE;
   eina_stringshare_replace(&(o->agent_request), req);
   bz_obj_ref(o);
   if (o->fn_change) o->fn_change(o);
   bz_obj_unref(o);
}

static void
cb_obj_add(void *data EINA_UNUSED, const Eldbus_Message *msg)
{
   const char *path = NULL;

   if (!eldbus_message_arguments_get(msg, "o", &path)) return;
   if (bz_obj_find(path)) return;
   bz_obj_add(path);
}

static void
cb_obj_del(void *data EINA_UNUSED, const Eldbus_Message *msg)
{
   Obj *o;
   const char *path = NULL;

   if (!eldbus_message_arguments_get(msg, "o", &path)) return;
   o = bz_obj_find(path);
   if (o)
     {
        bz_obj_ref(o);
        if (o->fn_del) o->fn_del(o);
        bz_obj_unref(o);
        bz_obj_unref(o);
     }
}

static void
cb_getobj(void *data EINA_UNUSED, const Eldbus_Message *msg,
          Eldbus_Pending *pending EINA_UNUSED)
{
   Eldbus_Message_Iter *prop, *dict;

   pend_getobj = NULL;
   ERR_PRINT("Get Objects");
   if (eldbus_message_arguments_get(msg, "a{oa{sa{sv}}}", &prop))
     {
        while (eldbus_message_iter_get_and_next(prop, 'e', &dict))
          {
             const char *path;

             path = NULL;
             if (!eldbus_message_iter_arguments_get(dict, "o", &path))
               {
                  return;
               }
             bz_obj_add(path);
          }
     }
}

static void
_obj_hash_free(Obj *o)
{
   o->in_table = 0;
   bz_obj_unref(o);
}

void
bz_obj_init(void)
{
   obj_table = eina_hash_string_superfast_new((void *)_obj_hash_free);

   objman_obj = eldbus_object_get(bz_conn, "org.bluez", "/");
   objman_proxy = eldbus_proxy_get(objman_obj, "org.freedesktop.DBus.ObjectManager");
   sig_ifadd = eldbus_proxy_signal_handler_add(objman_proxy, "InterfacesAdded",
                                               cb_obj_add, NULL);
   sig_ifdel = eldbus_proxy_signal_handler_add(objman_proxy, "InterfacesRemoved",
                                               cb_obj_del, NULL);
   pend_getobj = eldbus_proxy_call(objman_proxy, "GetManagedObjects",
                                   cb_getobj, NULL, -1, "");
}

void
bz_obj_shutdown(void)
{
   eina_hash_free(obj_table);
   obj_table = NULL;
   if (pend_getobj)
     {
        eldbus_pending_cancel(pend_getobj);
        pend_getobj = NULL;
     }
   if (sig_ifadd)
     {
        eldbus_signal_handler_del(sig_ifadd);
        sig_ifadd = NULL;
     }
   if (sig_ifdel)
     {
        eldbus_signal_handler_del(sig_ifdel);
        sig_ifdel = NULL;
     }
   if (objman_proxy)
     {
        eldbus_proxy_unref(objman_proxy);
        objman_proxy = NULL;
     }
   if (objman_obj)
     {
        eldbus_object_unref(objman_obj);
        objman_obj = NULL;
     }
}

void
bz_obj_add_func_set(void (*fn) (Obj *o))
{
   fn_obj_add = fn;
}
