#include "xsimple.h"
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

XVARS xv;

static Atom wm_protocols, wm_delete_window;


static void cleanup(void)
{
   XCloseDisplay(xv.d);
}

static void sighandler(int sig)
{
   xv.sig++;
}

void xinit(char *appname, char *classname, char *winname,
       int x, int y, int width, int height, int resizable,
       char *bgcolor, char *fgcolor, long evtmask)
{
   unsigned long bgc, fgc;
   XClassHint *ch;
   XSizeHints *sh;

   if (! (xv.d = XOpenDisplay(NULL))) {
       fprintf(stderr, "xinit(): Error opening display\n");
       exit(1);
   }
   atexit(cleanup);

   xv.s = DefaultScreen(xv.d);
   xv.v = DefaultVisual(xv.d, xv.s);
   xv.depth = DefaultDepth(xv.d, xv.s);
   xv.cmap = DefaultColormap(xv.d, xv.s);
   xv.x = x; xv.y = y;
   xv.dwidth = DisplayWidth(xv.d, xv.s);
   xv.dheight = DisplayHeight(xv.d, xv.s);
   xv.width = (width == 0 ? xv.dwidth : width);
   xv.height = (height == 0 ? xv.dheight : height);
   xv.bwidth = xv.twidth = 0;

   if ((xv.v->class != TrueColor) || (xv.depth != 16 && xv.depth != 24)) {
       fprintf(stderr, "xinit(): Unsupported visual\n");
       exit(1);
   }

   xv.sig = 0;
   signal(SIGHUP, sighandler);
   signal(SIGINT, sighandler);
   signal(SIGTERM, sighandler);

   initcolors();

   bgc = getnamedcolor(bgcolor, NULL); fgc = getnamedcolor(fgcolor, NULL);

   xv.w = XCreateSimpleWindow(xv.d, DefaultRootWindow(xv.d), xv.x, xv.y,
           xv.width, xv.height, 0, fgc, bgc);

   XSelectInput(xv.d, xv.w, StructureNotifyMask | ExposureMask |
           KeyPressMask | KeyReleaseMask | evtmask);

   xv.pmap = mkpixmap(resizable ? xv.dwidth : xv.width,
           resizable ? xv.dheight : xv.height, xv.depth);
   xv.gc = XCreateGC(xv.d, xv.pmap, 0, NULL);

   ch = XAllocClassHint();
   ch->res_name = appname;
   ch->res_class = classname;
   XSetClassHint(xv.d, xv.w, ch);
   settitle(winname);

   sh = XAllocSizeHints();
   sh->flags = USPosition | USSize | PMaxSize | (resizable ? 0 : PMinSize);
   sh->x = xv.x; sh->y = xv.y; sh->width = xv.width; sh->height = xv.height;
   if (resizable) {
       sh->max_width = xv.dwidth; sh->max_height = xv.dheight;
   } else {
       sh->min_width = sh->max_width = xv.width;
       sh->min_height = sh->max_height = xv.height;
   }
   XSetWMNormalHints(xv.d, xv.w, sh);

   wm_protocols = XInternAtom(xv.d, "WM_PROTOCOLS", False);
   wm_delete_window = XInternAtom(xv.d, "WM_DELETE_WINDOW", False);
   XSetWMProtocols(xv.d, xv.w, &wm_delete_window, 1);

   XMapWindow(xv.d, xv.w);
}

int getevent(XEvent *event)
{
   XEvent e;

   if (xv.sig) exit(0);

   if (! XPending(xv.d))
       return 0;
   XNextEvent(xv.d, &e);

   switch (e.type) {
       case ReparentNotify:
           {
               Window rootw, parentw = xv.w, currentw, child, *children;
               int nchildren;
               int x1, y1, x2, y2;
               XTranslateCoordinates(xv.d, xv.w, DefaultRootWindow(xv.d),
                       0, 0, &x1, &y1, &child);
               do {
                   currentw = parentw;
                   XQueryTree(xv.d, currentw, &rootw, &parentw, &children,
                           &nchildren);
                   XFree(children);
               } while (parentw != rootw);
               XTranslateCoordinates(xv.d, currentw, DefaultRootWindow(xv.d),
                       0, 0, &x2, &y2, &child);
               xv.bwidth = x1 - x2; xv.twidth = y1 - y2;
           }
           break;
       case ConfigureNotify:
           {
               XConfigureEvent *ce = (XConfigureEvent *)&e;
               Window child;
               XTranslateCoordinates(xv.d, xv.w, DefaultRootWindow(xv.d),
                       0, 0, &xv.x, &xv.y, &child);
               xv.width = ce->width; xv.height = ce->height;
           }
           break;
       case ClientMessage:
           if (e.xclient.message_type == wm_protocols &&
                   e.xclient.data.l[0] == wm_delete_window)
               exit(0);
           break;
   }

   *event = e;
   return 1;
}

void doexpose(XEvent *event)
{
   for (;;) {
       XExposeEvent *ee = (XExposeEvent *)event;
       update(ee->x, ee->y, ee->width, ee->height);
       if (! ee->count)
           break;
       XNextEvent(xv.d, event);
   }
}

void update(int x, int y, int width, int height)
{
   if (x == 0 && y == 0 && width == 0 && height == 0) {
       width = xv.width;
       height = xv.height;
   }
   XSync(xv.d, False);
   XCopyArea(xv.d, xv.pmap, xv.w, xv.gc, x, y, width, height, x, y);
   XSync(xv.d, False);
}