#include "batman.h"

#define BUS   "org.freedesktop.UPower"
#define PATH  "/org/freedesktop/UPower"
#define IFACE "org.freedesktop.UPower"
# define IFACE_DEVICE "org.freedesktop.UPower.Device"
# define IFACE_PROPERTIES "org.freedesktop.DBus.Properties"

EINTERN extern Eina_List *batman_device_batteries;
EINTERN extern Eina_List *batman_device_ac_adapters;
EINTERN extern double batman_init_time;

static Eldbus_Connection *conn;
static Eldbus_Proxy *upower_proxy;
static Eldbus_Proxy *upower_proxy_bat;
static Eldbus_Proxy *upower_proxy_ac;

typedef struct _Upower_Data Upower_Data;
struct _Upower_Data
{
   Eldbus_Proxy *proxy;
   Instance     *inst;
};

static void
_battery_free(Battery *bat)
{
   Eldbus_Object *obj = eldbus_proxy_object_get(bat->proxy);
   eldbus_proxy_unref(bat->proxy);
   eldbus_object_unref(obj);

   batman_device_batteries = eina_list_remove(batman_device_batteries, bat);
   eina_stringshare_del(bat->udi);
   if (bat->model)
     eina_stringshare_del(bat->model);
   if (bat->vendor)
     eina_stringshare_del(bat->vendor);
   E_FREE(bat);
}

static void
_ac_free(Ac_Adapter *ac)
{
   Eldbus_Object *obj = eldbus_proxy_object_get(ac->proxy);
   eldbus_proxy_unref(ac->proxy);
   eldbus_object_unref(obj);

   batman_device_ac_adapters = eina_list_remove(batman_device_ac_adapters, ac);
   eina_stringshare_del(ac->udi);
   E_FREE(ac);
}

static void
_ac_get_all_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending EINA_UNUSED)
{
   Ac_Adapter *ac = data;
   Eldbus_Message_Iter *array, *dict, *variant;

   if (!eldbus_message_arguments_get(msg, "a{sv}", &array))
     return;

   while (eldbus_message_iter_get_and_next(array, 'e', &dict))
     {
        const char *key;

        if (!eldbus_message_iter_arguments_get(dict, "sv", &key, &variant))
          continue;

        if (!strcmp(key, "Online"))
          {
             Eina_Bool b;
             eldbus_message_iter_arguments_get(variant, "b", &b);
             ac->present = b;
             break;
          }
     }
   _batman_device_update(ac->inst);
}

static void
_ac_changed_cb(void *data, const Eldbus_Message *msg EINA_UNUSED)
{
   Ac_Adapter *ac = data;
   eldbus_proxy_property_get_all(ac->proxy, _ac_get_all_cb, ac);
}

static void
_process_ac(Eldbus_Proxy *proxy, Instance *inst)
{
   Ac_Adapter *ac;
   ac = E_NEW(Ac_Adapter, 1);
   if (!ac) goto error;
   ac->inst = inst;
   ac->proxy = proxy;
   ac->udi = eina_stringshare_add(eldbus_object_path_get(eldbus_proxy_object_get(proxy)));
   eldbus_proxy_property_get_all(proxy, _ac_get_all_cb, ac);
   eldbus_proxy_signal_handler_add(upower_proxy_ac, "PropertiesChanged", _ac_changed_cb, ac);
   batman_device_ac_adapters = eina_list_append(batman_device_ac_adapters, ac);
   return;

error:
   eldbus_object_unref(eldbus_proxy_object_get(proxy));
   eldbus_proxy_unref(proxy);
   return;
}

static const char *bat_techologies[] = {
   "Unknown",
   "Lithium ion",
   "Lithium polymer",
   "Lithium iron phosphate",
   "Lead acid",
   "Nickel cadmium",
   "Nickel metal hydride"
};

