#include "e_illume_private.h"
#include "e_mod_policy.h"

/* local function prototypes */
static char *_e_mod_policy_find(void);
static int _e_mod_policy_load(char *file);
static void _e_mod_policy_handlers_add(void);
static void _e_mod_policy_hooks_add(void);
static void _e_mod_policy_cb_free(E_Illume_Policy *p);
static Eina_Bool _e_mod_policy_cb_border_add(void *data __UNUSED__, int type __UNUSED__, void *event);
static Eina_Bool _e_mod_policy_cb_border_del(void *data __UNUSED__, int type __UNUSED__, void *event);
static Eina_Bool _e_mod_policy_cb_border_focus_in(void *data __UNUSED__, int type __UNUSED__, void *event);
static Eina_Bool _e_mod_policy_cb_border_focus_out(void *data __UNUSED__, int type __UNUSED__, void *event);
static Eina_Bool _e_mod_policy_cb_border_show(void *data __UNUSED__, int type __UNUSED__, void *event);
static Eina_Bool _e_mod_policy_cb_zone_move_resize(void *data __UNUSED__, int type __UNUSED__, void *event);
static Eina_Bool _e_mod_policy_cb_client_message(void *data __UNUSED__, int type __UNUSED__, void *event);
static Eina_Bool _e_mod_policy_cb_window_property(void *data __UNUSED__, int type __UNUSED__, void *event);
static Eina_Bool _e_mod_policy_cb_policy_change(void *data __UNUSED__, int type, void *event __UNUSED__);
static void _e_mod_policy_cb_hook_post_fetch(void *data __UNUSED__, void *data2);
static void _e_mod_policy_cb_hook_post_assign(void *data __UNUSED__, void *data2);
static void _e_mod_policy_cb_hook_layout(void *data __UNUSED__, void *data2 __UNUSED__);

/* local variables */
static E_Illume_Policy *_policy = NULL;
static Eina_List *_policy_hdls = NULL, *_policy_hooks = NULL;

/* external variables */
int E_ILLUME_POLICY_EVENT_CHANGE = 0;

int 
e_mod_policy_init(void) 
{
   Eina_List *ml;
   E_Manager *man;
   char *file;

   /* try to find the policy specified in config */
   if (!(file = _e_mod_policy_find())) 
     {
        printf("Cannot find policy\n");
        return 0;
     }

   /* attempt to load policy */
   if (!_e_mod_policy_load(file)) 
     {
        /* loading policy failed, bail out */
        printf("Cannot load policy: %s\n", file);
        if (file) free(file);
        return 0;
     }

   /* create new event for policy changes */
   E_ILLUME_POLICY_EVENT_CHANGE = ecore_event_type_new();

   /* add our event handlers */
   _e_mod_policy_handlers_add();

   /* add our border hooks */
   _e_mod_policy_hooks_add();

   /* loop the root windows */
   EINA_LIST_FOREACH(e_manager_list(), ml, man) 
     {
        Eina_List *cl;
        E_Container *con;

        /* loop the containers */
        EINA_LIST_FOREACH(man->containers, cl, con) 
          {
             Eina_List *zl;
             E_Zone *zone;

             /* loop the zones */
             EINA_LIST_FOREACH(con->zones, zl, zone) 
               {
                  E_Illume_Config_Zone *cz;
                  Ecore_X_Illume_Mode mode = ECORE_X_ILLUME_MODE_SINGLE;

                  /* check for zone config */
                  if (!(cz = e_illume_zone_config_get(zone->id))) 
                    continue;

                  /* set mode on this zone */
                  if (cz->mode.dual == 0)
                    mode = ECORE_X_ILLUME_MODE_SINGLE;
                  else 
                    {
                       if ((cz->mode.dual == 1) && (cz->mode.side == 0)) 
                         mode = ECORE_X_ILLUME_MODE_DUAL_TOP;
                       else if ((cz->mode.dual == 1) && (cz->mode.side == 1))
                         mode = ECORE_X_ILLUME_MODE_DUAL_LEFT;
                    }
                  ecore_x_e_illume_mode_set(zone->black_win, mode);
               }
          }
     }

   return 1;
}

