/**
 *
 * ChoX11: XLib replacement for RISC OS
 *
 * Event handling and generation
 *
 * Copyright 2003 by Peter Naulls
 * Written by Peter Naulls and Chris Williams
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation. No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or
 * implied warranty.
 *
 */

#define XLIB_ILLEGAL_ACCESS
#define NeedFunctionPrototypes 1
#define NeedNestedPrototypes   1

#include <X11/Xlib.h> 

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <ctype.h>
#include <time.h>

#include "Desklib:Time.h"
#include "Desklib:Kbd.h"
#include "Desklib:Coord.h"

#include "chox11.h"

/* Event doubly linked list */
static XEventList *eventfirst = NULL, *eventlast = NULL;
/* Serial count for events. */
static int event_serial = -1;
/* Last event window, for determining when to change serial */
static int event_window = 0;

/* Number of events in list */
static int event_count = 0;

static unsigned int mouse_state = 0;

/* Whether it's valid to check for the pointer moving across one of the app's windows */
static Bool check_for_pointer = False;

/* Last positions of the mouse, for tracking mouse movements */
static int Chox11_mouseX, Chox11_mouseY;

/* Current window with mouse */
static XDrawable *Chox11_mouseCurrent = &Chox11_root_window;

/* Current Window with keyboard focus */
static XDrawable *Chox11_focusCurrent = &Chox11_root_window;

/* Grabbed data */
static Bool grabbed = False;
static Window grab_window;
static int grab_mask;
static int grab_pointer_mode;

/* Null event handlers mask */
static int null_handler_mask = 0;

/* Event types that are propagated.  Also events that are affected by
   XGrabPointer */
#define PropagateMask (KeyPressMask | KeyReleaseMask | \
                       ButtonPressMask | ButtonReleaseMask | \
                       EnterWindowMask | LeaveWindowMask | \
                       PointerMotionMask | PointerMotionHintMask | \
                       Button1MotionMask | Button2MotionMask | Button3MotionMask | \
                       Button4MotionMask | Button5MotionMask | ButtonMotionMask) 

/**
 * Determine if the event type is represented in the specified mask.
 *
 * Return true if it does. The switch is obviously missing entries
 */
static Bool Chox11_Mask(int event, int mask) {
  switch (event) {
    case KeyPress:
      return mask & KeyPressMask;

    case KeyRelease:
      return mask & KeyReleaseMask;

    case ButtonPress:
      return mask & ButtonPressMask;

    case ButtonRelease:
      return mask & ButtonReleaseMask;

    case Expose:
      /* Expose isn't maskable */
      /* I think in fact that Expose events are delivered when the CWEventMask for a
         window isn't set.  Otherwise if it is, when the ExposureMask is set */
      /*return True;*/
      return mask & ExposureMask;

    case MotionNotify:
//      printf("motion: %x\n", mask & PointerMotionMask);
//      return mask & PointerMotionMask;
      return True;

    case MapNotify:
    case CreateNotify:
//      printf("Checking Create/MapNotify\n");
      /* May be based upon parent window, so don't decide here */
      return True;

    case VisibilityNotify:
      return mask & VisibilityChangeMask;

    case EnterNotify:
      return mask & EnterWindowMask;

    case LeaveNotify:
      return mask & LeaveWindowMask;

    case ConfigureNotify:
      return mask & (StructureNotifyMask | SubstructureNotifyMask);
  }

  return False;
}


/**
 * Determine if the pointer is over one of our windows
 *
 * Return true if it is.
 */
Bool Chox11_IsPointerOverWindow(void) {
  mouse_block pointer;

  Wimp_GetPointerInfo(&pointer);

  if (Chox11_FindWindow(pointer.window))
    return True;
  else
    return False;
}


/**
 * Call Wimp_Poll to see if there are any wimp events waiting to be processed
 */
void Chox11_CheckPoll(void) {
  /* Add null handler before wimp poll call so it returns immediately */
  Chox11_AddNullHandler(CHECK_EVENTS_NULL_HANDLER);
  Chox11_CallWimpPoll();
  Chox11_RemoveNullHandler(CHECK_EVENTS_NULL_HANDLER);
}

/**
 * Allocate a new event and add it to the event list.
 *
 * A new event is allocated from memory, and the generic entries are
 * filled in.  Typically, the returned event has further specific fields
 * filled in by the caller.   The function returns NULL if the
 * mask for the event is not set in the window for the event.
 *
 * TODO: Realistically, there needs to be a limit on the number of
 * events in the queue.  e.g. if it contains 20, throw away the 5 oldest.
 */
static XEvent *Chox11_AddEvent(int type, Display *display, Window w) {
  XEventList *event;
  XDrawable *window = (XDrawable *)w;
  int mask;

  assert(window);

  mask = window->data.window.event_mask;

  if (grabbed)
    printf("add event while grabbed 1\n");

  while (!Chox11_Mask(type, mask) && !(grabbed && grab_window == w && Chox11_Mask(type, grab_mask))) {
    if (!(mask & PropagateMask) || !window->data.window.no_propagate)
      return NULL;

    if (!(window = window->data.window.parent.draw))
      return NULL;

    mask = window->data.window.event_mask;
  }

  w = (Window)window;

  if (grabbed)
    printf("add event while grabbed 2\n");

  event = calloc(sizeof(XEventList), 1);

  /* TODO: Something on allocate failure */

  /* Add to linked list of events */
  if (eventfirst == NULL) {
    eventfirst = eventlast = event;
  } else {
    eventlast->next = event;
    event->prev = eventlast;
    eventlast = event;
  }

  if (w != event_window) {
    event_window = w;
    event_serial++;
  }

  event->event.xany.type    = type;
  event->event.xany.serial  = event_serial;
  event->event.xany.display = display;
  event->event.xany.window  = w;

  event_count++;

  /* Check that if we're grabbing pointer events, and it is a pointer
     event in a the grabbed window. */
  if (grabbed && grab_window == w && Chox11_Mask(type, PropagateMask)) {
    event->grabbed_event = True;
    printf("set grabbed event\n");
  }

  return &event->event;
}


