/* -------------------------------
 * vim:tabstop=4:shiftwidth=4
 * embed.c
 * Fri, 03 Sep 2004 20:38:55 +0700
 * -------------------------------
 * embedding cycle implementation
 * -------------------------------*/

#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include <assert.h>
#include <unistd.h>

#ifdef DELAY_EMBEDDING_CONFIRMATION
#include <pthread.h>
#endif

#include "embed.h"

#include "common.h"
#include "debug.h"
#include "drag.h"
#include "icons.h"
#include "settings.h"
#include "tray.h"

#include "xutils.h"

#define CALC_INNER_POS(x_, y_, ti_)                 \
  do {                                              \
    x_ = (ti_->l.icn_rect.w - ti_->l.wnd_sz.x) / 2; \
    y_ = (ti_->l.icn_rect.h - ti_->l.wnd_sz.y) / 2; \
  } while (0);

#ifdef DELAY_EMBEDDING_CONFIRMATION
void *send_delayed_confirmation(void *dummy)
{
  struct TrayIcon *ti = (struct TrayIcon *)dummy;
  Display *dpy;

  if ((dpy = XOpenDisplay(settings.display_str)) != NULL) {
    LOG_TRACE(("will now sleep for %d seconds\n", settings.confirmation_delay));
    sleep(settings.confirmation_delay);
    LOG_TRACE(("sending embedding confirmation\n"));
    x11_send_client_msg32(dpy, tray_data.tray, tray_data.tray,
        tray_data.xa_tray_opcode, 0, STALONE_TRAY_DOCK_CONFIRMED, ti->wid, 0,
        0);
    XSync(dpy, False);
    XClose(dpy);
  } else {
    DIE_IE(("failed to initialize display\n"));
  }
  pthread_exit(NULL);
}
#endif

int embedder_embed(struct TrayIcon *ti)
{
  int x, y, rc;
  XSetWindowAttributes xswa;
  /* If the icon is being embedded as hidden,
   * we just start listening for property changes
   * to track _XEMBED mapped state */
  if (!ti->is_visible) {
    XSelectInput(tray_data.dpy, ti->wid, PropertyChangeMask);
    return x11_ok();
  }
  /* 0. Start listening for events on icon window */
  XSelectInput(
      tray_data.dpy, ti->wid, StructureNotifyMask | PropertyChangeMask);
  if (!x11_ok()) RETURN_STATUS(FAILURE);
  /* 1. Calculate position of mid-parent window */
  CALC_INNER_POS(x, y, ti);
  LOG_TRACE(
      ("position of icon 0x%lx inside the tray: (%d, %d)\n", ti->wid, x, y));
  /* 2. Create mid-parent window */
  ti->mid_parent =
      XCreateSimpleWindow(tray_data.dpy, tray_data.tray, ti->l.icn_rect.x + x,
          ti->l.icn_rect.y + y, ti->l.wnd_sz.x, ti->l.wnd_sz.y, 0, 0, 0);
  /* 2.5. Setup mid-parent window properties */
  xswa.win_gravity = settings.bit_gravity;
  XChangeWindowAttributes(tray_data.dpy, ti->mid_parent, CWWinGravity, &xswa);
#ifndef DEBUG_HIGHLIGHT_MIDPARENT
  XSetWindowBackgroundPixmap(tray_data.dpy, ti->mid_parent, ParentRelative);
#else
  XSetWindowBackgroundPixmap(tray_data.dpy, ti->mid_parent, 0);
#endif
  if (!x11_ok() || ti->mid_parent == None) RETURN_STATUS(FAILURE);
  LOG_TRACE(("created mid-parent window 0x%lx\n", ti->mid_parent));
  /* 3. Embed window into mid-parent */
  switch (ti->cmode) {
  case CM_KDE:
  case CM_FDO:
    XReparentWindow(tray_data.dpy, ti->wid, ti->mid_parent, 0, 0);
    XMapRaised(tray_data.dpy, ti->wid);
    break;
  default: break;
  }
  /* 4. Show mid-parent */
  XMapWindow(tray_data.dpy, ti->mid_parent);
  /* mid-parent must be lowered so that it does not osbcure
   * scollbar windows */
  XLowerWindow(tray_data.dpy, ti->mid_parent);
  drag_install_grab(ti);
  if (!x11_ok()) RETURN_STATUS(FAILURE);
#ifndef DELAY_EMBEDDING_CONFIRMATION
  /* 5. Send message confirming embedding */
  rc = x11_send_client_msg32(tray_data.dpy, tray_data.tray, tray_data.tray,
      tray_data.xa_tray_opcode, 0, STALONE_TRAY_DOCK_CONFIRMED, ti->wid, 0, 0);
  RETURN_STATUS(rc != 0);
#else
  /* This is here for debugging purposes */
  {
    pthread_t delayed_thread;
    pthread_create(
        &delayed_thread, NULL, send_delayed_confirmation, (void *)ti);
    LOG_TRACE(("sent delayed confirmation\n"));
    RETURN_STATUS(SUCCESS);
  }
#endif
}