int 
e_mod_policy_shutdown(void) 
{
   Ecore_Event_Handler *hdl;
   E_Border_Hook *hook;

   /* remove the ecore event handlers */
   EINA_LIST_FREE(_policy_hdls, hdl)
     ecore_event_handler_del(hdl);

   /* remove the border hooks */
   EINA_LIST_FREE(_policy_hooks, hook)
     e_border_hook_del(hook);

   /* destroy the policy if it exists */
   if (_policy) e_object_del(E_OBJECT(_policy));

   /* reset event type */
   E_ILLUME_POLICY_EVENT_CHANGE = 0;

   return 1;
}

/* local functions */
static char *
_e_mod_policy_find(void) 
{
   Eina_List *files;
   char buff[PATH_MAX], dir[PATH_MAX], *file;

   snprintf(buff, sizeof(buff), "%s.so", _e_illume_cfg->policy.name);
   snprintf(dir, sizeof(dir), "%s/policies", _e_illume_mod_dir);

   /* try to list all files in this directory */
   if (!(files = ecore_file_ls(dir))) return NULL;

   /* loop the returned files */
   EINA_LIST_FREE(files, file) 
     {
        /* compare file with needed .so */
        if (!strcmp(file, buff)) 
          {
             snprintf(dir, sizeof(dir), "%s/policies/%s", 
                      _e_illume_mod_dir, file);
             break;
          }
        free(file);
     }
   if (file) free(file);
   else 
     {
        /* if we did not find the requested policy, use a fallback */
        snprintf(dir, sizeof(dir), "%s/policies/illume.so", _e_illume_mod_dir);
     }

   return strdup(dir);
}

static int 
_e_mod_policy_load(char *file) 
{
   /* safety check */
   if (!file) return 0;

   /* delete existing policy first */
   if (_policy) e_object_del(E_OBJECT(_policy));

   /* try to create our new policy object */
   _policy = 
     E_OBJECT_ALLOC(E_Illume_Policy, E_ILLUME_POLICY_TYPE, 
                    _e_mod_policy_cb_free);
   if (!_policy) 
     {
        printf("Failed to allocate new policy object\n");
        return 0;
     }

   /* attempt to open the .so */
   if (!(_policy->handle = dlopen(file, (RTLD_NOW | RTLD_GLOBAL)))) 
     {
        /* cannot open the .so file, bail out */
        printf("Cannot open policy: %s\n", ecore_file_file_get(file));
        printf("\tError: %s\n", dlerror());
        e_object_del(E_OBJECT(_policy));
        return 0;
     }

   /* clear any existing errors in dynamic loader */
   dlerror();

   /* try to link to the needed policy api functions */
   _policy->api = dlsym(_policy->handle, "e_illume_policy_api");
   _policy->funcs.init = dlsym(_policy->handle, "e_illume_policy_init");
   _policy->funcs.shutdown = dlsym(_policy->handle, "e_illume_policy_shutdown");

   /* check that policy supports needed functions */
   if ((!_policy->api) || (!_policy->funcs.init) || (!_policy->funcs.shutdown)) 
     {
        /* policy doesn't support needed functions, bail out */
        printf("Policy does not support needed functions: %s\n", 
               ecore_file_file_get(file));
        printf("\tError: %s\n", dlerror());
        e_object_del(E_OBJECT(_policy));
        return 0;
     }

   /* check policy api version */
   if (_policy->api->version < E_ILLUME_POLICY_API_VERSION) 
     {
        /* policy is too old, bail out */
        printf("Policy is too old: %s\n", ecore_file_file_get(file));
        e_object_del(E_OBJECT(_policy));
        return 0;
     }

   /* try to initialize the policy */
   if (!_policy->funcs.init(_policy)) 
     {
        /* init failed, bail out */
        printf("Policy failed to initialize: %s\n", ecore_file_file_get(file));
        e_object_del(E_OBJECT(_policy));
        return 0;
     }

   return 1;
}

