#include "e.h"
#ifdef HAVE_EEZE
# include <Eeze.h>
#endif

// FIXME: backlight should be tied per zone but this implementation is just
// a signleton right now as thats 99% of use cases. but api supports
// doing more. for now make it work in the singleton

#define MODE_NONE  -1
#define MODE_RANDR 0
#define MODE_SYS   1

static double bl_val = 1.0;
static double bl_animval = 1.0;
static int sysmode = MODE_NONE;
static Ecore_Animator *bl_anim = NULL;
static Eina_List *bl_devs = NULL;

static Ecore_Event_Handler *_e_backlight_handler_config_mode = NULL;
static Ecore_Event_Handler *_e_backlight_handler_border_fullscreen = NULL;
static Ecore_Event_Handler *_e_backlight_handler_border_unfullscreen = NULL;
static Ecore_Event_Handler *_e_backlight_handler_border_remove = NULL;
static Ecore_Event_Handler *_e_backlight_handler_border_iconify = NULL;
static Ecore_Event_Handler *_e_backlight_handler_border_uniconify = NULL;
static Ecore_Event_Handler *_e_backlight_handler_border_desk_set = NULL;
static Ecore_Event_Handler *_e_backlight_handler_desk_show = NULL;

static void      _e_backlight_update(E_Zone *zone);
static void      _e_backlight_set(E_Zone *zone, double val);
static Eina_Bool _bl_anim(void *data, double pos);
static Eina_Bool bl_avail = EINA_FALSE;
static Eina_Bool _e_backlight_handler(void *d, int type, void *ev);
#ifdef HAVE_EEZE
static double bl_delayval = 1.0;
static const char *bl_sysval = NULL;
static Ecore_Event_Handler *bl_sys_exit_handler = NULL;
static Ecore_Exe *bl_sys_set_exe = NULL;
static Eina_Bool bl_sys_pending_set = EINA_FALSE;
static Eina_Bool bl_sys_set_exe_ready = EINA_TRUE;

static void      _bl_sys_find(void);
static void      _bl_sys_level_get(void);
static Eina_Bool _e_bl_cb_exit(void *data __UNUSED__, int type __UNUSED__, void *event);
static void      _bl_sys_level_set(double val);
#endif

EAPI int E_EVENT_BACKLIGHT_CHANGE = -1;

EINTERN int
e_backlight_init(void)
{
#ifdef HAVE_EEZE
   eeze_init();
#endif
// why did someone do this? this makes it ONLY work if xrandr has bl support.
// WRONG!
//   bl_avail = ecore_x_randr_output_backlight_available();
   bl_avail = EINA_TRUE;

   _e_backlight_handler_config_mode = ecore_event_handler_add
       (E_EVENT_CONFIG_MODE_CHANGED, _e_backlight_handler, NULL);

   _e_backlight_handler_border_fullscreen = ecore_event_handler_add
       (E_EVENT_BORDER_FULLSCREEN, _e_backlight_handler, NULL);

   _e_backlight_handler_border_unfullscreen = ecore_event_handler_add
       (E_EVENT_BORDER_UNFULLSCREEN, _e_backlight_handler, NULL);

   _e_backlight_handler_border_remove = ecore_event_handler_add
       (E_EVENT_BORDER_REMOVE, _e_backlight_handler, NULL);

   _e_backlight_handler_border_iconify = ecore_event_handler_add
       (E_EVENT_BORDER_ICONIFY, _e_backlight_handler, NULL);

   _e_backlight_handler_border_uniconify = ecore_event_handler_add
       (E_EVENT_BORDER_UNICONIFY, _e_backlight_handler, NULL);

   _e_backlight_handler_border_desk_set = ecore_event_handler_add
       (E_EVENT_BORDER_DESK_SET, _e_backlight_handler, NULL);

   _e_backlight_handler_desk_show = ecore_event_handler_add
       (E_EVENT_DESK_SHOW, _e_backlight_handler, NULL);

   if (bl_avail)
     {
        e_backlight_update();
        if (!getenv("E_RESTART"))
          {
             e_backlight_level_set(NULL, 0.1, 0.0);
             e_backlight_level_set(NULL, e_config->backlight.normal, 0.0);
          }
     }

   E_EVENT_BACKLIGHT_CHANGE = ecore_event_type_new();

   return 1;
}