void Chox11_EventWindowUnset(void) {
  event_window = 0;
}


/**
 * Remove the first event from the list.
 *
 * The caller will copy the contents of the event returned into the
 * structure passed in from the client application to the event function
 * calling Chox11_RemoveEvent.  This function should then call free()
 * on the event.
 *
 * If there are no events, NULL is returned.
 */
static XEventList *Chox11_RemoveEvent(void) {
  XEventList *event;
  event = eventfirst; 

  if (event != NULL) {
    event_count--;
    printf("remove event of type %d\n", event->event.xany.type);
    if ((eventfirst = event->next) != NULL) {
      /* Fix up linked list */
      event->next->prev = NULL;
    } else {
      /* List is now empty */
      eventlast = NULL;
    }
  }

  return event;
}

/**
 * Remove given event from the list and put into event_return
 */
static void Chox11_RemoveEventReturn(XEventList *event, XEvent *event_return) {
  memcpy(event_return, &event->event, sizeof(XEvent));

  if (event == eventfirst)
    eventfirst = event->next;
  else
    event->prev->next = event->next;

  if (event == eventlast)
    eventlast = event->prev;
  else
    event->next->prev = event->prev;  

  event_count--;
  free(event);
}



/**
 * Create an exposure event and add to the event list.
 *
 * Visibility events correspond to areas to redraw from the Wimp,
 * but with coordinates translated.
 */
static void Chox11_CreateExposeEvent(Display *display, Window window,
                                int x, int y, int width, int height,
                                int count) {
  XEvent *event = Chox11_AddEvent(Expose, display, window);

  if (event) {
    event->xexpose.x      = x;
    event->xexpose.y      = y;
    event->xexpose.width  = width;
    event->xexpose.height = height;
    event->xexpose.count  = count;
  }
}


/**
 * Create a visibility event and add to the event list.
 *
 * These events are either fully obscured, partially obscrued, or fully
 * visible.  Partially obscured isn't that easy to determine.  During a
 * redraw, if only one redraw loop was taken and the coordinates are for
 * the entire window, then the window is fully visible.
 */
void Chox11_CreateVisibilityEvent(Display *display, Window window,
                                         int state) {
  XEvent *event = Chox11_AddEvent(VisibilityNotify, display, window);

  if (event) {
    event->xvisibility.state = state;
  }
}
  

XEvent *Chox11_CreateMapNotifyEvent(Display *display, XDrawable *window, int type) {
  printf("Create/Map Notify: %x %x %d\n",
          window->data.window.event_mask,
          window->data.window.parent.draw->data.window.event_mask,
          type);           

  if ((window->data.window.event_mask & StructureNotifyMask) ||
      (window->data.window.parent.draw->data.window.event_mask & SubstructureNotifyMask)) {
    XEvent *event = Chox11_AddEvent(type, display, (Window)window);

    if (event) {
      printf("Sent create/map notify event\n");

      if (!(window->data.window.event_mask & StructureNotifyMask)) {
        event->xany.window = window->data.window.parent.ref;
      }

      return event;
    }
    /* TODO: override_redirect */
  }
  return NULL;
}

/**
 * Generates configure notify event if this window has a StructureNotifyMask
 * or one of it's ancestors has the SubstructureNotifyMask
 */
void Chox11_CreateConfigureNotifyEvent(Display *display, XDrawable *window) {
  XDrawable *event_window = NULL;
  if (window->data.window.event_mask & StructureNotifyMask) {
    event_window = window;;
  } else {
    event_window = window->data.window.parent.draw;
    while (event_window != &Chox11_root_window && !(event_window->data.window.event_mask & SubstructureNotifyMask)) {
      event_window = event_window->data.window.parent.draw;
    }
    if (event_window == &Chox11_root_window) event_window = NULL;
  }

  if (event_window) {
    XEvent *event = Chox11_AddEvent(ConfigureNotify, display, (Window)event_window);

    if (event) {
      event->xconfigure.window            = (Window)window;
      event->xconfigure.event             = (Window)event_window;
      event->xconfigure.x                 = window->data.window.x;
      event->xconfigure.y                 = window->data.window.y;
      event->xconfigure.width             = window->width;
      event->xconfigure.height            = window->height;
      event->xconfigure.border_width      = window->data.window.border_width / 2;
      event->xconfigure.above             = None; /* TODO */
      event->xconfigure.override_redirect = window->data.window.override;
//      printf("Configure Notify: %p %p %d,%d %dx%d %d\n",
//             event->xconfigure.window, event->xconfigure.event,
//             event->xconfigure.x, event->xconfigure.y,
//             event->xconfigure.width, event->xconfigure.height,
//             event->xconfigure.border_width
//             );
    }
  }
}

/**
 * Create a button event and add to the event list.
 */