static void
_bat_get_all_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending EINA_UNUSED)
{
   Battery *bat = data;
   Eldbus_Message_Iter *array, *dict, *variant;

   bat->got_prop = EINA_TRUE;
   if (!eldbus_message_arguments_get(msg, "a{sv}", &array))
     return;
   while (eldbus_message_iter_get_and_next(array, 'e', &dict))
     {
        const char *key;
        union
        {
           Eina_Bool   b;
           int64_t     i64;
           unsigned    u;
           double      d;
           const char *s;
        } val;

        if (!eldbus_message_iter_arguments_get(dict, "sv", &key, &variant))
          continue;
        if (!strcmp(key, "IsPresent"))
          {
             eldbus_message_iter_arguments_get(variant, "b", &val.b);
             bat->present = val.b;
          }
        else if (!strcmp(key, "TimeToEmpty"))
          {
             eldbus_message_iter_arguments_get(variant, "x", &val.i64);
             bat->time_left = (int)val.i64;
             if (bat->time_left > 0)
               bat->charging = EINA_FALSE;
             else
               bat->charging = EINA_TRUE;
          }
        else if (!strcmp(key, "Percentage"))
          {
             eldbus_message_iter_arguments_get(variant, "d", &val.d);
             bat->percent = (int)val.d;
          }
        else if (!strcmp(key, "Energy"))
          {
             eldbus_message_iter_arguments_get(variant, "d", &val.d);
             bat->current_charge = (int)val.d;
          }
        else if (!strcmp(key, "EnergyFullDesign"))
          {
             eldbus_message_iter_arguments_get(variant, "d", &val.d);
             bat->design_charge = (int)val.d;
          }
        else if (!strcmp(key, "EnergyFull"))
          {
             eldbus_message_iter_arguments_get(variant, "d", &val.d);
             bat->last_full_charge = (int)val.d;
          }
        else if (!strcmp(key, "TimeToFull"))
          {
             eldbus_message_iter_arguments_get(variant, "x", &val.i64);
             bat->time_full = (int)val.i64;
          }
        else if (!strcmp(key, "Technology"))
          {
             eldbus_message_iter_arguments_get(variant, "u", &val.u);
             if (val.u > EINA_C_ARRAY_LENGTH(bat_techologies))
               val.u = 0;
             bat->technology = bat_techologies[val.u];
          }
        else if (!strcmp(key, "Model"))
          {
             if (!eldbus_message_iter_arguments_get(variant, "s", &val.s))
               continue;
             eina_stringshare_replace(&bat->model, val.s);
          }
        else if (!strcmp(key, "Vendor"))
          {
             if (!eldbus_message_iter_arguments_get(variant, "s", &val.s))
               continue;
             if (bat->vendor)
               eina_stringshare_del(bat->vendor);
             bat->vendor = eina_stringshare_add(val.s);
          }
     }
   _batman_device_update(bat->inst);
}

static void
_bat_changed_cb(void *data, const Eldbus_Message *msg EINA_UNUSED)
{
   Battery *bat = data;
   eldbus_proxy_property_get_all(bat->proxy, _bat_get_all_cb, bat);
}

static void
_process_battery(Eldbus_Proxy *proxy, Instance *inst)
{
   Battery *bat;

   bat = E_NEW(Battery, 1);
   if (!bat)
     {
        eldbus_object_unref(eldbus_proxy_object_get(proxy));
        eldbus_proxy_unref(proxy);
        return;
     }
   bat->inst = inst;
   bat->proxy = proxy;
   bat->udi = eina_stringshare_add(eldbus_object_path_get(eldbus_proxy_object_get(proxy)));
   eldbus_proxy_property_get_all(proxy, _bat_get_all_cb, bat);
   eldbus_proxy_signal_handler_add(upower_proxy_bat, "PropertiesChanged", _bat_changed_cb, bat);
   batman_device_batteries = eina_list_append(batman_device_batteries, bat);
   _batman_device_update(bat->inst);
}

static void
_device_type_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending)
{
   Upower_Data *ud = data;
   Eldbus_Message_Iter *variant;
   Eldbus_Object *obj;
   unsigned int type;

   const char *path = eldbus_pending_path_get (pending);
   if (!eldbus_message_arguments_get(msg, "v", &variant))
     goto error;

   eldbus_message_iter_arguments_get(variant, "u", &type);
   if (type == 1)
     {
        obj = eldbus_object_get(conn, BUS, path);
        EINA_SAFETY_ON_FALSE_RETURN(obj);
        upower_proxy_ac = eldbus_proxy_get(obj, IFACE_PROPERTIES);
        _process_ac(ud->proxy, ud->inst);
     }
   else if (type == 2)
     {
        obj = eldbus_object_get(conn, BUS, path);
        EINA_SAFETY_ON_FALSE_RETURN(obj);
        upower_proxy_bat = eldbus_proxy_get(obj, IFACE_PROPERTIES);
        _process_battery(ud->proxy, ud->inst);
     }
   else
     goto error;

   E_FREE(ud);

   return;

error:
   obj = eldbus_proxy_object_get(ud->proxy);
   eldbus_proxy_unref(ud->proxy);
   eldbus_object_unref(obj);
   E_FREE(ud);
   return;
}