EINTERN int
e_backlight_shutdown(void)
{
   const char *s;

   if (bl_anim) ecore_animator_del(bl_anim);
   bl_anim = NULL;

   if (e_config->backlight.mode != E_BACKLIGHT_MODE_NORMAL)
     e_backlight_level_set(NULL, e_config->backlight.normal, 0.0);

   EINA_LIST_FREE(bl_devs, s)
     eina_stringshare_del(s);
#ifdef HAVE_EEZE
   if (bl_sysval) eina_stringshare_del(bl_sysval);
   bl_sysval = NULL;
   if (bl_sys_exit_handler) ecore_event_handler_del(bl_sys_exit_handler);
   bl_sys_exit_handler = NULL;
   bl_sys_set_exe = NULL;
   bl_sys_pending_set = EINA_FALSE;
   eeze_shutdown();
#endif
   if (_e_backlight_handler_config_mode)
     {
        ecore_event_handler_del(_e_backlight_handler_config_mode);
        _e_backlight_handler_config_mode = NULL;
     }

   if (_e_backlight_handler_border_fullscreen)
     {
        ecore_event_handler_del(_e_backlight_handler_border_fullscreen);
        _e_backlight_handler_border_fullscreen = NULL;
     }

   if (_e_backlight_handler_border_unfullscreen)
     {
        ecore_event_handler_del(_e_backlight_handler_border_unfullscreen);
        _e_backlight_handler_border_unfullscreen = NULL;
     }

   if (_e_backlight_handler_border_remove)
     {
        ecore_event_handler_del(_e_backlight_handler_border_remove);
        _e_backlight_handler_border_remove = NULL;
     }

   if (_e_backlight_handler_border_iconify)
     {
        ecore_event_handler_del(_e_backlight_handler_border_iconify);
        _e_backlight_handler_border_iconify = NULL;
     }

   if (_e_backlight_handler_border_uniconify)
     {
        ecore_event_handler_del(_e_backlight_handler_border_uniconify);
        _e_backlight_handler_border_uniconify = NULL;
     }

   if (_e_backlight_handler_border_desk_set)
     {
        ecore_event_handler_del(_e_backlight_handler_border_desk_set);
        _e_backlight_handler_border_desk_set = NULL;
     }

   if (_e_backlight_handler_desk_show)
     {
        ecore_event_handler_del(_e_backlight_handler_desk_show);
        _e_backlight_handler_desk_show = NULL;
     }
   return 1;
}

EAPI Eina_Bool
e_backlight_exists(void)
{
   if (sysmode == MODE_NONE) return EINA_FALSE;
   return EINA_TRUE;
}

EAPI void
e_backlight_update(void)
{
   Eina_List *m, *c, *z;
   E_Manager *man;
   E_Container *con;
   E_Zone *zone;

   if (bl_avail == EINA_FALSE) return;

   EINA_LIST_FOREACH(e_manager_list(), m, man)
     {
        EINA_LIST_FOREACH(man->containers, c, con)
          {
             EINA_LIST_FOREACH(con->zones, z, zone)
               {
                  _e_backlight_update(zone);
               }
          }
     }
}