static XEvent *Chox11_CreateButtonEvent(Display *display, XDrawable *window,
                                       Bool pressed, int x, int y) {
  XEvent *event = Chox11_AddEvent(pressed ? ButtonPress : ButtonRelease,
                             display, (Window)window);

  printf("button event: %p %d %x\n", window, pressed, window->data.window.event_mask);

  if (event) {
    event->xbutton.root         = window->data.window.parent.ref;
    event->xbutton.window       = (Window)window;
    event->xbutton.subwindow    = None;

    event->xbutton.time        = Time_Monotonic() * 10;

    event->xbutton.x           = x;
    event->xbutton.y           = y;

    event->xbutton.same_screen = True;
  }

  return event;
}


/**
 * Translate from RISC OS mouse/modifier states to X Windows
 */
static unsigned int Chox11_GetXState(mouse_block *mouse) {
 return (mouse->button.data.select ? Button1Mask : 0) |
        (mouse->button.data.menu   ? Button2Mask : 0) |
        (mouse->button.data.adjust ? Button3Mask : 0) |
        (Kbd_KeyDown(inkey_SHIFT)  ? ShiftMask   : 0) |
        (Kbd_KeyDown(inkey_CTRL)   ? ControlMask : 0);
}


static void Chox11_ButtonEvent(mouse_block *mouse, Bool pressed) {
  XDrawable *window = Chox11_FindWindow(mouse->window);
  XEvent *xevent;
  int x, y;
  window_info info;

  if (window) {
    int bw = window->data.window.border_width;

    Window_GetInfo(mouse->window, &info);
  
    x = (mouse->pos.x -
         info.block.screenrect.min.x +
         info.block.scroll.x - bw) / 2;
  
    y = (info.block.screenrect.max.y -
         info.block.scroll.y -
         mouse->pos.y - bw) / 2;

    printf("Chox11_ButtonEvent: in window %x, (x,y)=(%i,%i)\n",
           mouse->window, x, y);
    puts(pressed ? "pressed" : "released");

    xevent = Chox11_CreateButtonEvent(Chox11_display, window, pressed, x, y);

    if (xevent) {
      int buttons;

      // State is set to value before the event so we need to adjust
      // it to take into account the button that generated this event
      xevent->xbutton.state  = Chox11_GetXState(mouse);

      if (mouse->button.data.select) {
        buttons = Button1;
        if (pressed)
          xevent->xbutton.state &= ~Button1Mask;
        else
          xevent->xbutton.state |= Button1Mask;
        
      } else if (mouse->button.data.menu) {
        buttons = Button2;
        if (pressed)
          xevent->xbutton.state &= ~Button2Mask;
        else
          xevent->xbutton.state |= Button2Mask;
        
      } else if (mouse->button.data.adjust) {
        buttons = Button3;
        if (pressed)
          xevent->xbutton.state &= ~Button3Mask;
        else
          xevent->xbutton.state |= Button3Mask;
        
      } else {
        buttons = 0;

      }

      xevent->xbutton.button = buttons;
  
      xevent->xbutton.x_root = mouse->pos.x / 2;
      xevent->xbutton.y_root = (screen_size.y - mouse->pos.y) / 2;
    }
  }
}


extern int XGrabPointer(

    Display*		 display,
    Window		 gw,
    Bool		 owner_events,
    unsigned int	 event_mask,
    int			 pointer_mode,
    int			 keyboard_mode,
    Window		 confine_to,
    Cursor		 cursor,
    Time		 time

) {
  puts("XGrabPointer");

  grabbed           = True;
  grab_window       = gw;
  grab_mask         = event_mask;
  grab_pointer_mode = pointer_mode;

  puts("confine_to, cursor, time  not implemented");

  return GrabSuccess;
}


extern XUngrabPointer(

    Display*		 display,
    Time		 time
) {
  puts("XUngrabPointer");

  grabbed = False;
}


/**
 * Create a crossing event and add to the event list.
 */
static XEvent *Chox11_CreateCrossingEvent(XDrawable *window, Bool isEnter,
                                          mouse_block *mouse, int x, int y) {
  XEvent *event = Chox11_AddEvent(isEnter ? EnterNotify : LeaveNotify,
                                  Chox11_display, (Window)window);

  if (event) {
    event->xcrossing.time   = Time_Monotonic() * 10;
    event->xcrossing.x      = x;
    event->xcrossing.y      = y;
    event->xcrossing.x_root = mouse->pos.x / 2;
    event->xcrossing.y_root = (screen_size.y - mouse->pos.y) / 2;
    event->xcrossing.mode   = NotifyNormal; /* TODO - other modes */
    event->xcrossing.detail = 0;            /* TODO - Notify type */
    event->xcrossing.same_screen = True;
    event->xcrossing.focus  = False;        /* TODO - check focus */
    event->xcrossing.state  = Chox11_GetXState(mouse);
  }

  return event;
} 


extern XTimeCoord *XGetMotionEvents(

    Display*		 display,
    Window		 w,
    Time		 start,
    Time		 stop,
    int*		 nevents_return

) {
  puts("XGetMotionEvents: not implemented");
  return NULL;
}


