/**
 ** vd_xwin.c ---- the standard X Window driver
 **
 ** Author:     Ulrich Leodolter
 ** E-mail:     ulrich@lab1.psy.univie.ac.at
 ** Date:       Thu Sep 28 09:29:26 1995
 ** RCSId:      $Id: vd_xwin.c 1.2 1995/11/19 19:28:12 ulrich Exp $
 **
 ** This file is part of the GRX graphics library.
 **
 ** The GRX graphics library is free software; you can redistribute it
 ** and/or modify it under some conditions; see the "copying.grx" file
 ** for details.
 **
 ** This library is distributed in the hope that it will be useful,
 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 **
 ** Contributions by:
 ** 070505 M.Alvarez, Using a Pixmap for BackingStore
 ** 071201 M.Alvarez, Added videomodes for wide monitors
 ** 071201 M.Alvarez, The modes higher than the X resolution 
 **                   are made 'non-present'
 ** 071201 M.Alvarez, go to fullscreen if w,h == X resolution
 **                   GR_biggest_graphics is honored
 ** 170714 Use BStore only if not generated GREV_EXPOSE events
 **/

#include "libgrx.h"
#include "libxwin.h"
#include "grdriver.h"
#include "arith.h"

Display *       _XGrDisplay;
int             _XGrScreen;
Window          _XGrWindow;
Pixmap          _XGrBitmap;
Pixmap          _XGrPattern;
Pixmap          _XGrBStore;
Drawable        _XGRActDrawable;
Colormap        _XGrColormap;
GC              _XGrGC;
GC              _XGrBitmapGC;
GC              _XGrPatternGC;
GC              _XGrDefaultGC;
XIM             _XGrXim = NULL;
XIC             _XGrXic = NULL;
unsigned long   _XGrForeColor;
unsigned long   _XGrBackColor;
unsigned int    _XGrColorOper;
unsigned int    _XGrDepth;
unsigned int    _XGrBitsPerPixel;
unsigned int	_XGrScanlinePad;
unsigned int    _XGrMaxWidth;
unsigned int    _XGrMaxHeight;
int             _XGrBStoreInited = 0;
int             _XGrFullScreen = 0;
int             _XGrGenExposeEvents = GR_GEN_EXPOSE_NO;

unsigned long   _XGrColorPlanes[8];
unsigned int    _XGrColorNumPlanes;
unsigned long   _XGrColorPixels[2];
unsigned int    _XGrColorNumPixels;

char *_XGrClassNames[6] = {
    "StaticGray",
    "GrayScale",
    "StaticColor",
    "PseudoColor",
    "TrueColor",
    "DirectColor"
};

static void setbank(int bk);
static void setrwbanks(int rb, int wb);
static void loadcolor(int c, int r, int g, int b);
static int setmode(GrVideoMode *mp, int noclear);

GrVideoModeExt grtextext = {
  GR_frameText,                       /* frame driver */
  NULL,                               /* frame driver override */
  NULL,                               /* frame buffer address */
  { 0, 0, 0 },                        /* color precisions */
  { 0, 0, 0 },                        /* color component bit positions */
  0,                                  /* mode flag bits */
  setmode,                            /* mode set */
  NULL,                               /* virtual size set */
  NULL,                               /* virtual scroll */
  NULL,                               /* bank set function */
  NULL,                               /* double bank set function */
  NULL                                /* color loader */
};

static GrVideoModeExt grxwinext = {
  GR_frameUndef,                      /* frame driver */
  NULL,                               /* frame driver override */
  NULL,                               /* frame buffer address */
  { 0, 0, 0 },                        /* color precisions */
  { 0, 0, 0 },                        /* color component bit positions */
  0,                                  /* mode flag bits */
  setmode,                            /* mode set */
  NULL,                               /* virtual size set */
  NULL,                               /* virtual scroll */
  setbank,                            /* bank set function */
  setrwbanks,                         /* double bank set */
  loadcolor                           /* color loader */
};