EAPI void
e_backlight_level_set(E_Zone *zone, double val, double tim)
{
   double bl_now;
   // zone == NULL == everything
   // set backlight associated with zone to val over period of tim
   // if tim == 0.0 - then do it instantnly, if time == -1 use some default
   // transition time
   if (val < 0.0) val = 0.0;
   else if (val > 1.0)
     val = 1.0;
   if ((fabs(val - bl_val) < DBL_EPSILON) && (!bl_anim)) return;
   if (!zone) zone = e_util_zone_current_get(e_manager_current_get());
   ecore_event_add(E_EVENT_BACKLIGHT_CHANGE, NULL, NULL, NULL);
   bl_now = bl_val;
   bl_val = val;
   if (fabs(tim) < DBL_EPSILON)
     {
        _e_backlight_set(zone, val);
        return;
     }
//   if (e_config->backlight.mode != E_BACKLIGHT_MODE_NORMAL) return;
   if (e_config->backlight.mode == E_BACKLIGHT_MODE_NORMAL)
     tim = 0.5;
   else
   if (tim < 0.0)
     tim = e_config->backlight.transition;

   E_FREE_FUNC(bl_anim, ecore_animator_del);
   bl_anim = ecore_animator_timeline_add(tim, _bl_anim, zone);
   bl_animval = bl_now;
}

EAPI double
e_backlight_level_get(E_Zone *zone __UNUSED__)
{
   // zone == NULL == everything
   return bl_val;
}

EAPI void
e_backlight_mode_set(E_Zone *zone, E_Backlight_Mode mode)
{
   E_Backlight_Mode pmode;
   
   // zone == NULL == everything
   if (e_config->backlight.mode == mode) return;
   pmode = e_config->backlight.mode;
   e_config->backlight.mode = mode;
   if (e_config->backlight.mode == E_BACKLIGHT_MODE_NORMAL)
     {
        e_backlight_level_set(zone, e_config->backlight.normal, -1.0);
     }
   else if (e_config->backlight.mode == E_BACKLIGHT_MODE_OFF)
     {
        e_backlight_level_set(zone, 0.0, -1.0);
     }
   else if (e_config->backlight.mode == E_BACKLIGHT_MODE_DIM)
     {
        if ((pmode != E_BACKLIGHT_MODE_NORMAL) ||
            ((pmode == E_BACKLIGHT_MODE_NORMAL) &&
             (e_config->backlight.normal > e_config->backlight.dim)))
          e_backlight_level_set(zone, e_config->backlight.dim, -1.0);
     }
   else if (e_config->backlight.mode == E_BACKLIGHT_MODE_MAX)
     e_backlight_level_set(zone, 1.0, -1.0);
}

EAPI E_Backlight_Mode
e_backlight_mode_get(E_Zone *zone __UNUSED__)
{
   // zone == NULL == everything
   return e_config->backlight.mode;
}

EAPI const Eina_List *
e_backlight_devices_get(void)
{
   return bl_devs;
}

/* local subsystem functions */

static Eina_Bool
_e_backlight_handler(void *d __UNUSED__, int type __UNUSED__, void *ev __UNUSED__)
{
   e_backlight_update();
   return ECORE_CALLBACK_PASS_ON;
}

static void
_e_backlight_update(E_Zone *zone)
{
   double x_bl = -1.0;
   Ecore_X_Window root;
   Ecore_X_Randr_Output *out;
   int i, num = 0;

   root = zone->container->manager->root;
   // try randr
   out = ecore_x_randr_window_outputs_get(root, &num);
   if ((out) && (num > 0) && (ecore_x_randr_output_backlight_available()))
     {
        char *name;
        const char *s;
        Eina_Bool gotten = EINA_FALSE;

        EINA_LIST_FREE(bl_devs, s)
          eina_stringshare_del(s);
        for (i = 0; i < num; i++)
          {
             name = ecore_x_randr_output_name_get(root, out[i], NULL);
             bl_devs = eina_list_append(bl_devs, eina_stringshare_add(name));
             if ((name) && (e_config->backlight.sysdev) &&
                 (!strcmp(name, e_config->backlight.sysdev)))
               {
                  x_bl = ecore_x_randr_output_backlight_level_get(root, out[i]);
                  gotten = EINA_TRUE;
               }
             free(name);
          }
        if (!gotten)
          x_bl = ecore_x_randr_output_backlight_level_get(root, out[0]);
     }
   free(out);
   if (x_bl >= 0.0)
     {
        bl_val = x_bl;
        sysmode = MODE_RANDR;
        return;
     }
#ifdef HAVE_EEZE
   _bl_sys_find();
   if (bl_sysval)
     {
        sysmode = MODE_SYS;
        _bl_sys_level_get();
        return;
     }
#endif
}