static void
_process_enumerate_path(const char *path, Instance *inst)
{
   Eldbus_Object *obj;
   Eldbus_Proxy *proxy;
   Upower_Data *ud;

   obj = eldbus_object_get(conn, BUS, path);
   EINA_SAFETY_ON_FALSE_RETURN(obj);
   proxy = eldbus_proxy_get(obj, IFACE_DEVICE);
   ud = E_NEW(Upower_Data, 1);
   if (!ud)
     {
        eldbus_object_unref(eldbus_proxy_object_get(proxy));
        eldbus_proxy_unref(proxy);
        return;
     }
   ud->proxy = proxy;
   ud->inst = inst;
   eldbus_proxy_property_get(proxy, "Type", _device_type_cb, ud);
}

static void
_enumerate_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending EINA_UNUSED)
{
   Instance *inst = data;
   const char *path;
   Eldbus_Message_Iter *array;

   if (!eldbus_message_arguments_get(msg, "ao", &array))
     return;

   while (eldbus_message_iter_get_and_next(array, 'o', &path))
     _process_enumerate_path(path, inst);
}

static void
_device_added_cb(void *data, const Eldbus_Message *msg)
{
   const char *path;
   Instance *inst = data;

   if (!eldbus_message_arguments_get(msg, "o", &path))
     return;
   _process_enumerate_path(path, inst);
}

static void
_device_removed_cb(void *data, const Eldbus_Message *msg)
{
   Battery *bat;
   Ac_Adapter *ac;
   const char *path;
   Instance *inst = data;
   Eina_List *batteries, *adapters, *l;
   Eina_Bool exists = EINA_FALSE;

   if (!eldbus_message_arguments_get(msg, "o", &path))
     return;
   batteries = _batman_battery_find(path);
   if (eina_list_count(batteries))
     {
        EINA_LIST_FOREACH(batteries, l, bat)
          {
             if (inst == bat->inst)
               {
                  inst = bat->inst;
                  _battery_free(bat);
                  _batman_device_update(inst);
                  exists = EINA_TRUE;
               }
          }
        if (exists)
          {
             eina_list_free(batteries);
             return;
          }
     }
   exists = EINA_FALSE;
   adapters = _batman_ac_adapter_find(path);
   if (eina_list_count(adapters))
     {
        EINA_LIST_FOREACH(adapters, l, ac)
          {
             if (ac->inst == inst)
               {
                  inst = ac->inst;
                  _ac_free(ac);
                  _batman_device_update(inst);
                  exists = EINA_TRUE;
               }
          }
        eina_list_free(adapters);
     }
}

int
_batman_upower_start(Instance *inst)
{
   Eldbus_Object *obj;

   conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SYSTEM);
   EINA_SAFETY_ON_NULL_RETURN_VAL(conn, 0);

   obj = eldbus_object_get(conn, BUS, PATH);
   EINA_SAFETY_ON_NULL_GOTO(obj, obj_error);
   upower_proxy = eldbus_proxy_get(obj, IFACE);
   EINA_SAFETY_ON_NULL_GOTO(upower_proxy, proxy_error);

   eldbus_proxy_signal_handler_add(upower_proxy, "DeviceAdded", _device_added_cb, inst);
   eldbus_proxy_signal_handler_add(upower_proxy, "DeviceRemoved", _device_removed_cb, inst);
   eldbus_proxy_call(upower_proxy, "EnumerateDevices", _enumerate_cb, inst, -1, "");
   return 1;

proxy_error:
   eldbus_object_unref(obj);
obj_error:
   eldbus_connection_unref(conn);
   return 0;
}

void
_batman_upower_stop(void)
{
   Eina_List *list, *list2;
   Battery *bat;
   Ac_Adapter *ac;
   Eldbus_Object *obj;

   EINA_LIST_FOREACH_SAFE(batman_device_batteries, list, list2, bat)
     E_FREE_FUNC(bat, _battery_free);
   EINA_LIST_FOREACH_SAFE(batman_device_ac_adapters, list, list2, ac)
     E_FREE_FUNC(ac, _ac_free);

   obj = eldbus_proxy_object_get(upower_proxy);
   E_FREE_FUNC(upower_proxy, eldbus_proxy_unref);
   E_FREE_FUNC(obj, eldbus_object_unref);
   obj = eldbus_proxy_object_get(upower_proxy_ac);
   E_FREE_FUNC(upower_proxy_ac, eldbus_proxy_unref);
   E_FREE_FUNC(obj, eldbus_object_unref);
   obj = eldbus_proxy_object_get(upower_proxy_bat);
   E_FREE_FUNC(upower_proxy_bat, eldbus_proxy_unref);
   E_FREE_FUNC(obj, eldbus_object_unref);
   E_FREE_FUNC(conn, eldbus_connection_unref);
}