static GrVideoMode modes[] = {
  /* pres.  bpp wdt   hgt   BIOS   scan  priv. &ext  */
  {  TRUE,  8,   80,   25,  0x00,    80, 1,    &grtextext  },
  { FALSE,  0,  320,  240,  0x00,     0, 0,    &grxwinext  },
  { FALSE,  0,  640,  480,  0x00,     0, 0,    &grxwinext  },
  { FALSE,  0,  800,  600,  0x00,     0, 0,    &grxwinext  },
  { FALSE,  0, 1024,  768,  0x00,     0, 0,    &grxwinext  },
  { FALSE,  0, 1280, 1024,  0x00,     0, 0,    &grxwinext  },
  { FALSE,  0, 1600, 1200,  0x00,     0, 0,    &grxwinext  },
  { FALSE,  0, 1440,  900,  0x00,     0, 0,    &grxwinext  },
  { FALSE,  0, 1680, 1050,  0x00,     0, 0,    &grxwinext  },
  { FALSE,  0, 1920, 1200,  0x00,     0, 0,    &grxwinext  },
  { FALSE,  0, 2560, 1600,  0x00,     0, 0,    &grxwinext  },
  { FALSE,  0, 9999, 9999,  0x00,     0, 0,    &grxwinext  }
};

static void goto_fullscreen(Display *dsp, Window win)
{
  XEvent xev;
  Atom wm_state = XInternAtom(dsp, "_NET_WM_STATE", False);
  Atom fullscreen = XInternAtom(dsp, "_NET_WM_STATE_FULLSCREEN", False);
  memset(&xev, 0, sizeof(xev));
  xev.type = ClientMessage;
  xev.xclient.window = win;
  xev.xclient.message_type = wm_state;
  xev.xclient.format = 32;
  xev.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD
  xev.xclient.data.l[1] = fullscreen;
  xev.xclient.data.l[2] = 0;
  XSendEvent(dsp, DefaultRootWindow(dsp), False,
    SubstructureNotifyMask, &xev);
}

static void returnfrom_fullscreen(Display *dsp, Window win)
{
  XEvent xev;
  Atom wm_state = XInternAtom(dsp, "_NET_WM_STATE", False);
  Atom fullscreen = XInternAtom(dsp, "_NET_WM_STATE_FULLSCREEN", False);
  memset(&xev, 0, sizeof(xev));
  xev.type = ClientMessage;
  xev.xclient.window = win;
  xev.xclient.message_type = wm_state;
  xev.xclient.format = 32;
  xev.xclient.data.l[0] = 0; // _NET_WM_STATE_REMOVE
  xev.xclient.data.l[1] = fullscreen;
  xev.xclient.data.l[2] = 0;
  XSendEvent(dsp, DefaultRootWindow(dsp), False,
    SubstructureNotifyMask, &xev);
}

static void setbank(int bk)
{}

static void setrwbanks(int rb, int wb)
{}

static void loadcolor(int c, int r, int g, int b)
{
  GRX_ENTER();
  if (_XGrDisplay != NULL
     && _XGrColormap != None
     && !(_XGrColorNumPixels == 1
     && (c < _XGrColorPixels[0] || c > _XGrColorPixels[1])) ) {
    XColor xcolor;

    DBGPRINTF(DBG_DRIVER,("Setting X11 color %d to r=%d, g=%d, b=%d\n",c,r,g,b));
    xcolor.pixel = c;
    xcolor.red   = r * 257;
    xcolor.green = g * 257;
    xcolor.blue  = b * 257;
    xcolor.flags = DoRed|DoGreen|DoBlue;
    XStoreColor(_XGrDisplay, _XGrColormap, &xcolor);
  }
  GRX_LEAVE();
}