static void 
_e_mod_policy_handlers_add(void) 
{
   _policy_hdls = 
     eina_list_append(_policy_hdls, 
                      ecore_event_handler_add(E_EVENT_BORDER_ADD, 
                                              _e_mod_policy_cb_border_add, NULL));
   _policy_hdls = 
     eina_list_append(_policy_hdls, 
                      ecore_event_handler_add(E_EVENT_BORDER_REMOVE, 
                                              _e_mod_policy_cb_border_del, NULL));
   _policy_hdls = 
     eina_list_append(_policy_hdls, 
                      ecore_event_handler_add(E_EVENT_BORDER_FOCUS_IN, 
                                              _e_mod_policy_cb_border_focus_in, 
                                              NULL));
   _policy_hdls = 
     eina_list_append(_policy_hdls, 
                      ecore_event_handler_add(E_EVENT_BORDER_FOCUS_OUT, 
                                              _e_mod_policy_cb_border_focus_out, 
                                              NULL));
   _policy_hdls = 
     eina_list_append(_policy_hdls, 
                      ecore_event_handler_add(E_EVENT_BORDER_SHOW, 
                                              _e_mod_policy_cb_border_show, 
                                              NULL));
   _policy_hdls = 
     eina_list_append(_policy_hdls, 
                      ecore_event_handler_add(E_EVENT_ZONE_MOVE_RESIZE, 
                                              _e_mod_policy_cb_zone_move_resize, 
                                              NULL));
   _policy_hdls = 
     eina_list_append(_policy_hdls, 
                      ecore_event_handler_add(ECORE_X_EVENT_CLIENT_MESSAGE, 
                                              _e_mod_policy_cb_client_message, 
                                              NULL));
   _policy_hdls = 
     eina_list_append(_policy_hdls, 
                      ecore_event_handler_add(ECORE_X_EVENT_WINDOW_PROPERTY, 
                                              _e_mod_policy_cb_window_property, 
                                              NULL));
   _policy_hdls = 
     eina_list_append(_policy_hdls, 
                      ecore_event_handler_add(E_ILLUME_POLICY_EVENT_CHANGE, 
                                              _e_mod_policy_cb_policy_change, 
                                              NULL));
}

static void 
_e_mod_policy_hooks_add(void) 
{
   _policy_hooks = 
     eina_list_append(_policy_hooks, 
                      e_border_hook_add(E_BORDER_HOOK_EVAL_POST_FETCH, 
                                        _e_mod_policy_cb_hook_post_fetch, NULL));
   _policy_hooks = 
     eina_list_append(_policy_hooks, 
                      e_border_hook_add(E_BORDER_HOOK_EVAL_POST_BORDER_ASSIGN, 
                                        _e_mod_policy_cb_hook_post_assign, NULL));
   _policy_hooks = 
     eina_list_append(_policy_hooks, 
                      e_border_hook_add(E_BORDER_HOOK_CONTAINER_LAYOUT, 
                                        _e_mod_policy_cb_hook_layout, NULL));
}

static void 
_e_mod_policy_cb_free(E_Illume_Policy *p) 
{
   /* tell the policy to shutdown */
   if (p->funcs.shutdown) p->funcs.shutdown(p);
   p->funcs.shutdown = NULL;

   p->funcs.init = NULL;
   p->api = NULL;

   /* close the linked .so */
   if (p->handle) dlclose(p->handle);
   p->handle = NULL;

   E_FREE(p);
}

