#include "e_system.h"

typedef struct
{
   char *dev;
   int val, max, val_set, val_get_count;
   Eina_Bool prefer : 1;
   Eina_Bool set : 1;
} Light;

static Eina_Lock _devices_lock;
static Eina_List *_devices = NULL;
static Eina_Semaphore _worker_sem;

static void
_light_set(Light *lig, int val)
{ // val == 0->1000
   int nval = ((lig->max * val) + 500) / 1000;
   if (nval < 0) nval = 0;
   else if (nval > lig->max) nval = lig->max;
   lig->val = nval;
#ifdef HAVE_EEZE
   char buf[PATH_MAX];
   snprintf(buf, sizeof(buf), "%s/brightness", lig->dev);
   int fd = open(buf, O_WRONLY);
   if (fd >= 0)
     {
        char buf2[32];
        snprintf(buf2, sizeof(buf2), "%i", lig->val);
        if (write(fd, buf2, strlen(buf2)) <= 0)
          ERR("Write failed of [%s] to [%s]\n", buf2, buf);
        close(fd);
     }
#elif defined(__FreeBSD_kernel__)
   sysctlbyname(lig->dev, NULL, NULL, &(lig->val), sizeof(lig->val));
#endif
}

static void
_light_get(Light *lig)
{
#ifdef HAVE_EEZE
   const char *s;
   s = eeze_udev_syspath_get_sysattr(lig->dev, "brightness");
   if (s)
     {
        lig->val = atoi(s);
        eina_stringshare_del(s);
     }
#elif defined(__FreeBSD_kernel__)
   size_t plen = sizeof(lig->val);
   sysctlbyname(lig->dev, &(lig->val), &plen, NULL, 0);
#endif
}

static void
_cb_worker(void *data EINA_UNUSED, Ecore_Thread *th)
{
   for (;;)
     {
        Eina_List *l;
        Light *lig;

        eina_semaphore_lock(&_worker_sem);

        eina_lock_take(&_devices_lock);
        EINA_LIST_FOREACH(_devices, l, lig)
          {
             if (lig->val_get_count > 0)
               {
                  _light_get(lig);
                  while (lig->val_get_count > 0)
                    {
                       Light *lig2 = calloc(1, sizeof(Light));
                       lig->val_get_count--;
                       if (lig2)
                         {
                            lig2->dev = strdup(lig->dev);
                            lig2->max = lig->max;
                            if (lig2->dev)
                              {
                                 lig2->val = lig->val;
                                 ecore_thread_feedback(th, lig2);
                              }
                            else free(lig2);
                         }
                    }
               }
             if (lig->set)
               {
                  lig->set = EINA_FALSE;
                  lig->val = lig->val_set;
                  _light_set(lig, lig->val);
               }
          }
        eina_lock_release(&_devices_lock);
     }
}

static void
_cb_worker_message(void *data EINA_UNUSED, Ecore_Thread *th EINA_UNUSED, void *msg_data)
{
   Light *lig = msg_data;
   int val = 0;

   if (lig->max > 0)
     {
        val = ((1000 * lig->val) + 500) / lig->max;
        if (val < 0) val = 0;
        else if (val > 1000) val = 1000;
     }
   e_system_inout_command_send("bklight-val", "%s %i", lig->dev, val);
   free(lig->dev);
   free(lig);
}

static void
_cb_worker_end(void *data EINA_UNUSED, Ecore_Thread *th EINA_UNUSED)
{
}

static void
_cb_worker_cancel(void *data EINA_UNUSED, Ecore_Thread *th EINA_UNUSED)
{
}

static void
_light_add(const char *dev)
{
   Light *lig = calloc(1, sizeof(Light));

   if (!lig) abort();
   lig->dev = strdup(dev);
   if (!lig->dev) abort();
   lig->val = -1; // unknown
#ifdef HAVE_EEZE
   const char *s;

   s = eeze_udev_syspath_get_sysattr(lig->dev, "brightness");
   if (!s)
     {
        free(lig->dev);
        free(lig);
        return;
     }
   lig->val = atoi(s);
   eina_stringshare_del(s);

   s = eeze_udev_syspath_get_sysattr(lig->dev, "max_brightness");
   if (s)
     {
        lig->max = atoi(s);
        eina_stringshare_del(s);
     }
   if (lig->max <= 0) lig->max = 255;
   lig->prefer = eeze_udev_syspath_check_sysattr(lig->dev, "type", "firmware");
#elif defined(__FreeBSD_kernel__)
   size_t plen = sizeof(lig->val);
   sysctlbyname(lig->dev, &(lig->val), &plen, NULL, 0);
   lig->max = 100;
#endif
   _devices = eina_list_append(_devices, lig);
   if (alert_backlight_reset)
     {  // set brightness to max if alert mode is on
        eina_lock_take(&_devices_lock);
        lig->val_set = lig->max;
        lig->set = EINA_TRUE;
        eina_semaphore_release(&_worker_sem, 1);
        eina_lock_release(&_devices_lock);
     }
}