static void
_e_backlight_set(E_Zone *zone, double val)
{
   if (sysmode == MODE_RANDR)
     {
        Ecore_X_Window root;
        Ecore_X_Randr_Output *out;
        int num = 0, i;
        char *name;

        root = zone->container->manager->root;
        out = ecore_x_randr_window_outputs_get(root, &num);
        if ((out) && (num > 0))
          {
             Eina_Bool gotten = EINA_FALSE;
             for (i = 0; i < num; i++)
               {
                  name = ecore_x_randr_output_name_get(root, out[i], NULL);
                  if (name)
                    {
                       if ((e_config->backlight.sysdev) &&
                           (!strcmp(name, e_config->backlight.sysdev)))
                         {
                            ecore_x_randr_output_backlight_level_set(root, out[i], val);
                            gotten = EINA_TRUE;
                         }
                       free(name);
                    }
               }
             if (!gotten)
               {
                  for (i = 0; i < num; i++)
                    ecore_x_randr_output_backlight_level_set(root, out[i], val);
               }
          }
        free(out);
     }
#ifdef HAVE_EEZE
   else if (sysmode == MODE_SYS)
     {
        if (bl_sysval)
          {
             _bl_sys_level_set(val);
          }
     }
#endif
}

static Eina_Bool
_bl_anim(void *data, double pos)
{
   E_Zone *zone = data;
   double v;

   // FIXME: if zone is deleted while anim going... bad things.
   pos = ecore_animator_pos_map(pos, ECORE_POS_MAP_DECELERATE, 0.0, 0.0);
   v = (bl_animval * (1.0 - pos)) + (bl_val * pos);
   _e_backlight_set(zone, v);
   if (pos >= 1.0)
     {
        bl_anim = NULL;
        return EINA_FALSE;
     }
   return EINA_TRUE;
}

#ifdef HAVE_EEZE
static void
_bl_sys_find(void)
{
   Eina_List *l, *devs, *pdevs = NULL;
   Eina_Bool use;
   const char *f, *s;
   int v;

   devs = eeze_udev_find_by_filter("backlight", NULL, NULL);
   if (!devs)
     {
        /* FIXME: need to make this more precise so we don't set keyboard LEDs or something */
        devs = eeze_udev_find_by_filter("leds", NULL, NULL);
        if (!devs) return;
     }
   if (eina_list_count(devs) > 1)
     {
        /* prefer backlights of type "firmware" where available */
        EINA_LIST_FOREACH(devs, l, f)
          {
             s = eeze_udev_syspath_get_sysattr(f, "type");
             use = (s && (!strcmp(s, "firmware")));
             eina_stringshare_del(s);
             if (!use) continue;
             s = eeze_udev_syspath_get_sysattr(f, "brightness");
             if (!s) continue;
             v = atoi(s);
             eina_stringshare_del(s);
             if (v < 0) continue;
             pdevs = eina_list_append(pdevs, eina_stringshare_add(f));
             eina_stringshare_del(f);
             l->data = NULL;
          }
        EINA_LIST_FOREACH(devs, l, f)
          {
             if (!l->data) continue;
             s = eeze_udev_syspath_get_sysattr(f, "brightness");
             if (!s) continue;
             v = atoi(s);
             eina_stringshare_del(s);
             if (v < 0) continue;
             pdevs = eina_list_append(pdevs, eina_stringshare_add(f));
          }
     }
   if (!pdevs)
     {
        /* add the other backlight or led's if none found */
        EINA_LIST_FOREACH(devs, l, f)
          {
             use = EINA_FALSE;
             s = eeze_udev_syspath_get_sysattr(f, "brightness");
             if (!s) continue;
             v = atoi(s);
             eina_stringshare_del(s);
             if (v < 0) continue;
             pdevs = eina_list_append(pdevs, eina_stringshare_add(f));
          }
     }
   /* clear out original devs list now we've filtered */
   EINA_LIST_FREE(devs, f)
     {
        if (f) eina_stringshare_del(f);
     }
   /* clear out old configured bl sysval */
   if (bl_sysval)
     {
        eina_stringshare_del(bl_sysval);
        bl_sysval = NULL;
     }
   EINA_LIST_FREE(bl_devs, s)
     eina_stringshare_del(s);
   /* if configured backlight is there - use it, or if not use first */
   EINA_LIST_FOREACH(pdevs, l, f)
     {
        bl_devs = eina_list_append(bl_devs, eina_stringshare_add(f));
        if (!bl_sysval)
          {
             if ((e_config->backlight.sysdev) &&
                 (!strcmp(e_config->backlight.sysdev, f)))
               bl_sysval = eina_stringshare_add(f);
          }
     }
   if (!bl_sysval)
     {
        EINA_LIST_FOREACH(pdevs, l, f)
          {
             if (!bl_sysval)
               bl_sysval = eina_stringshare_add(f);
          }
     }
   /* clear out preferred devs list */
   EINA_LIST_FREE(pdevs, f)
     {
        eina_stringshare_del(f);
     }
}

