/* Copyright 2001-2003 by Norbert Freudemann, David Frese */

#include "xlib.h"

s48_ref_t scx_add_display = NULL;
s48_ref_t scx_lookup_display_bundle = NULL;

void list_add(scx_node **liste, void *element) {
  scx_node *new_element = malloc(sizeof(scx_node));
  new_element->content = element;
  new_element->next = *liste;
  *liste = new_element;
}

void *list_get(scx_node **liste) {
  scx_node *list_element = (*liste);
  void *content = list_element->content;
  (*liste) = (*liste)->next;
  free(list_element);
  return content;
}

int XNextEventTimeout(Display *display, XEvent *e, int timeout_ms) {
  int pending = XPending(display);
  if (!pending) {
    struct timeval tv;
    tv.tv_sec=0;
    tv.tv_usec=timeout_ms * 1000;
    int fd = XConnectionNumber(display);
    fd_set readset;
    FD_ZERO(&readset);
    FD_SET(fd, &readset);
    select( fd+1, &readset, 0, 0, &tv );
    pending = XPending(display);
  }
  if (pending) {
    XNextEvent(display, e);
    return 1;
  }
  return 0;
}

void *scx_Event_Loop(void *dpy_bundle)
{
  scx_event_bundle *display_bundle =
    (scx_event_bundle *) dpy_bundle;

  while(1) {
    XEvent *e;
    e = malloc(sizeof(XEvent));
    
    if(pthread_mutex_trylock(&display_bundle->close_display_lock) == EBUSY) {
      break;
    }

    if(XNextEventTimeout(display_bundle->display, e, 200)) {
      pthread_mutex_lock(&display_bundle->event_list_access_lock);
      list_add(&(display_bundle->temp_events), e);
      pthread_mutex_unlock(&display_bundle->event_list_access_lock);

      s48_note_external_event(display_bundle->event_uid);
    }

    pthread_mutex_unlock(&display_bundle->close_display_lock);
  }
}

scx_event_bundle* scx_extract_display_bundle(s48_call_t call,  s48_ref_t dpy) {
  return (scx_event_bundle*) s48_extract_pointer_2(call,
		     s48_checked_record_ref_2(call, dpy, 18, scx_display));
}

Display* scx_extract_display(s48_call_t call, s48_ref_t dpy) {
  scx_event_bundle* bundle =
    scx_extract_display_bundle(call, dpy);
  return bundle->display;
}

s48_ref_t scx_Get_X_Events(s48_call_t call, s48_ref_t dpy) {
  s48_ref_t scheme_event_list = s48_null_2(call);

  scx_event_bundle *bundle = scx_extract_display_bundle(call, dpy);

  pthread_mutex_lock(&bundle->event_list_access_lock);
  scx_node **event_list = &bundle->temp_events;
  
  while(*event_list) {
    XEvent *event = (XEvent *)list_get(event_list);
    scheme_event_list =
      s48_cons_2(call, scx_enter_event(call, event, bundle), scheme_event_list);
    XFree(event);
  }
  pthread_mutex_unlock(&bundle->event_list_access_lock);

  return scheme_event_list;
}

s48_ref_t scx_screen_format_binding = NULL;

s48_ref_t scx_enter_screenformat(s48_call_t call, ScreenFormat* sf) {
  s48_ref_t res = s48_make_record_2(call, scx_screen_format_binding);

  s48_record_set_2(call, res, 0, s48_enter_long_2(call, sf->depth));
  s48_record_set_2(call, res, 1, s48_enter_long_2(call, sf->bits_per_pixel));
  s48_record_set_2(call, res, 2, s48_enter_long_2(call, sf->scanline_pad));
  return res;
}

s48_ref_t scx_screen_list = NULL;

scx_event_bundle * scx_get_display_bundle(s48_call_t call, Display *dpy) {
  s48_ref_t fun =
    s48_shared_binding_ref_2(call, scx_lookup_display_bundle);

  s48_ref_t bundle_pointer =
    s48_call_scheme_2(call, fun, 1, s48_enter_pointer_2(call, dpy));

  return (scx_event_bundle *) s48_extract_pointer_2(call, bundle_pointer);
}

