#include "e.h"

/* intercept elm_win operations so we talk directly to e_client */
#undef elm_win_add
#undef elm_win_util_dialog_add
#undef elm_win_util_standard_add

typedef struct _Elm_Win_Trap_Ctx
{
   E_Client      *client;
   E_Pointer     *pointer;
   int x, y, w, h;
   Eina_Bool      centered E_BITFIELD;
   Eina_Bool      placed E_BITFIELD;
   Eina_Bool      sized E_BITFIELD;
   Eina_Bool      internal_no_remember E_BITFIELD;
   Eina_Bool      internal_no_reopen E_BITFIELD;
   Eina_Bool      visible E_BITFIELD;
   Eina_Bool      override E_BITFIELD;
} Elm_Win_Trap_Ctx;


static Elm_Win_Trap_Ctx *current_win = NULL;

static void *
_e_elm_win_trap_add(Evas_Object *o)
{
   Elm_Win_Trap_Ctx *ctx = calloc(1, sizeof(Elm_Win_Trap_Ctx));
   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
   evas_object_name_set(o, "E_Win");
   return ctx;
}

static void
_e_elm_win_trap_del(void *data, Evas_Object *o)
{
   Elm_Win_Trap_Ctx *ctx = data;
   EINA_SAFETY_ON_NULL_RETURN(ctx);
   if (ctx->client)
     {
        ctx->client->internal_elm_win = NULL;
        e_object_del(E_OBJECT(ctx->client));
        evas_object_data_set(o, "E_Client", NULL);
     }
   free(ctx);
}

static Eina_Bool
_e_elm_win_trap_hide(void *data, Evas_Object *o)
{
   Elm_Win_Trap_Ctx *ctx = data;
   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_TRUE);
   if (!ctx->visible) return EINA_FALSE;
   if (strncmp(ecore_evas_engine_name_get(e_win_ee_get(o)), "wayland", 7))
     E_FREE_FUNC(ctx->pointer, e_object_del);
#ifdef HAVE_WAYLAND
   else if (ctx->client)
     e_pixmap_alias(NULL, E_PIXMAP_TYPE_WL, ecore_wl2_window_surface_id_get(elm_win_wl_window_get(o)));
#endif

   if (!ctx->client) return EINA_TRUE;
   ctx->visible = 0;
   evas_object_hide(ctx->client->frame);
   return EINA_FALSE;
}