int embedder_unembed(struct TrayIcon *ti)
{
  if (!ti->is_embedded) return SUCCESS;
  switch (ti->cmode) {
  case CM_KDE:
  case CM_FDO:
    /* Unembed icon as described in system tray protocol */
    if (ti->is_embedded) {
      XSelectInput(tray_data.dpy, ti->wid, NoEventMask);
      XUnmapWindow(tray_data.dpy, ti->wid);
      XReparentWindow(tray_data.dpy, ti->wid, DefaultRootWindow(tray_data.dpy),
          ti->l.icn_rect.x, ti->l.icn_rect.y);
      XMapRaised(tray_data.dpy, ti->wid);
      if (!x11_ok())
        LOG_ERROR(("failed to move icon 0x%lx out of the tray\n", ti->wid));
    }
    /* Destroy mid-parent */
    if (ti->mid_parent != None) {
      XDestroyWindow(tray_data.dpy, ti->mid_parent);
      if (!x11_ok())
        LOG_ERROR(
            ("failed to destroy icon mid-parent 0x%lx\n", ti->mid_parent));
    }
    break;
  default:
    LOG_ERR_IE(("Error: the compatibility mode %d is not supported "
                "(should not happen)\n",
        ti->cmode));
    return FAILURE;
  }
  LOG_TRACE(("done unembedding 0x%lx\n", ti->wid));
  RETURN_STATUS(x11_ok()
      == 0); /* This resets error status for the generations to come (XXX) */
}

int embedder_hide(struct TrayIcon *ti)
{
  XUnmapWindow(tray_data.dpy, ti->mid_parent);
  /* We do not wany any StructureNotify events for icon window anymore */
  XSelectInput(tray_data.dpy, ti->wid, PropertyChangeMask);
  if (!x11_ok()) {
    ti->is_invalid = True;
    return FAILURE;
  } else {
    ti->is_size_set = False;
    ti->num_size_resets = 0;
    ti->is_visible = False;
    return SUCCESS;
  }
}

int embedder_show(struct TrayIcon *ti)
{
  unsigned int x, y;
  /* If the window has never been embedded,
   * perform real embedding */
  if (ti->mid_parent == None) {
    ti->is_visible = True;
    return embedder_embed(ti);
  }
  /* 0. calculate new position for mid-parent */
  CALC_INNER_POS(x, y, ti);
  /* 1. move mid-parent to new location */
  XMoveResizeWindow(tray_data.dpy, ti->mid_parent, ti->l.icn_rect.x + x,
      ti->l.icn_rect.y + y, ti->l.wnd_sz.x, ti->l.wnd_sz.y);
  /* 2. adjust icon position inside mid-parent */
  XMoveWindow(tray_data.dpy, ti->wid, 0, 0);
  /* 3. map icon ? */
  XMapRaised(tray_data.dpy, ti->wid);
  /* 4. map mid-parent */
  XMapWindow(tray_data.dpy, ti->mid_parent);
  XSelectInput(
      tray_data.dpy, ti->wid, StructureNotifyMask | PropertyChangeMask);
  if (!x11_ok()) {
    ti->is_invalid = True;
    return FAILURE;
  } else {
    ti->is_visible = True;
    return SUCCESS;
  }
}

static int update_forced = False;