static void Chox11_CheckMouse() {
  int windows;
  mouse_block pointer;

  Wimp_GetPointerInfo(&pointer);

  /* Check for mouse up */
  if (mouse_state && mouse_state != pointer.button.value) {
    unsigned int new_state = pointer.button.value;

    pointer.button.value = mouse_state;
    Chox11_ButtonEvent(&pointer, False);
    mouse_state = new_state;
  }

  /* Check if the mouse has changed position */
  if (pointer.pos.x != Chox11_mouseX || pointer.pos.y != Chox11_mouseY) {

    /* Check all windows for MotionNotify */
    for (windows = 0; windows < Chox11_drawable_num; windows++) {
      XDrawable *window = Chox11_drawable_list[windows];

      if (window->isWindow && pointer.window == window->data.window.handle) {
        window_info info;
        XEvent *event = Chox11_AddEvent(MotionNotify, Chox11_display,
                                        (Window)window);

        if (event) {
          int bw = window->data.window.border_width;

          Window_GetInfo(pointer.window, &info);

          event->xmotion.root        = window->data.window.parent.ref;
          event->xmotion.window      = (Window)window;
          event->xmotion.subwindow   = None;

          event->xmotion.time        = Time_Monotonic() * 10;

          event->xmotion.state       = Chox11_GetXState(&pointer);

          bw = window->data.window.border_width;

          event->xmotion.x           = (pointer.pos.x +
                                        info.block.scroll.x -
                                        info.block.screenrect.min.x - bw) / 2;
          event->xmotion.y           = (info.block.screenrect.max.y -
                                        info.block.scroll.y - 
                                        pointer.pos.y - bw) / 2;

          event->xmotion.x_root      = pointer.pos.x / 2;
          event->xmotion.y_root      = (screen_size.y - pointer.pos.y) / 2;

          event->xmotion.same_screen = True;
        }


        if (window != Chox11_mouseCurrent) {
          int bw = window->data.window.border_width;

          printf("Crossing event leave: %p\n", Chox11_mouseCurrent);

          /* Change of window */
          Chox11_CreateCrossingEvent(Chox11_mouseCurrent,
                                      False, &pointer, 0, 0);

          if (!event)
            Window_GetInfo(pointer.window, &info);

          Chox11_mouseCurrent = Chox11_FindWindow(pointer.window);
          if (Chox11_mouseCurrent == NULL)
            Chox11_mouseCurrent = &Chox11_root_window; 

         printf("Crossing event enter: %p\n", window);
          Chox11_CreateCrossingEvent(Chox11_mouseCurrent,
                                     True, &pointer,
                                     (pointer.pos.x - info.block.screenrect.min.x +
                                      info.block.scroll.x - bw) / 2,
                                     (info.block.screenrect.max.y -
                                      info.block.scroll.y -
                                      pointer.pos.y - bw) / 2);

        }

        break;

      }
    }
    Chox11_mouseX = pointer.pos.x;
    Chox11_mouseY = pointer.pos.y;
  }
}


static BOOL Chox11_NullPoll(event_pollblock *event, void *reference) {
  if (null_handler_mask & MOUSE_NULL_HANDLER) Chox11_CheckMouse();
  if (null_handler_mask & KEY_RELEASE_NULL_HANDLER) Chox11Key_CheckKeyRelease();
  return TRUE;
}


void Chox11_AddNullHandler(int type) {
  if (null_handler_mask == 0) {
    Event_Claim(event_NULL, event_ANY, event_ANY, Chox11_NullPoll, NULL);
  }
  null_handler_mask |= type;
}


void Chox11_RemoveNullHandler(int type) {
  null_handler_mask &= ~type;
  if (null_handler_mask == 0) {
    Event_Release(event_NULL, event_ANY, event_ANY, Chox11_NullPoll, NULL);
  }
}


extern Bool XQueryPointer(

    Display*		 display,
    Window		 w,
    Window*		 root_return,
    Window*		 child_return,
    int*		 root_x_return,
    int*		 root_y_return,
    int*		 win_x_return,
    int*		 win_y_return,
    unsigned int*        mask_return

) {
  mouse_block pointer;
  XDrawable *window;

  Chox11_CheckPoll();

  Wimp_GetPointerInfo(&pointer);

  window = Chox11_FindWindow(pointer.window);

  *root_return = (Drawable)&Chox11_root_window;
  *child_return = (Drawable)window;

  *root_x_return = pointer.pos.x / 2;
  *root_y_return = (screen_size.y - pointer.pos.y) / 2;

  if (w != *root_return && Chox11Debug_CheckWindow((Drawable)w)) {
    int bw;
    window_info info;

    window = (XDrawable *)w;

    bw = window->data.window.border_width;
    Window_GetInfo(pointer.window, &info);

    *win_x_return = (pointer.pos.x -
                     info.block.screenrect.min.x +
                     info.block.scroll.x - bw) / 2;

    *win_y_return = (info.block.screenrect.max.y -
                     info.block.scroll.y -
                     pointer.pos.y - bw) / 2;

  } else {
    *win_x_return = *root_x_return;
    *win_y_return = *root_y_return;

  }

  *mask_return = (pointer.button.data.select ? Button1Mask : 0) |
                 (pointer.button.data.adjust ? Button2Mask : 0) |
                 (pointer.button.data.menu   ? Button3Mask : 0);

  return True;
}


/**
 * Desklib handler for redraw
 *
 * Redraw border and queue Expose events 
 */