static Eina_Bool
_e_elm_win_trap_show(void *data, Evas_Object *o)
{
   Elm_Win_Trap_Ctx *ctx = data;
   Evas *e = evas_object_evas_get(o);
   Ecore_Evas *ee = ecore_evas_ecore_evas_get(e);
   Eina_Bool borderless;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_TRUE);
   borderless = elm_win_borderless_get(o);
   if (!ctx->client)
     {
        E_Client *ec;
        Ecore_Window win;
#ifdef HAVE_WAYLAND
        int64_t wl_win_id = -1;
#endif
        E_Pixmap_Type type = E_PIXMAP_TYPE_X;

        win = elm_win_window_id_get(o);
#ifdef HAVE_WAYLAND
        if (!strncmp(ecore_evas_engine_name_get(ee), "wayland", 7))
          {
             Ecore_Wl2_Window *ewin = elm_win_wl_window_get(o);

             type = E_PIXMAP_TYPE_WL;
             ecore_evas_object_cursor_set(ee, NULL, 0, 0, 0);
             ctx->pointer = e_comp->pointer;
             elm_win_borderless_set(o, 1);
             wl_win_id = ecore_wl2_window_surface_id_get(ewin);
          }
        else
#endif
          {
             type = E_PIXMAP_TYPE_X;
             ctx->pointer = e_pointer_window_new(win, EINA_TRUE);
          }

        if (type == E_PIXMAP_TYPE_WL)
          ec = e_pixmap_find_client(type, (int64_t)win);
        else
          ec = e_pixmap_find_client(type, win);

        if (ec)
          ctx->client = ec;
        else
          {
             E_Pixmap *cp;
             const char *title, *name, *clas;

             ecore_evas_name_class_get(ee, &name, &clas);
             if (!name) name = "E";
             if (!clas) clas = "_e_internal_window";
             ecore_evas_name_class_set(ee, name, clas);
             title = elm_win_title_get(o);
             if ((!title) || (!title[0]))
               title = "E";
             ecore_evas_title_set(ee, title);

             if (type == E_PIXMAP_TYPE_WL)
               cp = e_pixmap_new(type, (int64_t)win);
             else
               cp = e_pixmap_new(type, win);

             EINA_SAFETY_ON_NULL_RETURN_VAL(cp, EINA_TRUE);
#ifdef HAVE_WAYLAND
             if (wl_win_id >= 0)
               e_pixmap_alias(cp, type, wl_win_id);
#endif

             current_win = ctx;
             ctx->client = e_client_new(cp, 0, 1);
             current_win = NULL;
             EINA_SAFETY_ON_NULL_RETURN_VAL(ctx->client, EINA_TRUE);
             eina_stringshare_replace(&ctx->client->icccm.name, name);
             eina_stringshare_replace(&ctx->client->icccm.class, clas);
             eina_stringshare_replace(&ctx->client->icccm.title, title);
          }
        ctx->client->placed = ctx->placed | ctx->centered;
        ctx->client->internal_no_remember = ctx->internal_no_remember;
        ctx->client->internal_no_reopen = ctx->internal_no_reopen;
        ctx->client->internal_elm_win = o;
        elm_win_autodel_set(o, 1);
        evas_object_data_set(o, "E_Client", ctx->client);
        ctx->client->dialog = elm_win_type_get(o) == ELM_WIN_DIALOG_BASIC;
        ctx->client->tooltip = elm_win_type_get(o) == ELM_WIN_TOOLTIP;

        evas_object_size_hint_min_get(o, &ctx->client->icccm.min_w, &ctx->client->icccm.min_h);
        ctx->client->icccm.min_w = MAX(ctx->client->icccm.min_w, 0);
        ctx->client->icccm.min_h = MAX(ctx->client->icccm.min_h, 0);
        evas_object_size_hint_max_get(o, &ctx->client->icccm.max_w, &ctx->client->icccm.max_h);
        ctx->client->icccm.max_w = MAX(ctx->client->icccm.max_w, 0);
        ctx->client->icccm.max_h = MAX(ctx->client->icccm.max_h, 0);
     }
//#endif
   evas_object_geometry_get(o, &ctx->client->client.x, &ctx->client->client.y, &ctx->client->client.w, &ctx->client->client.h);
   ecore_evas_show(ee);
   eina_stringshare_replace(&ctx->client->internal_icon, elm_win_icon_name_get(o));
   if (!ctx->visible)
     {
        ctx->visible = 1;
        evas_object_show(ctx->client->frame);
     }
   ctx->client->borderless |= borderless;
   e_comp_object_frame_xy_adjust(ctx->client->frame, ctx->client->client.x, ctx->client->client.y, &ctx->client->x, &ctx->client->y);
   e_comp_object_frame_wh_adjust(ctx->client->frame, ctx->client->client.w, ctx->client->client.h, &ctx->client->w, &ctx->client->h);
   if (ctx->sized) evas_object_resize(ctx->client->frame, ctx->w, ctx->h);
   if (ctx->placed) evas_object_move(ctx->client->frame, ctx->x, ctx->y);
   if (ctx->centered)
     {
        e_comp_object_util_center(o);
        ctx->centered = 0;
     }
   return EINA_TRUE;
}

static Eina_Bool
_e_elm_win_trap_move(void *data, Evas_Object *o, int x, int y)
{
   Elm_Win_Trap_Ctx *ctx = data;
   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_TRUE);
   if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
     {
        int ex, ey;

        /* if the ee coords match the requested coords, this is coming from
         * a configure event which the compositor sent. failing to reject this
         * move will result in bad window positioning
         */
        ecore_evas_geometry_get(e_win_ee_get(o), &ex, &ey, NULL, NULL);
        if ((x == ex) && (y == ey)) return EINA_FALSE;
     }
   ctx->placed = 1;
   ctx->x = x, ctx->y = y;
   if (!ctx->client) return EINA_TRUE;
   if ((ctx->client->client.x != x) || (ctx->client->client.y != y))
     e_client_util_move_without_frame(ctx->client, x, y);
   return EINA_TRUE;
}