#ifdef HAVE_EEZE
static Eina_Bool
_light_device_include(const char *dev)
{ // filter out known undesirable devices
   if (strstr(dev, "::capslock")) return EINA_FALSE;
   if (strstr(dev, "::numlock")) return EINA_FALSE;
   if (strstr(dev, "::scrolllock")) return EINA_FALSE;
   if (strstr(dev, "::compose")) return EINA_FALSE;
   if (strstr(dev, "::kana")) return EINA_FALSE;
   return EINA_TRUE;
}
#endif

static void
_light_refresh_devices()
{
   Light *lig;

   EINA_LIST_FREE(_devices, lig)
     {
        free(lig->dev);
        free(lig);
     }
#ifdef HAVE_EEZE
   Eina_List *devs;
   const char *s;

   devs = eeze_udev_find_by_filter("backlight", NULL, NULL);
   EINA_LIST_FREE(devs, s)
     {
        if (_light_device_include(s)) _light_add(s);
        eina_stringshare_del(s);
     }
   devs = eeze_udev_find_by_filter("leds", NULL, NULL);
   EINA_LIST_FREE(devs, s)
     {
        if (_light_device_include(s)) _light_add(s);
        eina_stringshare_del(s);
     }
#elif defined(__FreeBSD_kernel__)
   // XXX; shouldn't we scan for devices?
   _light_add("hw.acpi.video.lcd0.brightness");
#endif
}

static Light *
_light_find(const char *dev)
{
   Eina_List *l;
   Light *lig;

   if (!dev) return NULL;
   EINA_LIST_FOREACH(_devices, l, lig)
     {
        if (!strcmp(lig->dev, dev)) return lig;
     }
   return NULL;
}

static void
_cb_bklight_list(void *data EINA_UNUSED, const char *params EINA_UNUSED)
{ // reply = "dev1 -|p dev2 -|p ..."
   Eina_List *l;
   Light *lig;
   char *rep = NULL, *p = NULL;
   size_t repsize = 0;

   eina_lock_take(&_devices_lock);
   EINA_LIST_FOREACH(_devices, l, lig)
     {
        repsize += strlen(lig->dev) + 2 + 1;
     }
   if (repsize > 0)
     {
        rep = malloc(repsize);
        if (!rep) abort();
        p = rep;
        EINA_LIST_FOREACH(_devices, l, lig)
          {
             size_t len = strlen(lig->dev);
             memcpy(p, lig->dev, len + 1);
             p[len] = ' '; len++;
             if (lig->prefer) p[len] = 'p';
             else p[len] = '-';
             len++;
             if (l->next) p[len] = ' ';
             else p[len] = '\0';
             p += len + 1;
          }
     }
   eina_lock_release(&_devices_lock);
   e_system_inout_command_send("bklight-list", "%s", rep);
   free(rep);
}

static void
_cb_bklight_refresh(void *data EINA_UNUSED, const char *params EINA_UNUSED)
{
   Eina_Bool tmp = alert_backlight_reset;
   alert_backlight_reset = EINA_FALSE;
   eina_lock_take(&_devices_lock);
   _light_refresh_devices();
   eina_lock_release(&_devices_lock);
   alert_backlight_reset = tmp;
}

static void
_cb_bklight_get(void *data EINA_UNUSED, const char *params)
{
   Light *lig;

   eina_lock_take(&_devices_lock);
   lig = _light_find(params);
   if (!lig) goto done;
   lig->val_get_count++;
   eina_semaphore_release(&_worker_sem, 1);
   _light_get(lig);
done:
   eina_lock_release(&_devices_lock);
}

static void
_cb_bklight_set(void *data EINA_UNUSED, const char *params)
{
   Light *lig;
   char dev[1024] = "";
   int val = 0;

   if (!params) return;
   if (sscanf(params, "%1023s %i", dev, &val) != 2) return;
   eina_lock_take(&_devices_lock);
   lig = _light_find(dev);
   if (!lig) goto done;
   lig->val_set = val;
   lig->set = EINA_TRUE;
   eina_semaphore_release(&_worker_sem, 1);
done:
   eina_lock_release(&_devices_lock);
}

void
e_system_backlight_init(void)
{
   eina_lock_new(&_devices_lock);
   eina_semaphore_new(&_worker_sem, 0);
   _light_refresh_devices();
   ecore_thread_feedback_run(_cb_worker, _cb_worker_message,
                             _cb_worker_end, _cb_worker_cancel,
                             NULL, EINA_TRUE);
   e_system_inout_command_register("bklight-list",    _cb_bklight_list, NULL);
   e_system_inout_command_register("bklight-refresh", _cb_bklight_refresh, NULL);
   e_system_inout_command_register("bklight-get",     _cb_bklight_get, NULL);
   e_system_inout_command_register("bklight-set",     _cb_bklight_set, NULL);
}

void
e_system_backlight_shutdown(void)
{
   // only shutdown things we really have to - no need to free mem etc.
}