static BOOL Chox11_Redraw(event_pollblock *event, void *reference) {
  window_redrawblock redraw;
  XDrawable *window;
  BOOL more;
  int x0, y0, x1, y1, bw, width, height;
  int bc;
  XEventList *eventlist;
  int event_count = 0;

  printf("Chox11_Redraw: redrawing window handle 0x%X\n",
         (int)event->data.openblock.window); fflush(stdout); 
  window = Chox11_FindWindow(event->data.openblock.window);

  printf("window = %p\n", window);

  printf("choX11 drawable %p\n", window);

  redraw.window = event->data.openblock.window;
  Wimp_RedrawWindow(&redraw, &more);
  
  x0 = redraw.rect.min.x - redraw.scroll.x;
  y0 = redraw.rect.max.y - redraw.scroll.y;
  x1 = redraw.rect.max.x - redraw.scroll.x;
  y1 = redraw.rect.min.y - redraw.scroll.y;
  bw = window ? window->data.window.border_width : 0;
  bc = window ? window->data.window.border : 0;


  /* Set to edge of X window */
  x0 += bw;
  y0 -= bw;

  printf("Chox11_Redraw: x0,y0,x1,y1=%i,%i,%i,%i bw=%i (%d,%d)\n",
         x0, y0, x1, y1, bw, x1 - x0, y0 - y1);

  while (more) {
    width = redraw.cliprect.max.x - redraw.cliprect.min.x;
    height = redraw.cliprect.max.y - redraw.cliprect.min.y;

    if (bw) {
      Chox11_SetGCOL(bc, 0);

      /* Left border */
      GFX_Move(x0 - bw, y0 + bw - 2);
      GFX_Plot(plot_RECTANGLEFILL + plot_DRAWABSFORE, x0 - 2, y1);
      /* Bottom border */
      GFX_Plot(plot_RECTANGLEFILL + plot_DRAWABSFORE, x1 - 2, y1 + bw - 2);
      /* Right border */
      GFX_Plot(plot_RECTANGLEFILL + plot_DRAWABSFORE, x1 - bw, y0 + bw - 2);
      /* Top border */
      GFX_Plot(plot_RECTANGLEFILL + plot_DRAWABSFORE, x0 - 2, y0);

      /* Clip redraw area to X window */
      if (x0 > redraw.cliprect.min.x)
        redraw.cliprect.min.x = x0;

      if (y0 < redraw.cliprect.max.y + 2)
        redraw.cliprect.max.y = y0 - 2;

      if (x1 < redraw.cliprect.max.x + bw + 2)
        redraw.cliprect.max.x = x1 - bw - 2;

      if (y1 > redraw.cliprect.min.y - bw)
        redraw.cliprect.min.y = y1 + bw;

      width = redraw.cliprect.max.x - redraw.cliprect.min.x;
      height = redraw.cliprect.max.y - redraw.cliprect.min.y;

    }

    if (width >= 0 && height >= 0) {
      Chox11_SetGCOL(window ? window->data.window.background : 0, 0);
      GFX_RectangleFill(redraw.cliprect.min.x,
                        redraw.cliprect.min.y,
                        width, height);

      if (window)
        Chox11_CreateExposeEvent(Chox11_display, (Window)window,
                            (redraw.cliprect.min.x - x0) / 2,
                            (y0 - redraw.cliprect.max.y) / 2,
                            width / 2,
                            height / 2,
                            0);

    }
    Wimp_GetRectangle(&redraw, &more);
  }


  /* Indicate the number of Expose events left after a given one */
  eventlist = eventfirst;

  while (window && eventlist && eventlist->event.xany.type == Expose &&
                                eventlist->event.xexpose.window == (Window)window) {
    eventlist->event.xexpose.count = event_count++;
    eventlist = eventlist->prev;
  }

  return TRUE;
}


/**
 * Close window handler.
 * For a top level window, if the application supports the delete window protocol
 * send the ClientMessage event to the window otherwise just exit the application. 
 */
static BOOL Chox11_Quit(event_pollblock *event, void *reference) {
  XDrawable *window = Chox11_FindWindow((window_handle)event->data.words[0]);
  if (window && window->data.window.parent.draw == &Chox11_root_window) {
    Window w = (Window)window;
    Atom *protocols;
    int num_protocols;
    Bool quit_app = True;

    if (XGetWMProtocols(Chox11_display,  w, &protocols, &num_protocols)  != 0) {
      int protocol;
      char *name;

      for (protocol = 0; protocol < num_protocols; protocol++) {
        name = XGetAtomName(Chox11_display, protocols[protocol]);
        if (strcmp(name, "WM_DELETE_WINDOW") == 0) {
          XEvent *event = (XEvent *)calloc(1, sizeof(XEvent));
          event->type = ClientMessage;
          event->xclient.message_type = XInternAtom(Chox11_display, "WM_PROTOCOLS", False);
          event->xclient.format = 32;
          event->xclient.data.l[0] = protocols[protocol];
          event->xclient.data.l[1] = Time_Monotonic() * 10;
	      XSendEvent(Chox11_display, w, False, NoEventMask, event);
          free(event);
          XFree(name);
          quit_app = False;
          break;
        }
        XFree(name);
      }
      XFree(protocols);
    }

    if (quit_app) exit(0);
  }

  return TRUE;
}

static XDrawable *Chox11_GetFocusWindow(XDrawable *event_window) {
  XDrawable *focus_window = event_window;
  if (event_window) {
    while (event_window && event_window != &Chox11_root_window) {
      if (event_window->data.window.event_mask & (KeyPressMask | KeyReleaseMask)) {
        focus_window = event_window;
        event_window = NULL;
      } else
        event_window = event_window->data.window.parent.draw;
    }
  }
  return focus_window;
}

static BOOL Chox11_ButtonClick(event_pollblock *event, void *reference) {
  /* Grab keyboard focus on click on window if window accepts keyboard input */
  if (Chox11_focusCurrent == &Chox11_root_window
      || Chox11_focusCurrent->data.window.handle != event->data.mouse.window) {
    XDrawable *focus_window = Chox11_FindWindow(event->data.mouse.window);
    if (focus_window) focus_window = Chox11_GetFocusWindow(focus_window);
    if (focus_window) {
      Chox11_focusCurrent = focus_window;
      Window_GainCaret(focus_window->data.window.handle);
    }
  }       

  Chox11_ButtonEvent(&event->data.mouse, True);

  mouse_state = event->data.mouse.button.value;

  return TRUE;
}