static Eina_Bool
_e_elm_win_trap_resize(void *data, Evas_Object *o EINA_UNUSED, int w, int h)
{
   Elm_Win_Trap_Ctx *ctx = data;
   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_TRUE);
   ctx->sized = 1;
   ctx->w = w, ctx->h = h;
   if (!ctx->client) return EINA_TRUE;
   e_comp_object_frame_wh_adjust(ctx->client->frame, w, h, &w, &h);
   e_client_resize_limit(ctx->client, &w, &h);
   evas_object_resize(ctx->client->frame, w, h);
   return EINA_TRUE;
}

static Eina_Bool
_e_elm_win_trap_center(void *data, Evas_Object *o EINA_UNUSED, Eina_Bool h, Eina_Bool v)
{
   Elm_Win_Trap_Ctx *ctx = data;
   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_TRUE);
   ctx->centered = h | v;
   ctx->placed = 1;
   if (!ctx->client) return EINA_FALSE;
   if (ctx->centered)
     {
        e_comp_object_util_center(ctx->client->frame);
        ctx->centered = 0;
     }
   return EINA_FALSE;
}

static Eina_Bool
_e_elm_win_trap_lower(void *data, Evas_Object *o EINA_UNUSED)
{
   Elm_Win_Trap_Ctx *ctx = data;
   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_TRUE);
   if (!ctx->client) return EINA_TRUE;
   evas_object_lower(ctx->client->frame);
   return EINA_FALSE;
}

static Eina_Bool
_e_elm_win_trap_raise(void *data, Evas_Object *o EINA_UNUSED)
{
   Elm_Win_Trap_Ctx *ctx = data;
   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_TRUE);
   if (!ctx->client) return EINA_TRUE;
   evas_object_raise(ctx->client->frame);
   return EINA_FALSE;
}

static Eina_Bool
_e_elm_win_trap_activate(void *data, Evas_Object *o EINA_UNUSED)
{
   Elm_Win_Trap_Ctx *ctx = data;
   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_TRUE);
   if (!ctx->client) return EINA_TRUE;
   evas_object_focus_set(ctx->client->frame, 1);
   if (!ctx->client->lock_user_stacking)
     evas_object_raise(ctx->client->frame);
   return EINA_FALSE;
}

static Eina_Bool
_e_elm_win_trap_size_min_set(void *data, Evas_Object *o EINA_UNUSED, int w, int h)
{
   Elm_Win_Trap_Ctx *ctx = data;
   int mw = 0, mh = 0;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_TRUE);
   if (!ctx->client) return EINA_TRUE;
   ctx->client->icccm.min_w = w;
   ctx->client->icccm.min_h = h;
   e_client_resize_limit(ctx->client, &mw, &mh);
   mw = ctx->client->w < w ? w : ctx->client->w;
   mh = ctx->client->h < h ? h : ctx->client->h;
   if ((ctx->client->w != mw) || (ctx->client->h != mh))
     evas_object_resize(ctx->client->frame, mw, mh);

   return EINA_TRUE;
}

static Eina_Bool
_e_elm_win_trap_size_max_set(void *data, Evas_Object *o EINA_UNUSED, int w, int h)
{
   Elm_Win_Trap_Ctx *ctx = data;
   int mw = 0, mh = 0;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_TRUE);
   if (!ctx->client) return EINA_TRUE;
   w = MAX(w, 0);
   h = MAX(h, 0);
   ctx->client->icccm.max_w = w;
   ctx->client->icccm.max_h = h;
   e_client_resize_limit(ctx->client, &mw, &mh);
   mw = ctx->client->w > w ? w : ctx->client->w;
   mh = ctx->client->h > h ? h : ctx->client->h;
   if ((ctx->client->w != mw) || (ctx->client->h != mh))
     evas_object_resize(ctx->client->frame, mw, mh);

   return EINA_TRUE;
}