static int embedder_update_window_position(struct TrayIcon *ti)
{
  int x, y;
  /* Ignore hidden icons */
  if (!ti->is_visible) return NO_MATCH;
  /* The icon currently following the cursor positions itself; the grid
   * value is for after-release snapping only. */
  if (drag_icon_is_floating(ti)) return NO_MATCH;
  /* Update only those icons that do want it (everyone if update was forced)
   */
  if (!update_forced && !ti->is_updated && !ti->is_resized && ti->is_embedded)
    return NO_MATCH;
  LOG_TRACE(("Updating position of icon 0x%lx\n", ti->wid));
  /* Recalculate icon position */
  CALC_INNER_POS(x, y, ti);
  /* Reset the flags */
  ti->is_resized = False;
  ti->is_updated = False;
  /* Move mid-parent window */
  XMoveResizeWindow(tray_data.dpy, ti->mid_parent, ti->l.icn_rect.x + x,
      ti->l.icn_rect.y + y, ti->l.wnd_sz.x, ti->l.wnd_sz.y);
  /* Sanitize icon position inside mid-parent */
  XMoveWindow(tray_data.dpy, ti->wid, 0, 0);
  /* Refresh the icon */
  embedder_refresh(ti);
  if (!x11_ok()) {
    LOG_TRACE(("failed to update position of icon 0x%lx\n", ti->wid));
    ti->is_invalid = True;
  }
  return NO_MATCH;
}

int embedder_update_positions(int forced)
{
  /* I wish C had closures =( */
  update_forced = forced;
  icon_list_forall(&embedder_update_window_position);
  return SUCCESS;
}

int embedder_refresh(struct TrayIcon *ti)
{
  if (!ti->is_visible) return NO_MATCH;
  XClearWindow(tray_data.dpy, ti->mid_parent);
  x11_refresh_window(
      tray_data.dpy, ti->wid, ti->l.wnd_sz.x, ti->l.wnd_sz.y, True);
  /* Check if the icon has survived all these manipulations */
  if (!x11_ok()) {
    LOG_TRACE(("could not refresh 0x%lx\n", ti->wid));
    ti->is_invalid = True;
  }
  return NO_MATCH;
}

/* This function defines initial icon size or
 * is used to reset size of the icon window */
int embedder_reset_size(struct TrayIcon *ti)
{
  struct Point icon_sz = {0, 0};
  int rc = FAILURE;
  /* Do not reset size for non-KDE icons with size set if icon_resizes
   * are handled */
  if (ti->is_size_set && ti->cmode != CM_KDE
      && !(settings.kludge_flags & KLUDGE_FORCE_ICONS_SIZE))
    return SUCCESS;
  /* Increase counter of size resets for given icon. If this number
   * exeeds the threshold, do nothing. This should work around the icons
   * that react badly to size changes */
  if (ti->is_size_set) ti->num_size_resets++;
  if (ti->num_size_resets > ICON_SIZE_RESETS_THRESHOLD) return SUCCESS;
  if (ti->cmode == CM_KDE) {
    icon_sz.x =
        settings.icon_size < KDE_ICON_SIZE ? settings.icon_size : KDE_ICON_SIZE;
    icon_sz.y = icon_sz.x;
  } else {
    /* If icon hints are to be respected, retrive the data */
    if (settings.kludge_flags & KLUDGE_USE_ICONS_HINTS)
      rc = x11_get_window_min_size(
          tray_data.dpy, ti->wid, &icon_sz.x, &icon_sz.y);
    /* If this has failed, or icon hinst are not respected, or minimal size
     * hints are too small, fall back to default values */
    if (!rc || !(settings.kludge_flags & KLUDGE_USE_ICONS_HINTS)
        || (settings.kludge_flags & KLUDGE_FORCE_ICONS_SIZE)
        || (icon_sz.x < settings.icon_size && icon_sz.y < settings.icon_size)) {
      icon_sz.x = settings.icon_size;
      icon_sz.y = settings.icon_size;
    }
  }
  LOG_TRACE(("proposed icon size: %dx%d\n", icon_sz.x, icon_sz.y));
  if (x11_set_window_size(tray_data.dpy, ti->wid, icon_sz.x, icon_sz.y)) {
    ti->l.wnd_sz = icon_sz;
    ti->is_size_set = True;
    return SUCCESS;
  } else {
    ti->is_invalid = True;
    return FAILURE;
  }
}