/**
 * If the pointer is over a window we own, poll to monitor pointer motion
 * for generating MotionNotify X events
 */
static BOOL Chox11_BoundaryCrossed(event_pollblock *event, void *reference) {
  Bool check;
  XDrawable *window;
  mouse_block pointer;

  Wimp_GetPointerInfo(&pointer);
  window = Chox11_FindWindow(pointer.window);
  if ((int)reference == 1 && window != NULL) {
    Chox11Cursor_SetPointer(window);
  } else Chox11Cursor_UnsetPointer();
  
  check = (window != NULL);
  if (check != check_for_pointer) {
    if (check == False) {
      Chox11_RemoveNullHandler(MOUSE_NULL_HANDLER);
    }
    else {
      Chox11_AddNullHandler(MOUSE_NULL_HANDLER);
    }
  }
  check_for_pointer = check;

  return True;
}


static XEvent *Chox11_CreateKeyEvent(int type, Display *display, XDrawable *window) {
  XEvent *xevent = Chox11_AddEvent(type, display, (Window)window);

  if (xevent) {
    mouse_block mouse;
    window_info info;
    int bw = window->data.window.border_width;

    Wimp_GetPointerInfo(&mouse);
    Window_GetInfo(mouse.window, &info);

    xevent->xkey.time        = Time_Monotonic() * 10;
    xevent->xkey.x           = (mouse.pos.x - info.block.screenrect.min.x + info.block.scroll.x - bw) / 2;
    xevent->xkey.y           = (info.block.screenrect.max.y - info.block.scroll.y - mouse.pos.y - bw) / 2;;
    xevent->xkey.x_root      = mouse.pos.x / 2;
    xevent->xkey.y_root      = (screen_size.y - mouse.pos.y) / 2;
    xevent->xkey.same_screen = True;
    xevent->xkey.state       = Chox11_GetXState(&mouse);
  }
  return xevent;
}


static BOOL Chox11_KeyPress(event_pollblock *event, void *reference) {
  int key = (int)event->data.key.code;
  XDrawable *window = Chox11_FindWindow(event->data.key.caret.window);

  if (window) {
    XEvent *xevent = Chox11_CreateKeyEvent(KeyPress, Chox11_display, window);

    if (xevent) {
      Chox11Key_ConvertWimpKey(key, &xevent->xkey);

      if (window->data.window.event_mask & KeyReleaseMask) {
        Chox11Key_AddKeyRelease(&(xevent->xkey));
      }
    }  
  }
  return TRUE;
}


/**
 * Following routine called from Chox11Key_CheckKeyRelease in choxkey.c
 * to create the key release events
 */
void Chox11_CreateKeyReleaseEvent(int keycode) {
  XDrawable *window = Chox11_focusCurrent;

printf("Creating key release event %d\n", keycode);
  if (window != &Chox11_root_window) {
    XEvent *xevent = Chox11_CreateKeyEvent(KeyRelease, Chox11_display, window);

    if (xevent) {
      xevent->xkey.keycode = keycode;
    }
  }
}


static BOOL Chox11_GainCaret(event_pollblock *event, void *reference) {
  XDrawable *window = Chox11_FindWindow(event->data.caret.window);
  XDrawable *focus_window = Chox11_GetFocusWindow(window);

  if (focus_window) {
    XEvent *xevent = Chox11_AddEvent(FocusIn, Chox11_display, (Window)focus_window);
    Chox11_focusCurrent = focus_window;
    if (xevent) {
      xevent->xfocus.mode = NotifyNormal; /*TODO: Other modes */
      xevent->xfocus.detail = NotifyDetailNone; /*TODO: focus details */
    }
  }
  return TRUE;   
}


static BOOL Chox11_LoseCaret(event_pollblock *event, void *reference)
{
  XDrawable *window = Chox11_FindWindow(event->data.caret.window);
  XDrawable *focus_window = Chox11_GetFocusWindow(window);

  if (focus_window) {
    XEvent *xevent = Chox11_AddEvent(FocusOut, Chox11_display, (Window)focus_window);

    if (xevent) {
      xevent->xfocus.mode = NotifyNormal; /*TODO: Other modes */
      xevent->xfocus.detail = NotifyDetailNone; /*TODO: focus details */
    }
  }
  Chox11_focusCurrent = &Chox11_root_window;
  
  return TRUE;
}

/**
 * Update window position and sizes from a wimp open block
 */