s48_ref_t scx_enter_screen(s48_call_t call, Screen* scr) {
  s48_ref_t s = scx_struct_cache_ref(call, scr, scx_screen_list);
  scx_event_bundle *bundle =
    scx_get_display_bundle(call, DisplayOfScreen(scr));

  if (s48_false_p_2(call, s)) {
    s = s48_make_record_2(call, scx_screen);
    scx_struct_cache_set(call, scr, &scx_screen_list, s);

    s48_record_set_2(call, s, 0, s48_enter_pointer_2(call, scr));
    s48_record_set_2(call, s, 1,
		     scx_enter_display(call, bundle));//DisplayOfScreen(scr)));
    s48_record_set_2(call, s, 2,
		     scx_enter_window(call, RootWindowOfScreen(scr)));
    s48_record_set_2(call, s, 3,
		     s48_enter_long_2(call, WidthOfScreen(scr)));
    s48_record_set_2(call, s, 4,
		     s48_enter_long_2(call, HeightOfScreen(scr)));
    s48_record_set_2(call, s, 5,
		     s48_enter_long_2(call, WidthMMOfScreen(scr)));
    s48_record_set_2(call, s, 6,
		     s48_enter_long_2(call, HeightMMOfScreen(scr)));
    s48_record_set_2(call, s, 7,
		     s48_enter_long_2(call, XScreenNumberOfScreen(scr)));
    s48_record_set_2(call, s, 8,
		     s48_enter_long_2(call, DefaultDepthOfScreen(scr)));
    s48_record_set_2(call, s, 9,
		     scx_enter_visual(call, DefaultVisualOfScreen(scr)));
    s48_record_set_2(call, s, 10,
		     scx_enter_gc(call, DefaultGCOfScreen(scr)));
    s48_record_set_2(call, s, 11,
		     scx_enter_colormap(call, DefaultColormapOfScreen(scr)));
    s48_record_set_2(call, s, 12,
		     scx_enter_pixel(call, WhitePixelOfScreen(scr)));
    s48_record_set_2(call, s, 13,
		     scx_enter_pixel(call, BlackPixelOfScreen(scr)));
    s48_record_set_2(call, s, 14,
		     s48_enter_long_2(call, MaxCmapsOfScreen(scr)));
    s48_record_set_2(call, s, 15,
		     s48_enter_long_2(call, MinCmapsOfScreen(scr)));
    s48_record_set_2(call, s, 16,
		     scx_enter_backing_store(call, DoesBackingStore(scr)));
    s48_record_set_2(call, s, 17,
		     s48_enter_boolean_2(call, DoesSaveUnders(scr)));
    s48_record_set_2(call, s, 18,
		     scx_enter_event_mask(call, EventMaskOfScreen(scr)));
  }
  return s;
}

s48_ref_t scx_Screen_Number_Of_Screen(s48_call_t call, s48_ref_t screen) {
  Screen* sp =
    (Screen*)s48_extract_pointer_2(call, s48_record_ref_2(call, screen, 0));
  int num = XScreenNumberOfScreen(sp);
  return s48_enter_long_2(call, num);
}

static s48_ref_t scx_display_list = NULL;
static s48_ref_t scx_general_after_function_binding = NULL;

static int scx_after_function_wrapper(Display* dpy) {
  s48_call_t call = s48_get_current_call();
  s48_ref_t display =
    scx_enter_display(call,
		      scx_get_display_bundle(call, dpy));
  s48_ref_t fun =
    s48_shared_binding_ref_2(call, scx_general_after_function_binding);
  s48_call_scheme_2(call, fun, 1, display);
  
  return 0;
}

s48_ref_t scx_initialize_display_binding = NULL;

s48_ref_t scx_enter_display(s48_call_t call,
			    scx_event_bundle *display_bundle) {
  Display* dpy = display_bundle->display;
  s48_ref_t d = scx_struct_cache_ref(call, dpy, scx_display_list);

  if (s48_false_p_2(call, d))
  {
    int i;
    s48_ref_t l = s48_null_2(call);
    s48_ref_t id = s48_enter_string(call, "X");
    s48_ref_t in_channel =
		s48_add_channel_2(call, s48_channel_status_input_2(call),
				  id, ConnectionNumber(dpy));

    d = s48_make_record_2(call, scx_display);

    /* have to do this first, because screens want to reference their
       display */
    scx_struct_cache_set(call, dpy, &scx_display_list, d);

    s48_record_set_2(call, d, 0, s48_enter_pointer_2(call, dpy));
    s48_record_set_2(call, d, 1, in_channel);
    s48_record_set_2(call, d, 2, s48_enter_long_2(call, ProtocolVersion(dpy)));
    s48_record_set_2(call, d, 3, s48_enter_long_2(call, ProtocolRevision(dpy)));
    s48_record_set_2(call, d, 4, s48_enter_string(call, ServerVendor(dpy)));
    s48_record_set_2(call, d, 5, scx_enter_byte_order(call, ImageByteOrder(dpy)));
    s48_record_set_2(call, d, 6, s48_enter_long_2(call, BitmapUnit(dpy)));
    s48_record_set_2(call, d, 7, scx_enter_bit_order(call, BitmapBitOrder(dpy)));
    s48_record_set_2(call, d, 8, s48_enter_long_2(call, BitmapPad(dpy)));
    s48_record_set_2(call, d, 9, s48_enter_long_2(call, VendorRelease(dpy)));
    s48_record_set_2(call, d, 10, s48_enter_long_2(call, QLength(dpy)));
    s48_record_set_2(call, d, 11, s48_enter_string(call, DisplayString(dpy)));
    s48_record_set_2(call, d, 12,
	    scx_enter_screen(call, ScreenOfDisplay(dpy, DefaultScreen(dpy))));
    for (i = ScreenCount(dpy)-1; i >= 0; i--)
      l = s48_cons_2(call, scx_enter_screen(call, ScreenOfDisplay(dpy, i)), l);
    s48_record_set_2(call, d, 13, l);
    s48_record_set_2(call, d, 14, s48_false_2(call)); /* the after-function */
    XSetAfterFunction(dpy, &scx_after_function_wrapper);
    s48_record_set_2(call, d, 15, s48_false_2(call)); /* wakeup-port, set by initialize */
    s48_record_set_2(call, d, 16, s48_true_2(call)); /* use-warnings? */
    s48_record_set_2(call, d, 17, s48_false_2(call)); /* error-queue, set by initialize */
    s48_record_set_2(call, d, 18, s48_enter_pointer_2(call, display_bundle));
    s48_call_scheme_2(call,
	      s48_shared_binding_ref_2(call, scx_initialize_display_binding), 1, d);
  }

  return d;
}