static Eina_Bool
_e_elm_win_trap_size_base_set(void *data, Evas_Object *o EINA_UNUSED, int w, int h)
{
   Elm_Win_Trap_Ctx *ctx = data;
   int mw = 0, mh = 0;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_TRUE);
   if (!ctx->client) return EINA_TRUE;
   ctx->client->icccm.base_w = w;
   ctx->client->icccm.base_h = h;
   e_client_resize_limit(ctx->client, &mw, &mh);
   mw = ctx->client->w < w ? w : ctx->client->w;
   mh = ctx->client->h < h ? h : ctx->client->h;
   if ((ctx->client->w != mw) || (ctx->client->h != mh))
     evas_object_resize(ctx->client->frame, mw, mh);

   return EINA_TRUE;
}

static Eina_Bool
_e_elm_win_trap_borderless_set(void *data, Evas_Object *o EINA_UNUSED, Eina_Bool borderless)
{
   Elm_Win_Trap_Ctx *ctx = data;
   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_TRUE);
   if (!ctx->client) return EINA_TRUE;

   borderless = !!borderless;

   ctx->client->borderless = borderless;
   EC_CHANGED(ctx->client);
   ctx->client->changes.border = 1;
   return EINA_TRUE;
}

static Eina_Bool
_e_elm_win_trap_override_set(void *data, Evas_Object *o EINA_UNUSED, Eina_Bool override)
{
   Elm_Win_Trap_Ctx *ctx = data;
   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_TRUE);

   if (ctx->client)
     CRI("Override being set too late on internal client!");
   ctx->override = !!override;
   return EINA_TRUE;
}

static const Elm_Win_Trap _e_elm_win_trap = {
   ELM_WIN_TRAP_VERSION,
   _e_elm_win_trap_add,
   _e_elm_win_trap_del,
   _e_elm_win_trap_hide,
   _e_elm_win_trap_show,
   _e_elm_win_trap_move,
   _e_elm_win_trap_resize,
   _e_elm_win_trap_center,
   _e_elm_win_trap_lower,
   _e_elm_win_trap_raise,
   _e_elm_win_trap_activate,
   /* alpha_set */ NULL,
   /* aspect_set */ NULL,
   /* avoid_damage_set */ NULL,
   _e_elm_win_trap_borderless_set,
   /* demand_attention_set */ NULL,
   /* focus_skip_set */ NULL,
   /* fullscreen_set */ NULL,
   /* iconified_set */ NULL,
   /* layer_set */ NULL,
   /* manual_render_set */ NULL,
   /* maximized_set */ NULL,
   /* modal_set */ NULL,
   /* name_class_set */ NULL,
   /* object_cursor_set */ NULL,
   _e_elm_win_trap_override_set,
   /* rotation_set */ NULL,
   /* rotation_with_resize_set */ NULL,
   /* shaped_set */ NULL,
   _e_elm_win_trap_size_base_set,
   /* size_step_set */ NULL,
   _e_elm_win_trap_size_min_set,
   _e_elm_win_trap_size_max_set,
   /* sticky_set */ NULL,
   /* title_set */ NULL,
   /* urgent_set */ NULL,
   /* withdrawn_set */ NULL
};

static void
_e_win_client_hook_new(void *d EINA_UNUSED, E_Client *ec)
{
   if (!ec->internal) return;
   if (current_win)
     ec->override = current_win->override;
}

/* externally accessible functions */
EINTERN int
e_win_init(void)
{
   if (!elm_win_trap_set(&_e_elm_win_trap)) return 0;
   e_client_hook_add(E_CLIENT_HOOK_NEW_CLIENT, _e_win_client_hook_new, NULL);
   return 1;
}

EINTERN int
e_win_shutdown(void)
{
   return 1;
}

E_API E_Client *
e_win_client_get(Evas_Object *obj)
{
   Elm_Win_Trap_Ctx *ctx;

   if (!e_obj_is_win(obj)) return NULL;
   ctx = elm_win_trap_data_get(obj);

   return ctx ? ctx->client : NULL;
}