void Chox11_UpdateWindowPos(XDrawable *window, window_openblock *openblock) {
  XDrawable *parent = window->data.window.parent.draw;
  int old_width = window->width;
  int old_height = window->height;
  int border_width = window->data.window.border_width;

  if (parent == &Chox11_root_window) {
    window->data.window.x = (openblock->screenrect.min.x - border_width) / 2;
    window->data.window.y = (screen_size.y - openblock->screenrect.max.y + border_width)/2-2;
  } else {
    /* Co-ordinates are parent relative */
    convert_block coords;
    
    Window_GetCoords(parent->data.window.handle, &coords);
    window->data.window.x = (Coord_XToWorkArea(openblock->screenrect.min.x, &coords) - parent->data.window.border_width - border_width)/2;
    window->data.window.y = (-Coord_YToWorkArea(openblock->screenrect.max.y, &coords) - parent->data.window.border_width - border_width)/2;
  }

  window->width = (openblock->screenrect.max.x - openblock->screenrect.min.x) / 2 - border_width;
  window->height = (openblock->screenrect.max.y - openblock->screenrect.min.y) / 2 - border_width;

  if (border_width) {
    // Need to force redraw of window borders if size has increased
    if (old_width < window->width) {
      Window_ForceRedraw(window->data.window.handle,
        old_width * 2 + border_width - 2, -window->height * 2 - 2 * border_width,
         old_width * 2 + 2 * border_width, 0);
    } else if (old_width > window->width) {
      Window_ForceRedraw(window->data.window.handle,
        window->width * 2 + border_width - 2, -window->height * 2 - 2 * border_width,
         window->width * 2 + 2 * border_width, 0);
    }

    if (old_height < window->height) {
      Window_ForceRedraw(window->data.window.handle,
        0, -old_height * 2 - 2 * border_width,
        window->width * 2 + 2 * border_width, -old_height * 2 - border_width + 2);
    } else if (old_height > window->height) {
      Window_ForceRedraw(window->data.window.handle,
        0, -window->height * 2 - 2 * border_width,
        window->width * 2 + 2 * border_width, -window->height * 2 - border_width + 2);
    }
  }

// printf("** New Window pos %p %d,%d,%d,%d (%d,%d,%d,%d)\n", window,
//         window->data.window.x,  window->data.window.y,
//         window->width, window->height, 
//         openblock->screenrect.min.x, openblock->screenrect.min.y,
//         openblock->screenrect.max.x,openblock->screenrect.max.y); 
}


/**
 * Open a window wimp event
 *
 * Informs us of user changes to window position/size/order.
 */
static BOOL Chox11_OpenWindow(event_pollblock *event, void *reference) {
  XDrawable *window = Chox11_FindWindow(event->data.openblock.window);

  printf("OpenWindow: %d %d\n", window->data.window.visibility, VisibilityFullyObscured); 

  if (window->data.window.visibility == VisibilityFullyObscured) {
    puts("visibility event");
    Chox11_CreateVisibilityEvent(Chox11_display, (Window)window, VisibilityUnobscured);
    window->data.window.visibility = VisibilityUnobscured; 
  }

  Window_SetExtent(window->data.window.handle, 0, event->data.openblock.screenrect.min.y - event->data.openblock.screenrect.max.y, 32767, 0);
  
  /* Update internal versions of position/size */
  Chox11_UpdateWindowPos(window, &event->data.openblock);
  /* Generate Configure notify if necessary */
  Chox11_CreateConfigureNotifyEvent(Chox11_display, window);
  
  return Handler_OpenWindow(event, reference);
}


/**
 * The main mechanism to get events from the native RISC OS window manager.
 */
void Chox11_CallWimpPoll(void) {

  Wimp_Poll(event_mask, &event_lastevent);
  
  if (event_lastevent.type != event_NULL)
    printf("Chox11_CallWimpPoll: Processing wimp event %d\n",
           event_lastevent.type);
  Event_Process(&event_lastevent);
  Chox11Queue_EmptyAll();
}


void Chox11_EventInitialise(const char *application_name)
{
  Event_Initialise3(application_name, 380, NULL);
  printf("Chox11_EventInitialise: handle=%i\n", event_taskhandle);

  Event_Claim(event_OPEN, event_ANY, event_ANY, Chox11_OpenWindow, NULL);
  Event_Claim(event_CLOSE, event_ANY, event_ANY, Chox11_Quit, NULL);
  Event_Claim(event_REDRAW, event_ANY, event_ANY, Chox11_Redraw, NULL);
  Event_Claim(event_CLICK, event_ANY, event_ANY, Chox11_ButtonClick, NULL);
  Event_Claim(event_NULL, event_ANY, event_ANY, Chox11_NullPoll, NULL);
  Event_Claim(event_PTRLEAVE, event_ANY, event_ANY, Chox11_BoundaryCrossed, (void *)0);
  Event_Claim(event_PTRENTER, event_ANY, event_ANY, Chox11_BoundaryCrossed, (void *)1);
  Event_Claim(event_KEY, event_ANY, event_ANY, Chox11_KeyPress, NULL);
  Event_Claim(event_GAINCARET, event_ANY, event_ANY, Chox11_GainCaret, NULL);
  Event_Claim(event_LOSECARET, event_ANY, event_ANY, Chox11_LoseCaret, NULL);
}


extern XAllowEvents(

    Display*		 display,
    int			 event_mode,
    Time		 time

);


extern Bool XCheckIfEvent(

    Display*		 display,
    XEvent*		 event_return,
    Bool (*predicate) (
#if NeedNestedPrototypes
	       Display*			 display,
               XEvent*			 event,
               XPointer			 arg
#endif
             ),
    XPointer		 arg

) {
  XEventList *event = eventfirst;

  Chox11_CheckPoll();

  puts("XCheckIfEvent");

  if (!predicate) return False;

  while (event) {
    if (predicate(display, &event->event, arg)) {
      Chox11_RemoveEventReturn(event, event_return);
      return True;
    }
    event = event->next;
  }

  return False;
}

extern int XEventsQueued(

    Display*		 display,
    int			 mode

) {
//  printf("XEventsQueued: %d\n", event_count);

  Chox11_CheckPoll();

  return event_count;
}



extern XIfEvent(

    Display*		 display,
    XEvent*		 event_return,
    Bool (*predicate) (
	       Display*			 display,
               XEvent*			 event,
               XPointer			 arg
             ),
    XPointer		 arg

) {
  puts("XIfEvent");

  do {
    while (event_count == 0) {
      Chox11_CallWimpPoll();
    }

  } while (XCheckIfEvent(display, event_return, predicate, arg));
}