static int setmode(GrVideoMode *mp, int noclear)
{
  char name[100], *window_name, *icon_name;
  int res;

  GRX_ENTER();
  res = FALSE;
  if (_XGrWindow != None) {

    if (_XGrFullScreen) {
      returnfrom_fullscreen(_XGrDisplay, _XGrWindow);
      _XGrFullScreen = 0;
    }
    XUnmapWindow(_XGrDisplay, _XGrWindow);
    if (_XGrBStoreInited) {
      XFreePixmap(_XGrDisplay, _XGrBStore);
      _XGrBStoreInited = 0;
    }

    if (mp->extinfo->mode != GR_frameText) {
      XSizeHints hints;

      _XGrGenExposeEvents = GR_GEN_EXPOSE_NO;
      XResizeWindow(_XGrDisplay, _XGrWindow, mp->width, mp->height);
      _XGrBStore = XCreatePixmap(_XGrDisplay, _XGrWindow, mp->width, mp->height, _XGrDepth);
      _XGrBStoreInited = 1;
      XFillRectangle(_XGrDisplay, _XGrBStore, _XGrGC, 0, 0, mp->width, mp->height);
      _XGRActDrawable = (Drawable) _XGrBStore;
      /*_XGRActDrawable = (Drawable *) &_XGrWindow;*/
      grxwinext.frame = (char *) &_XGRActDrawable;

      sprintf (name, "mgrx %dx%dx%d", mp->width, mp->height, mp->bpp);
      window_name = name;
      icon_name = name;

      hints.min_width = mp->width;
      hints.max_width = mp->width;
      hints.min_height = mp->height;
      hints.max_height = mp->height;
      hints.flags = PMaxSize | PMinSize;

      XSetStandardProperties (_XGrDisplay,        /* display */
                              _XGrWindow,         /* w */
                              window_name,        /* window_name */
                              icon_name,          /* icon_name */
                              None,               /* icon_pixmap */
                              NULL,               /* argv */
                              0,                  /* argc */
                              &hints);            /* hints */

      XMapRaised(_XGrDisplay, _XGrWindow);

      /* Wait until window appears on screen */
      {
        XEvent xevent;

        while (1) {
          XNextEvent(_XGrDisplay, &xevent);
          if (xevent.type == MapNotify)
           break;
        }
      }

      /* Go to fullscreen if w,h == X resolution */
      if ((mp->width == _XGrMaxWidth) && (mp->height == _XGrMaxHeight)) {
        goto_fullscreen(_XGrDisplay, _XGrWindow);
        _XGrFullScreen = 1;
      }
    }
    res = TRUE;
  }
  GRX_RETURN(res);
}

static int (*previous_error_handler)(Display *dpy, XErrorEvent *ev);

static int error_handler(Display *dpy, XErrorEvent *ev)
{
  char buffer[200];

  /*
   * This error is triggerd in fd_xwin.c:bltv2r() if the source rectangle
   * is not fully contained in the root window
   */
  if (ev->request_code == X_GetImage && ev->error_code == BadMatch)
    return 0;

  if (previous_error_handler)
    return (*previous_error_handler) (dpy, ev);

  XGetErrorText(dpy, ev->error_code, buffer, sizeof(buffer)),
  fprintf(stderr, "GRX: XError: %s XID=%ld request_code=%d serial=%lu\n",
          buffer, ev->resourceid, ev->request_code, ev->serial);
  return 0;
}

static GrVideoMode * _xw_selectmode( GrVideoDriver * drv, int w, int h, int bpp,
                                     int txt, unsigned int * ep )
{
  GrVideoMode *mp, *res;
  GRX_ENTER();
  if (txt) {
    res = _gr_selectmode(drv, w, h, bpp, txt, ep);
    goto done;
  }
  for (mp = &modes[1]; mp < &modes[itemsof(modes)-1]; mp++) {
    if ( mp->present && mp->width == w && mp->height == h) {
      res = _gr_selectmode (drv,w,h,bpp,txt,ep);
      goto done;
    }
  }
  /* no predefined mode found. Create a new mode */
  mp->present = TRUE;
  mp->width = (w > _XGrMaxWidth) ? _XGrMaxWidth : w;
  mp->height = (h > _XGrMaxHeight) ? _XGrMaxHeight : h;
  mp->lineoffset = (mp->width * mp->bpp) / 8;
  res = _gr_selectmode (drv,w,h,bpp,txt,ep);
done:
  GRX_RETURN(res);
}

static int detect(void)
{
  int res;
  GRX_ENTER();
  res = (getenv("DISPLAY") ? TRUE : FALSE);
  GRX_RETURN(res);
}

