;; Copyright (c) 2001-2003 by Norbert Freudemann, David Frese

(define-syntax import-xlib-function
  (lambda (exp rename compare)
    (let ((id (cadr exp))
	  (formals (caddr exp))
	  (external-id (cadddr exp))
	  (%define (rename 'define))
	  (%begin (rename 'begin))
	  (%lambda (rename 'lambda))
	  (%binding (rename 'binding))
	  (%import (rename 'import-lambda-definition-2))
	  (%call-xlib-function (rename 'call-xlib-function)))
      `(,%begin
	(,%import ,%binding ,formals ,external-id)
	(,%define ,id
	  (,%lambda ,formals
	    (,%call-xlib-function ,(car formals) ,id
				  (,%lambda ()
				    (,%binding . ,formals)))))))))

(define (call-xlib-function display name thunk)
  (if (display? display)
      (if (display:warnings? display)
	  (call-critical
	   (lambda ()
	     (let* ((queue (display:error-queue display))
		    (result (thunk)))
	       (if (not (eq? queue (display:error-queue display)))
		   (let* ((next (next-x-error-queue queue))
			  (error (x-error-queue:this next)))
		     (signal-x-warning error)))
	       result)))
	  (thunk))
      (error "first argument of an xlib-function must be a display object"
	     name display)))

(define (call-critical thunk)
  (let ((old-enabled (set-enabled-interrupts! no-interrupts)))
    (with-handler
     (lambda (condition punt)
       (set-enabled-interrupts! old-enabled)
       (punt))
     (lambda ()
       (let ((result (thunk)))
	 (set-enabled-interrupts! old-enabled)
	 result)))))

;; TODO: pixmap-formats (XListPixmapFormats)
(define-record-type display :display
  (make-display cpointer connection-number protocol-version protocol-revision
		server-vendor image-byte-order bitmap-unit bitmap-pad
		bitmap-bit-order vendor-release queue-length name
		default-screen screens after-function wakeup
		warnings? error-queue)
  display?
  (cpointer display:cpointer)
  (connection-number display:connection-number)
  (protocol-version display:protocol-version)
  (protocol-revision display:protocol-revision)
  (server-vendor display:server-vendor)
  (image-byte-order display:image-byte-order)
  (bitmap-unit display:bitmap-unit)
  (bitmap-pad display:bitmap-pad)
  (bitmap-bit-order display:bitmap-bit-order)
  (vendor-release display:vendor-release)
  (queue-length display:queue-length)
  (name display:name)
  (default-screen display:default-screen)
  (screens display:screens)
  (after-function display:after-function set-display:after-function!)
  (wakeup display:wakeup set-display:wakeup!)
  (warnings? display:warnings? set-display:warnings?!)
  (error-queue display:error-queue set-display:error-queue!)
  (event-bundle display:cevent-bundle))

(define-exported-binding "scx-display" :display)

(define (wakeup-display dpy)
  (write-char #\x (cdr (display:wakeup dpy))))

(define (display-wakeup-inport dpy)
  (breakpoint "display-wakeup-inport")
  (car (display:wakeup dpy)))

(define (initialize-display dpy)
  (set-display:error-queue! dpy (empty-x-error-queue))
  (call-with-values open-pipe
		    (lambda (r w)
		      (set-display:wakeup! dpy (cons r w)))))

(define-exported-binding "scx-initialize-display" initialize-display)

(define (display-message-inport display)
  (input-channel->port (display:connection-number display)))

(define-enumerated-type byte-order :byte-order
  byte-order? byte-orders byte-order-name byte-order-index
  (lsb-first msb-first))

(define-exported-binding "scx-byte-order" :byte-order)
(define-exported-binding "scx-byte-orders" byte-orders)

(define-enumerated-type bit-order :bit-order
  bit-order? bit-orders bit-order-name bit-order-index
  (lsb-first msb-first))

(define-exported-binding "scx-bit-order" :bit-order)
(define-exported-binding "scx-bit-orders" bit-orders)

(define-record-type screen-format :screen-format ;; aka pixmap-format
  (make-screen-format depth bits-per-pixel scanline-pad)
  screen-format?
  (depth screen-format:depth)
  (bits-per-pixel screen-format:bits-per-pixel)
  (scanline-pad screen-format:scanline-pad))

(define-exported-binding "scx-screen-format" :screen-format)

(define-record-type screen :screen
  (make-screen cpointer display root-window width height width-mm
	       height-mm number root-depth default-visual default-gc
	       default-colormap white-pixel black-pixel max-maps min-maps
	       does-backing-store does-save-unders? event-mask)
  ;; maybe add depths ?? (TODO)
  ;; does event-mask change ?? (TODO)
  screen?
  (cpointer screen:cpointer)
  (display screen:display)
  (root-window screen:root-window)
  (width screen:width)
  (height screen:height)
  (width-mm screen:width-mm)
  (height-mm screen:height-mm)
  (number screen:number)
  (root-depth screen:root-depth)
  (default-visual screen:default-visual)
  (default-gc screen:default-gc)
  (default-colormap screen:default-colormap)
  (white-pixel screen:white-pixel)
  (black-pixel screen:black-pixel)
  (max-maps screen:max-maps)
  (min-maps screen:min-maps)
  (does-backing-store screen:does-backing-store)
  (does-save-unders? screen:does-save-unders?)
  (event-mask screen:event-mask))

(define-exported-binding "scx-screen" :screen)

;(define (screen:cells screen)
;  (visual:map-entries (screen:default-visual screen)))

(import-lambda-definition-2 screen-number-of-screen (screen)
  "scx_Screen_Number_Of_Screen")

;; *** connect or disconnect to X server *****************************

(define (try-really-hard v)
  (or (v) (try-really-hard v)))

(import-lambda-definition-2 %open-display (display-name event-uid)
  "scx_Open_Display")

(define *opened-displays* '())

(define (scx-add-display display-pointer bundle-pointer)
  (set! *opened-displays*
	    (cons (cons display-pointer bundle-pointer)
		  *opened-displays*)))

(define (scx-lookup-display-bundle display-pointer)
  (let loop ((opened-displays *opened-displays*))
    (cond
     ((null? opened-displays) #f)
     ((pair? opened-displays)
      (if (byte-vector=? (caar opened-displays) display-pointer)
	  (cdar opened-displays)
	  (loop (cdr opened-displays)))))))

(define-exported-binding "scx-lookup-display-bundle" scx-lookup-display-bundle)
(define-exported-binding "scx-add-display" scx-add-display)

;; returns a display or #f
(define (open-display . args)
  (let ((display-name (if (null? args)
			  ""
			  (if (null? (cdr args))
			      (car args)
			      (error "invalid arguments" (cdr args)))))) ;; TODO
    (try-really-hard (lambda () (%open-display display-name *xlib-event-uid*)))))

(import-xlib-function %close-display (display)
  "scx_Close_Display")

(define (close-display dpy)
  ;(breakpoint "close-display-1")
  (%close-display dpy)
  ;(breakpoint "close-display-2")
  (set! *opened-displays*
	(filter (lambda (x)
		  (not (byte-vector=? (car x) (display:cpointer dpy))))
		*opened-displays*)))

(define none 0)
(define parent-relative 1)
(define copy-from-parent 0)
(define pointer-window 0)
(define input-focus 1)
(define pointer-root 1)
(define any-property-type 0)
(define any-key 0)
(define all-temporary 0)
(define current-time 0)
(define no-symbol 0)
(define all-planes (- (arithmetic-shift 1 32) 1))

(import-lambda-definition-2 display:last-request-read (display)
  "scx_Display_Last_Request_Read")

;; *** convenience functions *****************************************

(define (default-root-window display)
  (screen:root-window (display:default-screen display)))

(define (black-pixel display)
  (screen:black-pixel (display:default-screen display)))

(define (white-pixel display)
  (screen:white-pixel (display:default-screen display)))

(import-lambda-definition next-request (display)
  "scx_Next_Request")

;; *** enable or disable synchronization *****************************

(define (synchronize dpy on?)
  (if on?
      (set-after-function! dpy
			   (lambda (dpy)
			     (display-sync dpy #f)))
      (set-after-function! dpy #f)))

;; returns the previous after-function. An after-function is called
;; with the display object.

(define (general-after-function display)
  (if (display:after-function display)
      ((display:after-function display) display)
      ;; else the default behaviour ;; TODO: check if this is the real one
      (display-flush display))

  ;; if events are in the queue now, then wakeup a wait-event call
  (if (> (events-queued display (queued-mode already)) 0)
      (wakeup-display display)))

(define-exported-binding "scx-general-after-function" general-after-function)

(define (set-after-function! display fun)
  (let ((prev (display:after-function display)))
    (set-display:after-function! display fun)
    prev))

;; *** handle output buffer or event queue ***************************

(import-xlib-function display-flush (display)
  "scx_Display_Flush")

(import-xlib-function display-sync (display discard?)
  "scx_Display_Sync")

;; display-no-op sends a NoOperation protocol request to the X server, thereby
;; exercising the connection. See XNoOp.

(import-xlib-function display-no-op (display)
  "scx_No_Op")

;; *** select input events *******************************************

(import-xlib-function display-select-input (display window event-mask)
  "scx_Display_Select_Input")