extern XNextEvent(

    Display*		 display,
    XEvent*		 event_return

) {
  XEventList *event;
  
  puts("XNextEvent");
  
  do {
    /* Call Wimp_Poll (with filters) and add any events to
       the end of the input event queue */
    Chox11_CallWimpPoll();

    /* Remove the first event from the queue - which may or may
       not be the event just generated from the above call to
       Wimp_Poll */
    event = Chox11_RemoveEvent();
      
    if (event != NULL) {
      /* return a valid event from the queue */
      memcpy(event_return, &event->event, sizeof(XEvent));
      free(event);
      return;
    }
  } while (True);
}


extern XPeekEvent(

    Display*		 display,
    XEvent*		 event_return

) {
  puts("XPeekEvent");

  while (event_count == 0) {
    Chox11_CallWimpPoll();
  }

  memcpy(event_return, &eventfirst->event, sizeof(XEvent));
}


extern XPeekIfEvent(

    Display*		 display,
    XEvent*		 event_return,
    Bool (*predicate) (
               Display*		 display,
               XEvent*		 event,
               XPointer		 arg
             ),
    XPointer		 arg

) {
  puts("XPeekIfEvent - not implemented");

  memset(event_return, 0, sizeof(XEvent));
}


extern int XPending(

    Display*		 display

) {

//  puts("XPending");

  Chox11_CheckPoll();

  return event_count;
}


extern XPutBackEvent(

    Display*		 display,
    XEvent*		 event

) {
  puts("XPutBackEvent - not implemented");
}


extern XSelectInput(

    Display*		 display,
    Window		 w,
    long		 event_mask

) {
  XDrawable *draw = (XDrawable *)w;

  printf("XSelectInput: %p %x\n", draw, event_mask);

  draw->data.window.event_mask = event_mask;

  return Success;
}


extern Status XSendEvent(

    Display*		 display,
    Window		 w,
    Bool		 propagate,
    long		 event_mask,
    XEvent*		 event_send

) {
  XEventList *event;
  
  if (w == PointerWindow || w == InputFocus || propagate || event_mask != NoEventMask) {
    puts("XSendEvent - PointerWindow/InputFocus/propagate=True/event_mask not implemented");
    return 0;
  }

  /*TODO: Check this is one of our windows */

  printf("XSendEvent - window %x, type %x\n", (int)w, event_send->type);

  event = calloc(sizeof(XEventList), 1);

  /* TODO: Something on allocate failure */

  /* Add to linked list of events */
  if (eventfirst == NULL) {
    eventfirst = eventlast = event;
  } else {
    eventlast->next = event;
    event->prev = eventlast;
    eventlast = event;
  }

  if (w != event_window) {
    event_window = w;
    event_serial++;
  }

  memcpy(&event->event, event_send, sizeof(XEvent));
  event->event.xany.serial  = event_serial;
  event->event.xany.display = display;
  event->event.xany.window  = w;
  event->event.xany.send_event = True;

  event_count++;

  return 1;
}


static Bool Chox11_MatchEvent(Window w, XEvent *event_return, int event_value, Bool useMask) {
  XEventList *event = eventfirst;

  Chox11_CheckPoll();

  if (w != None && !Chox11Debug_CheckWindow(w)) return False;

  while (event) {
    if (w == None || w == event->event.xany.window) {
      /* Ignore non grabbed pointer events when in Sync mode */
      if (grabbed && grab_pointer_mode == GrabModeSync && !event->grabbed_event &&
          Chox11_Mask(event->event.xany.type, PropagateMask))
        break;

      if ((useMask && Chox11_Mask(event->event.xany.type, event_value)) ||
          (!useMask && event_value == event->event.xany.type)) {
        Chox11_RemoveEventReturn(event, event_return);
        return True;
      }
    }
    event = event->next;
  }

  return False;
}


extern Bool XCheckWindowEvent(

    Display*		 display,
    Window		 w,
    long		 xevent_mask,
    XEvent*		 event_return

) {
  puts("XCheckWindowEvent");

  return Chox11_MatchEvent(w, event_return, xevent_mask, True);
}


extern XWindowEvent(

    Display*		 display,
    Window		 w,
    long		 xevent_mask,
    XEvent*		 event_return

) {
  printf("XWindowEvent: %d\n", event_count);

   while (!Chox11_MatchEvent(w, event_return, xevent_mask, True))
      Chox11_CallWimpPoll();

}


extern Bool XCheckTypedEvent(

    Display*		 display,
    int			 event_type,
    XEvent*		 event_return

) {
  puts("XCheckTypedEvent");

  return Chox11_MatchEvent(None, event_return, event_type, False);
}

extern Bool XCheckTypedWindowEvent(

    Display*		 display,
    Window		 w,
    int			 event_type,
    XEvent*		 event_return

) {
  puts("XCheckTypedWindowEvent");

  return Chox11_MatchEvent(w, event_return, event_type, False);
}


extern Bool XCheckMaskEvent(

    Display*		 display,
    long		 event_mask,
    XEvent*		 event_return

) {
//  puts("XCheckMaskEvent");

  return Chox11_MatchEvent(None, event_return, event_mask, True);
}


extern XMaskEvent(

    Display*		 display,
    long		 event_mask,
    XEvent*		 event_return

) {

   while (!Chox11_MatchEvent(None, event_return, event_mask, True))
      Chox11_CallWimpPoll();
}


extern Bool XFilterEvent(

    XEvent*	 event,
    Window	 window

) {
  puts("XFilterEvent - not implemented");
  printf("%p %x %d\n", event, window, event->xany.type); 
  return False;
}


