#include "gdraw.h"

// gdraw variables which give access to the window

/** A pointer representing the display connection */
Display*  gd_display = NULL;

/** The current window */
Window    gd_window;

/** ??? */
int       gd_screen;

/** The current drawable, either the same as gd_window with a
    single-buffer or with double-buffering a backbuffer pixmap */
Drawable  gd_drawable;

/** The GraphicContext of the GC */
GC        gd_gc;

/** The GraphicContext of the Window */
GC        gd_window_gc;

int       gd_init    = 0;
XSetWindowAttributes gd_attributes;
unsigned long gd_attributemask;
int       gd_use_doublebuffer = 0;

int       gd_mouse_x = 0;
int       gd_mouse_y = 0;

pthread_t    gd_thread_id;
//struct pthread_attr gd_thread_attributes;

#define   GD_MAX_MOUSE_BUTTONS 10
int       gd_mouse_buttons[GD_MAX_MOUSE_BUTTONS] = {0};

void gd_check_init()
{
  if (!gd_init) 
    gd_no_init_error ();
}

void gd_lock_screen ()
{
  gd_lock = true;
}

void gd_unlock_screen ()
{
  gd_lock = false;
}

/** Thread which handles the incoming events */
void* gd_event_loop (void* v)
{
  XEvent event;
  while (1) 
    {
      // Avoid blocking of XNextEvent
      if (XPending (gd_display) != 0)
        {
          XNextEvent (gd_display, &event);
          switch (event.type) {
          case MotionNotify:
            //puts ("Motion event");
            //printf ("%d %d\n", event.xmotion.x, event.xmotion.y);
            gd_mouse_x = event.xmotion.x;
            gd_mouse_y = event.xmotion.y;
            break;
          case ButtonPress:
            puts ("Button press");
            printf("%d\n", event.xbutton.button);
            gd_mouse_buttons[event.xbutton.button] = 1;
            break;
          case ButtonRelease:
            puts ("Button release");
            gd_mouse_buttons[event.xbutton.button] = 0;
            break;
          case KeyPress:
            puts ("key press");
            break;
          case KeyRelease:
            puts ("key release");
            break;
          case Expose:
            if (event.xexpose.count == 0)
              gd_flip_screen ();
            break;
          default:
            //puts ("Unhandled event");
            break;
          }
        }
    }
  return NULL;
}

/** Init the conection to the X server */
void gd_init_display_connection () 
{
  if (!gd_display)
    {
      //assert(XInitThreads ());
      gd_display = XOpenDisplay(NULL);
      gd_screen  = DefaultScreen(gd_display);
    }
  else
    {
      puts ("Display already inited");
    }
}

void gd_init_drawable_gc () 
{
  XGCValues gd_gcv;
  gd_gcv.foreground = 0xFFFFFF;
  gd_gcv.background = 0x000000;
  
  gd_gc = XCreateGC(gd_display, gd_drawable, GCForeground, &gd_gcv);
  gd_window_gc = XCreateGC(gd_display, gd_window, GCForeground, &gd_gcv);
}

void gd_init_screen (int width, int height, bool doublebuffer)
{
  gd_use_doublebuffer = doublebuffer;

  gd_init_display_connection ();

  gd_attributes.background_pixel = WhitePixel(gd_display, gd_screen);
  gd_attributes.border_pixel     = WhitePixel(gd_display, gd_screen);

  gd_attributes.event_mask = 
    KeyPressMask         |
    ExposureMask         | 
    PointerMotionMask    |
    ButtonPressMask      |
    ButtonReleaseMask    |
    StructureNotifyMask;

  gd_attributemask = CWBackPixel|CWBorderPixel|CWEventMask;
  puts ("Trying CreateScreen");
  gd_window = XCreateWindow(gd_display, RootWindow(gd_display, gd_screen),
                            0,0, // position
                            width, height, 0,
                            CopyFromParent, InputOutput, CopyFromParent, gd_attributemask,&gd_attributes);

  if (gd_use_doublebuffer)
    {
      puts ("Using double buffer");
      gd_drawable = XCreatePixmap (gd_display, gd_window, width, height, 
                                   DefaultDepth(gd_display, gd_screen));
    }
  else
    {
      gd_drawable = gd_window;
    }

  XMapRaised(gd_display, gd_window);

  gd_init_drawable_gc ();
  // Init down, clear the screen for convenience
  gd_init = 1;
  pthread_create(&gd_thread_id, NULL,
                 &gd_event_loop, NULL);

  gd_clear_screen ();
}