static int init(char *options)
{
  Window root;
  XSetWindowAttributes attr;
  unsigned long mask;
  Visual *visual;
  XPixmapFormatValues *pfmt;
  int pfmt_count;
  GrVideoMode *mp;
  unsigned int depth, bpp, pad;
  int private_colormap;
  int i, j, res;

  GRX_ENTER();
  res = FALSE;
  private_colormap = FALSE;
  _XGrDisplay = XOpenDisplay ("");
  if (_XGrDisplay == NULL)
    goto done; /* FALSE */

  previous_error_handler = XSetErrorHandler (error_handler);

  _XGrScreen = DefaultScreen(_XGrDisplay);
  _XGrDepth = depth = DefaultDepth(_XGrDisplay, _XGrScreen);
  _XGrMaxWidth = DisplayWidth(_XGrDisplay, _XGrScreen);
  _XGrMaxHeight = DisplayHeight(_XGrDisplay, _XGrScreen);

  visual = DefaultVisual(_XGrDisplay, _XGrScreen);
  root = RootWindow(_XGrDisplay, _XGrScreen);

  if (options && (strncmp ("privcmap", options, 8) == 0)) {
    private_colormap = TRUE;
  }

  _XGrColorNumPlanes = 0;
  _XGrColorNumPixels = 0;

  if (visual->class == PseudoColor && (depth == 4 || depth == 8)) {
    if (!private_colormap) {
      /*
       * Allocate at least 4 planes (= 16 colors) of writable cells
       */
      _XGrColormap = DefaultColormap(_XGrDisplay, _XGrScreen);
      for (i = depth - 1; i >= 4; i--) {
        if (XAllocColorCells(_XGrDisplay, _XGrColormap, True, /* contiguous */
                             _XGrColorPlanes, i, _XGrColorPixels, 1)) {
          _XGrColorNumPlanes = i;
          _XGrColorNumPixels = 1;
          _GR_firstFreeColor = _XGrColorPixels[0];
          _XGrColorPixels[1] = _XGrColorPixels[0];
          for (j = 0; j < _XGrColorNumPlanes; j++) {
            _XGrColorPixels[1] |= _XGrColorPlanes[j];
          }
          _GR_lastFreeColor = _XGrColorPixels[1]-1;
          break;
        }
      }
      if (_XGrColorNumPlanes == 0)
        private_colormap = TRUE;
    }
    if (private_colormap) {
      _XGrColormap = XCreateColormap(_XGrDisplay, root, visual, AllocAll);
    }
    grxwinext.cprec[0] = visual->bits_per_rgb;
    grxwinext.cprec[1] = visual->bits_per_rgb;
    grxwinext.cprec[2] = visual->bits_per_rgb;
    grxwinext.cpos[0] = 0;
    grxwinext.cpos[1] = 0;
    grxwinext.cpos[2] = 0;
    /* loadcolor (0, 0, 0, 0); */       /* load black */
  }
  else if (visual->class == TrueColor
           && (depth == 8 || depth == 15 || depth == 16 || depth == 24)) {
    int i, pos, prec;
    unsigned long mask[3];

    _XGrColormap = (Colormap)0;
    mask[0] = visual->red_mask;
    mask[1] = visual->green_mask;
    mask[2] = visual->blue_mask;
    for (i = 0; i < 3; i++) {
      for (pos = 0; !(mask[i] & 1); pos++, mask[i]>>=1);
      for (prec = 0; (mask[i] & 1); prec++, mask[i]>>=1);
      grxwinext.cprec[i] = prec;
      grxwinext.cpos[i] = pos;
    }
  }
  else {
    XCloseDisplay(_XGrDisplay);
    fprintf(stderr, "GRX init: Visual class=%s depth=%d not supported\n",
            _XGrClassNames[visual->class], depth);
    exit (1);
  }
  _XGrForeColor = GrNOCOLOR;    /* force XSetForeground */
  _XGrBackColor = GrNOCOLOR;    /* force XSetBackground */
  _XGrColorOper = C_WRITE;

  mask = 0L;
  attr.backing_store = NotUseful;
  mask |= CWBackingStore;
  if (_XGrColormap) {
    attr.colormap = _XGrColormap;
    mask |= CWColormap;
  }
  attr.background_pixel = 0L;
  mask |= CWBackPixel;
  attr.border_pixel = 0L;
  mask |= CWBorderPixel;
  attr.event_mask = (StructureNotifyMask
                     | KeyPressMask
                     | KeyReleaseMask
                     | ButtonPressMask
                     | ButtonReleaseMask
                     | ButtonMotionMask
                     | ExposureMask
                     | PointerMotionMask);
  mask |= CWEventMask;
  _XGrWindow = XCreateWindow(_XGrDisplay,
                             root,             /* parent */
                             0,                /* x */
                             0,                /* y */
                             1,                /* width */
                             1,                /* height */
                             0,                /* border_width */
                             depth,            /* depth */
                             InputOutput,      /* class */
                             visual,           /* visual */
                             mask,             /* valuemask */
                             &attr);           /* attributes */
  _XGrBitmap = XCreatePixmap(_XGrDisplay, root, 128, 128, 1);
  _XGrBitmapGC = XCreateGC(_XGrDisplay, _XGrBitmap, 0L, NULL);
  _XGrPattern = XCreatePixmap(_XGrDisplay, root, 8, 1, 1);
  _XGrPatternGC = XCreateGC(_XGrDisplay, _XGrPattern, 0L, NULL);
  _XGrDefaultGC = DefaultGC(_XGrDisplay, _XGrScreen);
  _XGrGC = XCreateGC (_XGrDisplay, _XGrWindow, 0L, NULL);

  /* is this required ?? */
  if (_XGrColormap) {
     XInstallColormap(_XGrDisplay, _XGrColormap);
     XSetWindowColormap(_XGrDisplay, _XGrWindow, _XGrColormap);
  }

  pfmt = XListPixmapFormats(_XGrDisplay, &pfmt_count);
  if (!pfmt || pfmt_count <= 0) {
    XCloseDisplay(_XGrDisplay);
    fprintf(stderr, "GRX init: cannot list pixmap formats\n");
    exit(1);
  }

  bpp = 0;
  pad = 0;
  for (i = 0; i < pfmt_count; i++) {
    if (pfmt[i].depth == depth)
      {
        bpp = pfmt[i].bits_per_pixel;
        pad = pfmt[i].scanline_pad;
        break;
      }
  }
  XFree(pfmt);
  if (!bpp) {
    XCloseDisplay(_XGrDisplay);
    fprintf(stderr, "GRX init: cannot find pixmap format\n");
    exit(1);
  }

  switch (depth) {
  case  1: grxwinext.mode = GR_frameXWIN1; break;
  case  4: grxwinext.mode = GR_frameXWIN4; break;
  case  8: grxwinext.mode = GR_frameXWIN8; break;
  case 15:
  case 16: grxwinext.mode = GR_frameXWIN16; break;
  case 24:
    switch (bpp) {
    case 24: grxwinext.mode = GR_frameXWIN24; break;
    case 32: grxwinext.mode =
               (visual->red_mask & 0xff000000) ? GR_frameXWIN32H : GR_frameXWIN32L; break;
    }
    break;
  }

  _XGrBitsPerPixel = bpp;
  _XGrScanlinePad  = pad;

  /* fixed size modes */
  for (mp = &modes[1]; mp < &modes[itemsof(modes)-1]; mp++) {
    mp->present = TRUE;
    mp->bpp = bpp;
    mp->lineoffset = (mp->width * bpp) / 8;
    if (mp->width > _XGrMaxWidth) mp->present = FALSE;
    if (mp->height > _XGrMaxHeight) mp->present = FALSE;
  }
  /* this is the variable size mode */
  mp->present = FALSE;
  mp->bpp = bpp;
  mp->lineoffset = 0;
  res = TRUE;
done:
  GRX_RETURN( res );
}

static void reset(void)
{
  GRX_ENTER();
  if (_XGrDisplay) XCloseDisplay(_XGrDisplay);
  _XGrDisplay = NULL;
  _GR_firstFreeColor =  0;
  _GR_lastFreeColor  = -1;
  GRX_LEAVE();
}

void _GrXwinEventGenExpose(int when)
{
  if (when == _XGrGenExposeEvents || _XGrWindow == None || !_XGrBStoreInited)
    return;
  
  _XGrGenExposeEvents = when;
  if (_XGrGenExposeEvents == GR_GEN_EXPOSE_NO)
    _XGRActDrawable = (Drawable) _XGrBStore;
  else
    _XGRActDrawable = (Drawable) _XGrWindow;
}

GrVideoDriver _GrVideoDriverXWIN = {
  "xwin",                               /* name */
  GR_XWIN,                              /* adapter type */
  NULL,                                 /* inherit modes from this driver */
  modes,                                /* mode table */
  itemsof(modes),                       /* # of modes */
  detect,                               /* detection routine */
  init,                                 /* initialization routine */
  reset,                                /* reset routine */
  _xw_selectmode,                       /* special mode select routine */
  GR_DRIVERF_USER_RESOLUTION            /* arbitrary resolution possible */
};