E_API Ecore_Evas *
e_win_ee_get(Evas_Object *obj)
{
   return ecore_evas_ecore_evas_get(evas_object_evas_get(obj));
}

E_API E_Pointer *
e_win_pointer_get(Evas_Object *obj)
{
   Elm_Win_Trap_Ctx *ctx = elm_win_trap_data_get(obj);

   return ctx ? ctx->pointer : NULL;
}

E_API Eina_Bool
e_win_centered_get(Evas_Object *obj)
{
   Elm_Win_Trap_Ctx *ctx = elm_win_trap_data_get(obj);

   return ctx ? ctx->centered : EINA_FALSE;
}

E_API void
e_win_client_icon_set(Evas_Object *obj, const char *icon)
{
   Elm_Win_Trap_Ctx *ctx = elm_win_trap_data_get(obj);

   if (ctx->client)
     eina_stringshare_replace(&ctx->client->internal_icon, icon);
}

E_API void
e_win_client_icon_key_set(Evas_Object *obj, const char *key)
{
   Elm_Win_Trap_Ctx *ctx = elm_win_trap_data_get(obj);

   if (ctx->client)
     eina_stringshare_replace(&ctx->client->internal_icon_key, key);
}

E_API void
e_win_placed_set(Evas_Object *obj, Eina_Bool placed)
{
   Elm_Win_Trap_Ctx *ctx = elm_win_trap_data_get(obj);

   EINA_SAFETY_ON_NULL_RETURN(obj);
   ctx->placed = !!placed;
   if (ctx->client)
     {
        ctx->client->placed = !!placed;
        EC_CHANGED(ctx->client);
     }
}

E_API void
e_win_no_remember_set(Evas_Object *obj, Eina_Bool no_rem)
{
   Elm_Win_Trap_Ctx *ctx = elm_win_trap_data_get(obj);

   EINA_SAFETY_ON_NULL_RETURN(obj);
   ctx->internal_no_remember = !!no_rem;
   if (ctx->client)
     ctx->client->internal_no_remember = !!no_rem;
}

E_API void
e_win_no_reopen_set(Evas_Object *obj, Eina_Bool no_reopen)
{
   Elm_Win_Trap_Ctx *ctx = elm_win_trap_data_get(obj);

   EINA_SAFETY_ON_NULL_RETURN(obj);
   ctx->internal_no_reopen = !!no_reopen;
   if (ctx->client)
     ctx->client->internal_no_reopen = !!no_reopen;
}

E_API Evas_Object *
e_elm_win_add(Evas_Object *parent, const char *name, Elm_Win_Type type)
{
   char *eng;
   Evas_Object *o;

   if (type == ELM_WIN_INLINED_IMAGE) return elm_win_add(parent, name, type);
   eng = eina_strdup(getenv("ELM_ACCEL"));
   e_util_env_set("ELM_ACCEL", "none");
   o = elm_win_add(parent, name, type);
   e_util_env_set("ELM_ACCEL", eng);
   elm_win_alpha_set(o, 1);
   free(eng);
   return o;
}

E_API Evas_Object *
elm_win_util_standard_add(const char *name, const char *title)
{
   char *eng;
   Evas_Object *o;

   eng = eina_strdup(getenv("ELM_ACCEL"));
   e_util_env_set("ELM_ACCEL", "none");
   o = elm_win_util_standard_add(name, title);
   e_util_env_set("ELM_ACCEL", eng);
   elm_win_alpha_set(o, 1);
   free(eng);
   return o;
}

E_API Evas_Object *
e_elm_win_util_dialog_add(Evas_Object *parent, const char *name, const char *title)
{
   char *eng;
   Evas_Object *o;

   eng = eina_strdup(getenv("ELM_ACCEL"));
   e_util_env_set("ELM_ACCEL", "none");
   o = elm_win_util_dialog_add(parent, name, title);
   e_util_env_set("ELM_ACCEL", eng);
   elm_win_alpha_set(o, 1);
   free(eng);
   return o;
}