static void
_bl_sys_level_get(void)
{
   int maxval, val;
   const char *str;

   if (bl_anim) return;

   str = eeze_udev_syspath_get_sysattr(bl_sysval, "max_brightness");
   if (!str) return;

   maxval = atoi(str);
   eina_stringshare_del(str);
   if (maxval < 0) maxval = 255;
   str = eeze_udev_syspath_get_sysattr(bl_sysval, "brightness");
   if (!str) return;

   val = atoi(str);
   eina_stringshare_del(str);
   if ((!maxval) && (!val))
     {
        bl_val = 0;
        sysmode = MODE_NONE;
        return;
     }
   if (!maxval) maxval = 255;
   if ((val >= 0) && (val <= maxval))
     bl_val = (double)val / (double)maxval;
//   fprintf(stderr, "GET: %i/%i (%1.3f)\n", val, maxval, bl_val);
}

static Eina_Bool
_e_bl_cb_ext_delay(void *data __UNUSED__)
{
   bl_sys_set_exe_ready = EINA_TRUE;
   if (bl_sys_pending_set)
     {
        bl_sys_pending_set = EINA_FALSE;

        _bl_sys_level_set(bl_delayval);
     }
   return EINA_FALSE;
}

static Eina_Bool
_e_bl_cb_exit(void *data __UNUSED__, int type __UNUSED__, void *event)
{
   Ecore_Exe_Event_Del *ev;

   ev = event;
   if (ev->exe == bl_sys_set_exe)
     {
        bl_sys_set_exe_ready = EINA_FALSE;
        bl_sys_set_exe = NULL;
        ecore_timer_add(0.05, _e_bl_cb_ext_delay, NULL);
     }
   return ECORE_CALLBACK_RENEW;
}

static void
_bl_sys_level_set(double val)
{
   char buf[PATH_MAX];

   if (!bl_sys_exit_handler)
     bl_sys_exit_handler = ecore_event_handler_add(ECORE_EXE_EVENT_DEL,
                                                   _e_bl_cb_exit, NULL);
   bl_delayval = val;
   if ((bl_sys_set_exe) || (!bl_sys_set_exe_ready))
     {
        bl_sys_pending_set = EINA_TRUE;
        return;
     }
//   fprintf(stderr, "SET: %1.3f\n", val);
   snprintf(buf, sizeof(buf),
            "%s/enlightenment/utils/enlightenment_backlight %i %s",
            e_prefix_lib_get(), (int)(val * 1000.0), bl_sysval);
   bl_sys_set_exe = ecore_exe_run(buf, NULL);
}

#endif