s48_ref_t scx_Open_Display (s48_call_t call, s48_ref_t name, s48_ref_t sch_event_uid) {
  Display* res = XOpenDisplay(s48_extract_string(call, name));

  if (res == NULL) {
    return s48_false_2(call);
  }
  else {
    pthread_t *event_thead = malloc(sizeof(pthread_t));
    
    scx_event_bundle *display_bundle =
      malloc(sizeof(scx_event_bundle));
    display_bundle->event_thread = event_thead;
    display_bundle->display = res;
    pthread_mutex_init(&display_bundle->event_list_access_lock, NULL);
    pthread_mutex_init(&display_bundle->close_display_lock, NULL);
    display_bundle->temp_events = NULL;
    display_bundle->event_uid = s48_extract_long_2(call, sch_event_uid);

    pthread_create(event_thead, NULL, scx_Event_Loop, (void*)display_bundle);

    /* current display has to be added here to the *opened-displays*-list, because
       it is needed in scx_enter_screen, which is called in scx_enter_display */
    s48_ref_t fun =
      s48_shared_binding_ref_2(call, scx_add_display);
    s48_call_scheme_2(call, fun, 2, s48_enter_pointer_2(call, res),
		      s48_enter_pointer_2(call, display_bundle));

    return scx_enter_display(call, display_bundle);
  }
}

s48_ref_t scx_Close_Display(s48_call_t call, s48_ref_t display) {
  scx_event_bundle * bundle = scx_extract_display_bundle(call, display);
  pthread_mutex_lock(&bundle->close_display_lock);
  XCloseDisplay(bundle->display);
  free(bundle->event_thread);
  free(bundle);
  return s48_unspecific_2(call);
}

s48_ref_t scx_Display_Last_Request_Read(s48_call_t call, s48_ref_t display) {
  return s48_enter_long_2(call,
	   LastKnownRequestProcessed(scx_extract_display(call, display)));
}

s48_ref_t scx_Next_Request(s48_call_t call, s48_ref_t display) {
  return s48_enter_long_2(call,
	   NextRequest(scx_extract_display(call, display)));
}

s48_ref_t scx_Display_Flush(s48_call_t call, s48_ref_t display) {
  XFlush(scx_extract_display(call, display));
  return s48_unspecific_2(call);
}

s48_ref_t scx_Display_Sync(s48_call_t call, s48_ref_t display,
			   s48_ref_t discard) {
  XSync(scx_extract_display(call, display),
	s48_extract_boolean_2(call, discard));
  return s48_unspecific_2(call);
}

s48_ref_t scx_No_Op(s48_call_t call, s48_ref_t display) {
  XNoOp(scx_extract_display(call, display));
  return s48_unspecific_2(call);
}

s48_ref_t scx_Display_Select_Input(s48_call_t call, s48_ref_t display,
	    s48_ref_t window, s48_ref_t event_mask) {
  XSelectInput(scx_extract_display(call, display),
	       scx_extract_window(call, window),
	       scx_extract_event_mask(call, event_mask));
  return s48_unspecific_2(call);
}

void scx_init_display(void) {
  if(!XInitThreads())
    fprintf(stderr, "XThread initialization failed.");

  scx_screen_format_binding =
    s48_get_imported_binding_2("scx-screen-format");
  scx_general_after_function_binding =
    s48_get_imported_binding_2("scx-general-after-function");
  scx_initialize_display_binding =
    s48_get_imported_binding_2("scx-initialize-display");

  scx_lookup_display_bundle =
    s48_get_imported_binding_2("scx-lookup-display-bundle");

  scx_add_display =
    s48_get_imported_binding_2("scx-add-display");

  scx_display_list = s48_make_global_ref(_s48_value_null);
  scx_screen_list = s48_make_global_ref(_s48_value_null);

  S48_EXPORT_FUNCTION(scx_Screen_Number_Of_Screen);
  S48_EXPORT_FUNCTION(scx_Open_Display);
  S48_EXPORT_FUNCTION(scx_Close_Display);
  S48_EXPORT_FUNCTION(scx_Display_Last_Request_Read);
  S48_EXPORT_FUNCTION(scx_Next_Request);
  S48_EXPORT_FUNCTION(scx_Display_Flush);
  S48_EXPORT_FUNCTION(scx_Display_Sync);
  S48_EXPORT_FUNCTION(scx_No_Op);
  S48_EXPORT_FUNCTION(scx_Display_Select_Input);
  S48_EXPORT_FUNCTION(scx_Get_X_Events);
}