static Eina_Bool
_e_mod_policy_cb_border_add(void *data __UNUSED__, int type __UNUSED__, void *event) 
{
   E_Event_Border_Add *ev;

   ev = event;
   if ((_policy) && (_policy->funcs.border_add))
     _policy->funcs.border_add(ev->border);

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_mod_policy_cb_border_del(void *data __UNUSED__, int type __UNUSED__, void *event) 
{
   E_Event_Border_Remove *ev;

   ev = event;
   if ((_policy) && (_policy->funcs.border_del))
     _policy->funcs.border_del(ev->border);

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_mod_policy_cb_border_focus_in(void *data __UNUSED__, int type __UNUSED__, void *event) 
{
   E_Event_Border_Focus_In *ev;

   ev = event;
   if ((_policy) && (_policy->funcs.border_focus_in))
     _policy->funcs.border_focus_in(ev->border);

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_mod_policy_cb_border_focus_out(void *data __UNUSED__, int type __UNUSED__, void *event) 
{
   E_Event_Border_Focus_Out *ev;

   ev = event;
   if ((_policy) && (_policy->funcs.border_focus_out))
     _policy->funcs.border_focus_out(ev->border);

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_mod_policy_cb_border_show(void *data __UNUSED__, int type __UNUSED__, void *event) 
{
   E_Event_Border_Show *ev;

   ev = event;
   if ((_policy) && (_policy->funcs.border_show))
     _policy->funcs.border_show(ev->border);

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_mod_policy_cb_zone_move_resize(void *data __UNUSED__, int type __UNUSED__, void *event) 
{
   E_Event_Zone_Move_Resize *ev;

   ev = event;
   if ((_policy) && (_policy->funcs.zone_move_resize))
     _policy->funcs.zone_move_resize(ev->zone);

   return ECORE_CALLBACK_PASS_ON;
}

static E_Zone *
_e_mod_zone_win_get(Ecore_X_Window win)
{
   E_Zone *zone = NULL;
   E_Border *bd;

   if (!(bd = e_border_find_by_client_window(win)))
     {
        if (!(zone = e_util_zone_window_find(win))) return NULL;
     }
   else if (bd->zone) zone = bd->zone;
   return zone;
}

static Eina_Bool
_e_mod_policy_cb_client_message(void *data __UNUSED__, int type __UNUSED__, void *event) 
{
   Ecore_X_Event_Client_Message *ev;

   ev = event;
   if (ev->message_type == ECORE_X_ATOM_NET_ACTIVE_WINDOW) 
     {
        E_Border *bd;

        if (!(bd = e_border_find_by_client_window(ev->win))) return ECORE_CALLBACK_PASS_ON;
        if ((_policy) && (_policy->funcs.border_activate))
          _policy->funcs.border_activate(bd);
     }
   else if (ev->message_type == ECORE_X_ATOM_E_ILLUME_MODE) 
     {
        E_Zone *zone;
        
        if (!(zone = _e_mod_zone_win_get(ev->win))) return ECORE_CALLBACK_PASS_ON;
        if ((_policy) && (_policy->funcs.zone_mode_change))
          _policy->funcs.zone_mode_change(zone, ev->data.l[0]);
     }
   else if (ev->message_type == ECORE_X_ATOM_E_ILLUME_CLOSE) 
     {
        E_Zone *zone;

        if (!(zone = _e_mod_zone_win_get(ev->win))) return ECORE_CALLBACK_PASS_ON;
        if ((_policy) && (_policy->funcs.zone_close))
          _policy->funcs.zone_close(zone);
     }
   else if (ev->message_type == ECORE_X_ATOM_E_ILLUME_FOCUS_BACK) 
     {
        E_Zone *zone;

        if (!(zone = _e_mod_zone_win_get(ev->win))) return ECORE_CALLBACK_PASS_ON;
        if ((_policy) && (_policy->funcs.focus_back))
          _policy->funcs.focus_back(zone);
     }
   else if (ev->message_type == ECORE_X_ATOM_E_ILLUME_FOCUS_FORWARD) 
     {
        E_Zone *zone;

        if (!(zone = _e_mod_zone_win_get(ev->win))) return ECORE_CALLBACK_PASS_ON;
        if ((_policy) && (_policy->funcs.focus_forward))
          _policy->funcs.focus_forward(zone);
     }
   else if (ev->message_type == ECORE_X_ATOM_E_ILLUME_FOCUS_HOME) 
     {
        E_Zone *zone;

        if (!(zone = _e_mod_zone_win_get(ev->win))) return ECORE_CALLBACK_PASS_ON;
        if ((_policy) && (_policy->funcs.focus_home))
          _policy->funcs.focus_home(zone);
     }
   else if (ev->message_type == ECORE_X_ATOM_E_ILLUME_DRAG_START) 
     {
        E_Border *bd;

        if (!(bd = e_border_find_by_client_window(ev->win))) return ECORE_CALLBACK_PASS_ON;
        if ((_policy) && (_policy->funcs.drag_start))
          _policy->funcs.drag_start(bd);
     }
   else if (ev->message_type == ECORE_X_ATOM_E_ILLUME_DRAG_END) 
     {
        E_Border *bd;

        if (!(bd = e_border_find_by_client_window(ev->win))) return ECORE_CALLBACK_PASS_ON;
        if ((_policy) && (_policy->funcs.drag_end))
          _policy->funcs.drag_end(bd);
     }

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_mod_policy_cb_window_property(void *data __UNUSED__, int type __UNUSED__, void *event) 
{
   Ecore_X_Event_Window_Property *ev;

   ev = event;
   if ((_policy) && (_policy->funcs.property_change))
     _policy->funcs.property_change(ev);

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_mod_policy_cb_policy_change(void *data __UNUSED__, int type, void *event __UNUSED__) 
{
   char *file;

   if (type != E_ILLUME_POLICY_EVENT_CHANGE) return ECORE_CALLBACK_PASS_ON;

   /* find policy specified in config */
   if (!(file = _e_mod_policy_find())) return ECORE_CALLBACK_PASS_ON;

   /* try to load the policy */
   _e_mod_policy_load(file);

   if (file) free(file);

   return ECORE_CALLBACK_PASS_ON;
}

static void 
_e_mod_policy_cb_hook_post_fetch(void *data __UNUSED__, void *data2) 
{
   E_Border *bd;

   if (!(bd = data2)) return;
   if ((_policy) && (_policy->funcs.border_post_fetch))
     _policy->funcs.border_post_fetch(bd);
}

static void 
_e_mod_policy_cb_hook_post_assign(void *data __UNUSED__, void *data2) 
{
   E_Border *bd;

   if (!(bd = data2)) return;
   if ((_policy) && (_policy->funcs.border_post_assign))
     _policy->funcs.border_post_assign(bd);
}

static void 
_e_mod_policy_cb_hook_layout(void *data __UNUSED__, void *data2 __UNUSED__) 
{
   E_Zone *zone;
   E_Border *bd;
   Eina_List *zl = NULL, *l;

   /* loop through border list and find what changed */
   EINA_LIST_FOREACH(e_border_client_list(), l, bd) 
     {
        if ((bd->new_client) || (bd->pending_move_resize) || 
            (bd->changes.pos) || (bd->changes.size) || (bd->changes.visible) || 
            (bd->need_shape_export) || (bd->need_shape_merge)) 
          {
             /* NB: this border changed. add it's zone to list of what needs 
              * updating. This is done so we do not waste cpu cycles 
              * updating zones where nothing changed */
             if (!eina_list_data_find(zl, bd->zone))
               zl = eina_list_append(zl, bd->zone);
          }
     }
   l = eina_list_free(l);

   /* loop the zones that need updating and call the policy update function */
   EINA_LIST_FREE(zl, zone) 
     {
        if ((_policy) && (_policy->funcs.zone_layout))
          _policy->funcs.zone_layout(zone);
     }
   zl = eina_list_free(zl);
}