/** Shutdown all windows and the connection to the X server */
void gd_deinit_screen ()
{
  if (gd_display)
    XCloseDisplay (gd_display);
}

void gd_clear_screen ()
{
  XSetForeground (gd_display, gd_gc, 0x000000);
  XFillRectangle (gd_display, gd_drawable, gd_gc, 0, 0, gd_get_width (), gd_get_height ());
  XSetForeground (gd_display, gd_gc, 0xFFFFFF);
  XFlush(gd_display);
}

void gd_flip_screen ()
{
  if (gd_use_doublebuffer)
    {
      //puts ("Flipping screen");
      if (1)
        {
          XCopyArea (gd_display, gd_drawable, gd_window, gd_window_gc,
                     0, 0, // source
                     gd_get_width (), gd_get_height (),
                     0, 0 // destination
                     );
          XFlush (gd_display);
        }
      else
        {
          XSetFillStyle(gd_display, gd_window_gc, FillTiled);
          XSetTile(gd_display, gd_window_gc, gd_drawable);
          XFillRectangle(gd_display, gd_window, gd_window_gc, 
                         0, 0, gd_get_width (), gd_get_height ());
          XFlush (gd_display);
        }
    }
  else
    {
      puts ("No flipscreen, since no doublebuffer");
    }
}

void gd_set_fg_color (int color)
{
  gd_check_init ();
  XSetForeground(gd_display, gd_gc, color);
}

int gd_get_mouse_x ()
{
  gd_check_init ();
  return gd_mouse_x;
}

int gd_get_mouse_y ()
{
  gd_check_init ();
  return gd_mouse_y;
}

int gd_get_mouse_button (int number)
{
  gd_check_init ();
  if (number >= GD_MAX_MOUSE_BUTTONS)
    return 0;
  else
    return gd_mouse_buttons[number];
}

void gd_set_bg_color (int color)
{
  gd_check_init ();
  XSetBackground(gd_display, gd_gc, color);
}

void gd_flush_screen ()
{
  gd_check_init ();
  XFlush (gd_display);
}

void gd_sync_screen ()
{
  gd_check_init ();
  XSync (gd_display, 0);
}

void gd_draw_rect (int x1, int y1, int width, int height, bool fill)
{
  gd_check_init ();

  if (fill)
    XDrawRectangle (gd_display, gd_drawable, gd_gc, x1, y1, width, height);
  else
    XFillRectangle (gd_display, gd_drawable, gd_gc, x1, y1, width, height);
  
  // FIXME: Optimize me
  gd_flip_screen ();
}

void gd_draw_line (int x1, int y1, int x2, int y2)
{
  gd_check_init ();

  XDrawLine (gd_display, gd_drawable, gd_gc, x1, y1, x2, y2);

  // FIXME: Optimize me
  gd_flip_screen ();
}

void gd_draw_polygon ()
{
  //XFillPolygon
}

void gd_draw_string (int x, int y, const char* str)
{
  gd_check_init ();
  XDrawString (gd_display, gd_drawable, gd_gc, x, y, str, strlen (str));
}

int gd_get_width ()
{
  XWindowAttributes attr;
  gd_check_init ();
  XGetWindowAttributes (gd_display, gd_window, &attr);
  return attr.width;
}

int gd_get_height ()
{
  XWindowAttributes attr;
  gd_check_init ();
  XGetWindowAttributes (gd_display, gd_window, &attr);
  return attr.height;
}

void gd_no_init_error ()
{
  puts ("init-screen wasn't called, bailout");
  exit (EXIT_FAILURE);
}

/* EOF */

