/*
   This file contains routines to open an X window display and window
   This consists of a number of routines that set the various
   fields in the Window structure, which is passed to
   all of these routines.

   Note that if you use the default visual and colormap, then you
   can use these routines with any X toolkit that will give you the
   Window id of the window that it is managing.  Use that instead of the
   call to PetscDrawXiCreateWindow .  Similarly for the Display.
*/

#include <../src/sys/classes/draw/impls/x/ximpl.h>

PETSC_INTERN PetscErrorCode PetscDrawSetColormap_X(PetscDraw_X *, Colormap);

/*
  PetscDrawXiOpenDisplay - Open and setup a display
*/
static PetscErrorCode PetscDrawXiOpenDisplay(PetscDraw_X *XiWin, const char display[])
{
  PetscFunctionBegin;
  XiWin->disp = XOpenDisplay(display);
  if (!XiWin->disp) {
    SETERRQ(PETSC_COMM_SELF, PETSC_ERR_LIB, "Unable to open display on %s\n\
    Make sure your COMPUTE NODES are authorized to connect \n\
    to this X server and either your DISPLAY variable\n\
    is set or you use the -display name option\n",
            display);
  }
  XiWin->screen     = DefaultScreen(XiWin->disp);
  XiWin->vis        = DefaultVisual(XiWin->disp, XiWin->screen);
  XiWin->depth      = DefaultDepth(XiWin->disp, XiWin->screen);
  XiWin->cmap       = DefaultColormap(XiWin->disp, XiWin->screen);
  XiWin->background = WhitePixel(XiWin->disp, XiWin->screen);
  XiWin->foreground = BlackPixel(XiWin->disp, XiWin->screen);
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode PetscDrawXiClose(PetscDraw_X *XiWin)
{
  PetscFunctionBegin;
  if (!XiWin) PetscFunctionReturn(PETSC_SUCCESS);
  PetscCall(PetscFree(XiWin->font));
  if (XiWin->disp) {
#if defined(PETSC_HAVE_SETJMP_H)
    jmp_buf                 jmpbuf;
    PetscXIOErrorHandlerFn *xioerrhdl;
    PetscCall(PetscMemcpy(&jmpbuf, &PetscXIOErrorHandlerJumpBuf, sizeof(jmpbuf)));
    xioerrhdl = PetscSetXIOErrorHandler(PetscXIOErrorHandlerJump);
    if (!setjmp(PetscXIOErrorHandlerJumpBuf))
#endif
    {
      XFreeGC(XiWin->disp, XiWin->gc.set);
      XCloseDisplay(XiWin->disp);
    }
    XiWin->disp = NULL;
#if defined(PETSC_HAVE_SETJMP_H)
    (void)PetscSetXIOErrorHandler(xioerrhdl);
    PetscCall(PetscMemcpy(&PetscXIOErrorHandlerJumpBuf, &jmpbuf, sizeof(jmpbuf)));
#endif
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*
   PetscDrawXiCreateGC - setup the GC structure
*/
static PetscErrorCode PetscDrawXiCreateGC(PetscDraw_X *XiWin, PetscDrawXiPixVal fg)
{
  XGCValues gcvalues; /* window graphics context values */

  PetscFunctionBegin;
  /* Set the graphics contexts */
  /* create a gc for the ROP_SET operation (writing the fg value to a pixel) */
  /* (do this with function GXcopy; GXset will automatically write 1) */
  gcvalues.function   = GXcopy;
  gcvalues.foreground = fg;
  XiWin->gc.cur_pix   = fg;
  XiWin->gc.set       = XCreateGC(XiWin->disp, RootWindow(XiWin->disp, XiWin->screen), GCFunction | GCForeground, &gcvalues);
  PetscCheck(XiWin->gc.set, PETSC_COMM_SELF, PETSC_ERR_LIB, "Unable to create X graphics context");
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*
   PetscDrawXiInit - basic setup the draw (display, graphics context, font)
*/
PetscErrorCode PetscDrawXiInit(PetscDraw_X *XiWin, const char display[])
{
  PetscFunctionBegin;
  PetscCall(PetscDrawXiOpenDisplay(XiWin, display));
  PetscCall(PetscDrawXiCreateGC(XiWin, XiWin->foreground));
  PetscCall(PetscDrawXiFontFixed(XiWin, 6, 10, &XiWin->font));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*
    This routine waits until the window is actually created. If the window was
    never mapped it generates an error
*/
static PetscErrorCode PetscDrawXiWaitMap(PetscDraw_X *XiWin)
{
  XEvent event;

  PetscFunctionBegin;
  while (1) {
    XMaskEvent(XiWin->disp, ExposureMask | StructureNotifyMask, &event);
    if (event.xany.window != XiWin->win) break;
    else {
      switch (event.type) {
      case ConfigureNotify:
        /* window has been moved or resized */
        XiWin->w = event.xconfigure.width - 2 * event.xconfigure.border_width;
        XiWin->h = event.xconfigure.height - 2 * event.xconfigure.border_width;
        break;
      case DestroyNotify:
        SETERRQ(PETSC_COMM_SELF, PETSC_ERR_LIB, "Window was not properly created");
      case Expose:
        PetscFunctionReturn(PETSC_SUCCESS);
        /* else ignore event */
      }
    }
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*
    Actually display a window at [x,y] with sizes (w,h)
*/
static PetscErrorCode PetscDrawXiDisplayWindow(PetscDraw_X *XiWin, char *label, int x, int y, int w, int h)
{
  unsigned int         wavail, havail;
  XSizeHints           size_hints;
  XWindowAttributes    in_window_attributes;
  XSetWindowAttributes window_attributes;
  unsigned int         border_width  = 0;
  unsigned long        backgnd_pixel = WhitePixel(XiWin->disp, XiWin->screen);
  unsigned long        wmask;

  PetscFunctionBegin;
  /* get the available widths */
  wavail = DisplayWidth(XiWin->disp, XiWin->screen);
  havail = DisplayHeight(XiWin->disp, XiWin->screen);
  PetscCheck(w > 0 && h > 0, PETSC_COMM_SELF, PETSC_ERR_LIB, "X Window display has invalid height or width");
  if ((unsigned int)w > wavail) w = wavail;
  if ((unsigned int)h > havail) h = havail;

  if (x < 0) x = (int)(wavail - (unsigned int)w + (unsigned int)x);
  if (y < 0) y = (int)(havail - (unsigned int)h + (unsigned int)y);
  x = ((unsigned int)x + w > wavail) ? (int)(wavail - (unsigned int)w) : x;
  y = ((unsigned int)y + h > havail) ? (int)(havail - (unsigned int)h) : y;

  /* We need XCreateWindow since we may need an visual other than the default one */
  XGetWindowAttributes(XiWin->disp, RootWindow(XiWin->disp, XiWin->screen), &in_window_attributes);
  window_attributes.background_pixmap = None;
  window_attributes.background_pixel  = backgnd_pixel;
  /* No border for now */
  window_attributes.border_pixmap = None;
  /*
  window_attributes.border_pixel      = border_pixel;
  */
  window_attributes.bit_gravity = in_window_attributes.bit_gravity;
  window_attributes.win_gravity = in_window_attributes.win_gravity;
  /* Backing store is too slow in color systems */
  window_attributes.backing_store         = NotUseful;
  window_attributes.backing_pixel         = backgnd_pixel;
  window_attributes.save_under            = 1;
  window_attributes.event_mask            = 0;
  window_attributes.do_not_propagate_mask = 0;
  window_attributes.override_redirect     = 0;
  window_attributes.colormap              = XiWin->cmap;
  /* None for cursor does NOT mean none, it means cursor of Parent */
  window_attributes.cursor = None;

  wmask = CWBackPixmap | CWBackPixel | CWBorderPixmap | CWBitGravity | CWWinGravity | CWBackingStore | CWBackingPixel | CWOverrideRedirect | CWSaveUnder | CWEventMask | CWDontPropagate | CWCursor | CWColormap;

  XiWin->win = XCreateWindow(XiWin->disp, RootWindow(XiWin->disp, XiWin->screen), x, y, w, h, border_width, XiWin->depth, InputOutput, XiWin->vis, wmask, &window_attributes);
  PetscCheck(XiWin->win, PETSC_COMM_SELF, PETSC_ERR_LIB, "Unable to open X window");

  /* set window manager hints */
  {
    XWMHints      wm_hints;
    XClassHint    class_hints;
    XTextProperty windowname, iconname;

    if (label) XStringListToTextProperty(&label, 1, &windowname);
    else XStringListToTextProperty(&label, 0, &windowname);
    if (label) XStringListToTextProperty(&label, 1, &iconname);
    else XStringListToTextProperty(&label, 0, &iconname);

    wm_hints.initial_state = NormalState;
    wm_hints.input         = True;
    wm_hints.flags         = StateHint | InputHint;

    /* These properties can be used by window managers to decide how to display a window */
    class_hints.res_name  = (char *)"petsc";
    class_hints.res_class = (char *)"PETSc";

    size_hints.x          = x;
    size_hints.y          = y;
    size_hints.min_width  = 4 * border_width;
    size_hints.min_height = 4 * border_width;
    size_hints.width      = w;
    size_hints.height     = h;
    size_hints.flags      = USPosition | USSize | PMinSize;

    XSetWMProperties(XiWin->disp, XiWin->win, &windowname, &iconname, NULL, 0, &size_hints, &wm_hints, &class_hints);
    XFree((void *)windowname.value);
    XFree((void *)iconname.value);
  }

  /* make the window visible */
  XSelectInput(XiWin->disp, XiWin->win, ExposureMask | StructureNotifyMask);
  XMapWindow(XiWin->disp, XiWin->win);
  /* some window systems are cruel and interfere with the placement of
     windows.  We wait here for the window to be created or to die */
  PetscCall(PetscDrawXiWaitMap(XiWin));
  XSelectInput(XiWin->disp, XiWin->win, NoEventMask);
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode PetscDrawXiQuickWindow(PetscDraw_X *XiWin, char *name, int x, int y, int nx, int ny)
{
  PetscFunctionBegin;
  PetscCall(PetscDrawSetColormap_X(XiWin, (Colormap)0));
  PetscCall(PetscDrawXiDisplayWindow(XiWin, name, x, y, nx, ny));
  XSetWindowBackground(XiWin->disp, XiWin->win, XiWin->background);
  XClearWindow(XiWin->disp, XiWin->win);
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*
   A version from an already defined window
*/
PetscErrorCode PetscDrawXiQuickWindowFromWindow(PetscDraw_X *XiWin, Window win)
{
  XWindowAttributes attributes;

  PetscFunctionBegin;
  XiWin->win = win;
  XGetWindowAttributes(XiWin->disp, XiWin->win, &attributes);
  PetscCall(PetscDrawSetColormap_X(XiWin, attributes.colormap));
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode PetscDrawXiQuickPixmap(PetscDraw_X *XiWin)
{
  PetscFunctionBegin;
  if (XiWin->drw) XFreePixmap(XiWin->disp, XiWin->drw);
  XiWin->drw = XCreatePixmap(XiWin->disp, RootWindow(XiWin->disp, XiWin->screen), XiWin->w, XiWin->h, XiWin->depth);
  PetscDrawXiSetPixVal(XiWin, XiWin->background);
  XFillRectangle(XiWin->disp, XiWin->drw, XiWin->gc.set, 0, 0, XiWin->w, XiWin->h);
  XSync(XiWin->disp, False);
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode PetscDrawXiResizeWindow(PetscDraw_X *XiWin, int w, int h)
{
  XEvent event;

  PetscFunctionBegin;
  XSelectInput(XiWin->disp, XiWin->win, StructureNotifyMask);
  XResizeWindow(XiWin->disp, XiWin->win, (unsigned int)w, (unsigned int)h);
  XWindowEvent(XiWin->disp, XiWin->win, StructureNotifyMask, &event);
  XSelectInput(XiWin->disp, XiWin->win, NoEventMask);
  PetscFunctionReturn(PETSC_SUCCESS);
}

PetscErrorCode PetscDrawXiGetGeometry(PetscDraw_X *XiWin, int *x, int *y, int *w, int *h)
{
  XWindowAttributes attributes;
  Window            root, parent, child;
  int               xx = 0, yy = 0;
  unsigned int      ww = 0, hh = 0, dummy;

  PetscFunctionBegin;
  if (XiWin->win) {
    XGetGeometry(XiWin->disp, XiWin->win, &parent, &xx, &yy, &ww, &hh, &dummy, &dummy);
    root = RootWindow(XiWin->disp, XiWin->screen);
    if (!XTranslateCoordinates(XiWin->disp, XiWin->win, root, 0, 0, &xx, &yy, &child)) {
      XGetWindowAttributes(XiWin->disp, XiWin->win, &attributes);
      root = attributes.screen->root;
      (void)XTranslateCoordinates(XiWin->disp, XiWin->win, root, 0, 0, &xx, &yy, &child);
    }
  } else if (XiWin->drw) {
    XGetGeometry(XiWin->disp, XiWin->drw, &root, &xx, &yy, &ww, &hh, &dummy, &dummy);
  }
  if (x) *x = xx;
  if (y) *y = yy;
  if (w) *w = (int)ww;
  if (h) *h = (int)hh;
  PetscFunctionReturn(PETSC_SUCCESS);
}
