#ifdef __linux__

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

#include "CImage.h"

struct XData
{
  Display         *display;
  Window           window;
  XImage          *image;
  char            *data;
  bool             bFreeData;
  XVisualInfo     *vinfo;
  GC               gc;

  // Initialization and clean-up func;
  XData()
  {
    display   = NULL;
    window    = 0;
    image     = NULL;
    data      = NULL;
    bFreeData = true;
    vinfo     = NULL;
    gc        = 0;
  }
  ~XData()
  {
    if (gc)
      XFreeGC(display, gc);
    if (vinfo)
      XFree(vinfo);
    if (data != NULL && bFreeData)
      free(data);
    if (image)
      XDestroyImage(image);
    if (window)
      XDestroyWindow(display,window);
    if (display)
      XCloseDisplay(display);
  }
};
Display *display;
XEvent *event;
char *arg;

static Bool WindowMatch(Display *display,
                        XEvent  *event,
                        char    *arg)
{
  XData *pdata = (XData*)arg;
  return ( display == pdata->display &&
           event->xany.window == pdata->window );
}


void CImage::ExecuteShow() const
{
  XData xdata;
  xdata.display = XOpenDisplay(NULL);
  if (!xdata.display)
    {
      std::cerr << "Couldn't open display." << std::endl;
      return;
    }

  xdata.window =
    XCreateSimpleWindow(xdata.display,                    // display
                        DefaultRootWindow(xdata.display), // parent
                        0,                                // x
                        0,                                // y
                        m_Width,                          // width
                        m_Height,                         // height
                        0,                                // border_width
                        BlackPixel(xdata.display,         // border
                                   DefaultScreen(xdata.display)),
                        BlackPixel(xdata.display,         // background
                                   DefaultScreen(xdata.display)));

  XSizeHints Hints;
  Hints.min_width    = m_Width;
  Hints.max_width    = m_Width;
  Hints.base_width   = m_Width;
  Hints.min_height   = m_Height;
  Hints.max_height   = m_Height;
  Hints.base_height  = m_Height;
  Hints.flags        = PSize | PMinSize | PMaxSize;
  XSetWMNormalHints(xdata.display, xdata.window, &Hints);

  Atom a = XInternAtom(xdata.display, "WM_DELETE_WINDOW", true);
  if (a != None)
    {
      XSetWMProtocols(xdata.display, xdata.window, &a, 1);
    }
  else
    {
      std::cerr << "Could set WM_DELETE_WINDOW properly." << std::endl;
    }


  XSelectInput(xdata.display,
               xdata.window,
               StructureNotifyMask | ExposureMask | KeyPressMask);

  int iTmp;
  xdata.vinfo = XGetVisualInfo(xdata.display, 0, NULL, &iTmp);

  int bytesperpixel;

  switch(xdata.vinfo[0].depth)
    {
    case (8):
      {
        bytesperpixel = 1;
        break;
      }
    case (16):
      {
        bytesperpixel = 2;
        break;
      }
    case (24):
      {
        bytesperpixel = 4;
        break;
      }
    default:
      {
        std::cerr << "Bad display depth." << std::endl;
        return;
      }
    }

  xdata.data = (char*) malloc(m_Width*m_Height*bytesperpixel);
  if (!xdata.data)
    {
      std::cerr << "malloc failed." << std::endl;
      return;
    }

  xdata.image = XCreateImage(xdata.display,          // display
                             xdata.vinfo[0].visual,  // visual
                             xdata.vinfo[0].depth,   // depth
                             ZPixmap,                // format
                             0,                      // offset
                             xdata.data,             // data
                             m_Width,                // width
                             m_Height,               // height
                             8,                      // bitmap_pad
                             bytesperpixel*m_Width); // bytes per line

  if (!xdata.image)
    {
      std::cerr << "Couldn't create image." << endl;
      return;
    }

  xdata.bFreeData = false; // XFreeImage releases this memory.

  for (int j = 0; j < m_Height; ++j)
    for (int i = 0; i < m_Width; ++i)
      {
        long accum = 0;
        accum += ((long)(xdata.vinfo[0].red_mask * (*this)(i,j).red()) &
                  (xdata.vinfo[0].red_mask));
        accum += ((long)(xdata.vinfo[0].green_mask * (*this)(i,j).green()) &
                  (xdata.vinfo[0].green_mask));
        accum += ((long)(xdata.vinfo[0].blue_mask * (*this)(i,j).blue()) &
                  (xdata.vinfo[0].blue_mask));

        memcpy(&xdata.data[(m_Width*j+i)*bytesperpixel],
               &accum,
               bytesperpixel);
      }

  xdata.gc = XCreateGC(xdata.display, xdata.window, 0, 0);
  if (!xdata.gc)
    {
      std::cerr << "Couldn't create graphic context." << std::endl;
      return;
    }


  XMapWindow(xdata.display, xdata.window);


  XEvent ev;

  while(true)
    {
      XIfEvent(xdata.display,    // display
               &ev,              // event returned
               WindowMatch,      // predicate function
               (char*) &xdata);  // data passed to the function
      if (ev.type == MapNotify)
        break;
    }

  while(true)
    {
      XIfEvent(xdata.display,
               &ev,
               WindowMatch,
               (char*) &xdata);
      switch (ev.type)
        {
        case (Expose):
          XPutImage(xdata.display,         // display
                    ev.xexpose.window,     // window
                    xdata.gc,              // graphic context
                    xdata.image,           // image
                    ev.xexpose.x,          // x
                    ev.xexpose.y,          // y
                    ev.xexpose.x,          // z
                    ev.xexpose.y,          // y
                    ev.xexpose.width,      // width
                    ev.xexpose.height);    // height
          break;

        case (ClientMessage): // Comes from the WM_DELETE_WINDOW
        case (KeyPress):
        case (DestroyNotify):
          return;
        default:
          break;
        }
    }
}

#endif // __linux__
