(in-package :de.anvi.croatoan.test)

(defun snake ()
  (labels ((display-snake (scr body)
             (mapc #'(lambda (pair) (add-char scr (char-code #\*) :y (car pair) :x (cadr pair))) body)))
    (with-screen (scr :input-echoing nil :input-blocking nil :enable-function-keys t :cursor-visible nil)
      (let* ((body '((0 7) (0 6) (0 5) (0 4) (0 3) (0 2) (0 1) (0 0)))
             (xpos (cadar body))
             (ypos (caar body))
             (dir #c(1 0)))

        (clear scr)
        (display-snake scr body)
        (refresh scr)

         (loop
            (let ((event (get-event scr)))
              (if event
                  (case event
                    (:up    (setf dir #c( 0  1)))
                    (:down  (setf dir #c( 0 -1)))
                    (:right (setf dir #c( 1  0)))
                    (:left  (setf dir #c(-1  0)))
                    (#\q    (return)))
                  (progn
                    (sleep 0.1)
                    (add-char scr (char-code #\space) :y (caar (last body)) :x (cadar (last body)))
                    (setf body (cons (list (incf ypos (- (imagpart dir)))
                                           (incf xpos (realpart dir)))
                                     (butlast body)))
                    (display-snake scr body)
                    (refresh scr)))))))))

(defun snake2 ()
  "Use bind and run-event-loop for event handling. Use lists instead of complex numbers for directions."
  (with-screen (scr :input-echoing nil :input-blocking nil :enable-function-keys t :cursor-visible nil)
    (let* ((body '((0 7) (0 6) (0 5) (0 4) (0 3) (0 2) (0 1) (0 0)))
           (head (car body))
           (tail (car (last body)))
           ;; initial direction = right
           (dir '(0 1)))
      (flet ((draw-snake (win body)
               (mapc (lambda (pos) (add win #\* :position pos)) body)))
        (bind scr #\q    'exit-event-loop)
        (bind scr :right (lambda (w e) (setq dir '( 0  1))))
        (bind scr :left  (lambda (w e) (setq dir '( 0 -1))))
        (bind scr :down  (lambda (w e) (setq dir '( 1  0))))
        (bind scr :up    (lambda (w e) (setq dir '(-1  0))))
        (bind scr nil    (lambda (w e)
                           ;; snake moves = erase last body pair by overwriting it with space
                           (add scr #\space :position tail)
                           (setq body (cons (mapcar #'+ head dir) (butlast body)))
                           (setq head (car body))
                           (setq tail (car (last body)))
                           (draw-snake scr body)
                           (refresh scr)))
        (clear scr)
        (setf (frame-rate scr) 20)
        (run-event-loop scr)))))

;; https://github.com/skydrome/random/blob/master/shell/screensaver.sh
;; https://github.com/pipeseroni/pipes.sh/blob/master/pipes.sh
;; https://www.youtube.com/watch?v=T4n87IIa--U
(defun pipes ()
  (with-screen (scr :input-echoing nil :input-blocking nil :enable-function-keys t :cursor-visible nil)
    (let* ((pos (list (round (/ (height scr) 2)) (round (/ (width scr) 2))))
           (dirs '((1 0) (-1 0) (0 -1) (0 1)))
           (dir (nth (random 4) dirs))
           (dir-prev dir)
           ch
           (i 0))
      (clear scr)
      (box scr)
      (refresh scr)
      (event-case (scr event)
        (#\q (return-from event-case))
        ((nil)
         (sleep 0.1)
         (move scr (car pos) (cadr pos))
         (setf ch (cond ((equal dir dir-prev) (if (= (car dir) 0) :horizontal-line :vertical-line))
                        ((and (equal dir-prev '(1  0)) (equal dir '(0 -1))) :lower-right-corner)
                        ((and (equal dir-prev '(1  0)) (equal dir '(0  1))) :lower-left-corner)
                        ((and (equal dir-prev '(0  1)) (equal dir '(-1 0))) :lower-right-corner)
                        ((and (equal dir-prev '(0  1)) (equal dir '(1  0))) :upper-right-corner)
                        ((and (equal dir-prev '(-1 0)) (equal dir '(0 -1))) :upper-right-corner)
                        ((and (equal dir-prev '(-1 0)) (equal dir '(0  1))) :upper-left-corner)
                        ((and (equal dir-prev '(0 -1)) (equal dir '(-1 0))) :lower-left-corner)
                        ((and (equal dir-prev '(0 -1)) (equal dir '(1  0))) :upper-left-corner)))
         (add-char scr ch)
         (let ((pos-trial (list (+ (car pos) (car dir)) (+ (cadr pos) (cadr dir)))))
           (if (or (= (car pos-trial)  0)
                   (= (cadr pos-trial) 0)
                   (= (car pos-trial)  (1- (height scr)))
                   (= (cadr pos-trial) (1- (width scr))))
               (setf pos (list (round (/ (height scr) 2)) (round (/ (width scr) 2))))
               (setf pos pos-trial)))
         (if (> i (random 5))
             (progn (setf i 0)
                    (setf dir-prev dir)
                    (setf dir (nth (random 3) (remove (list (- (car dir)) (- (cadr dir))) dirs :test #'equal))))
             (progn (incf i)
                    (setf dir-prev dir)))
         (refresh scr))))))

(defun matrix ()
  (with-screen (scr :input-echoing nil :input-blocking nil :cursor-visible nil)
    (let* ((width (width scr))
           (height (height scr))
           (positions (loop repeat width collect (random height)))
           (speeds (loop repeat width collect (random 4))))
      (event-case (scr event)
        (#\q (return-from event-case))
        ((nil)
         (sleep 0.05)
         (loop for column from 0 to (1- width) do
              (loop repeat (nth column speeds) do
                   (setf (attributes scr) '(:bold))
                   (setf (color-pair scr) '(:white :black))
                   (add-char scr (+ 64 (random 58)) :y (mod (nth column positions) height) :x column)
                   (setf (color-pair scr) '(:green :black))
                   (add-char scr (+ 64 (random 58)) :y (mod (- (nth column positions) 1) height) :x column)
                   (add-char scr (+ 64 (random 58)) :y (mod (- (nth column positions) 2) height) :x column)
                   (setf (attributes scr) '())
                   (add-char scr (+ 64 (random 58)) :y (mod (- (nth column positions) 3) height) :x column)
                   (add-char scr (char-code #\space) :y (mod (- (nth column positions) (floor height 2)) height) :x column)
                   (refresh scr)
                   (setf (nth column positions) (mod (1+ (nth column positions)) height)))))))))

(defun matrix2 ()
  (with-screen (scr :input-echoing nil :input-blocking nil :cursor-visible nil)
    (let* ((width (width scr))
           (height (height scr))
           ;; start at a random height in each column.
           (positions (loop repeat width collect (random height)))
           ;; run each column at a random speed.
           (speeds (loop repeat width collect (random 4))))
      (flet ((randch () (+ 64 (random 58))))
        ;; hit the q key to exit the main loop.
        (bind scr #\q 'exit-event-loop)
        (bind scr nil
          (lambda (win event)
            ;; generate a random ascii char
            (loop for column from 0 to (1- width) do
                 (loop repeat (nth column speeds) do
                      ;; position of the first point in the current column
                      (let ((pos (nth column positions)))
                        (setf (attributes win) '(:bold))
                        (setf (color-pair win) '(:white :black))
                        (add win (randch) :y (mod pos height) :x column)
                        (setf (color-pair win) '(:green :black))
                        (add win (randch) :y (mod (- pos 1) height) :x column)
                        (add win (randch) :y (mod (- pos 2) height) :x column)
                        (setf (attributes win) '())
                        (add win (randch) :y (mod (- pos 3) height) :x column)
                        ;; overwrite the last char with a space
                        (add win #\space  :y (mod (- pos (floor height 3)) height) :x column)
                        (refresh win)
                        ;; increment the column positions
                        (setf (nth column positions) (mod (1+ pos) height)))))))))
    ;; after the handlers have been defined, run the main event loop at 20 fps.
    (setf (frame-rate scr) 20)
    (run-event-loop scr)))

(defun matrix3 ()
  "Test character styles and color pair completion."
  (with-screen (scr :input-echoing nil :input-blocking nil :cursor-visible nil)
    (let* ((width (width scr))
           (height (height scr))
           (positions (loop repeat width collect (random height)))
           (speeds (loop repeat width collect (random 4)))
           (s1 (list :attributes '(:bold) :fgcolor :white))
           (s2 (list :attributes '(:bold) :fgcolor :green :bgcolor nil))
           (s3 (list :attributes nil      :fgcolor :green :bgcolor :black)))
      (flet ((randch () (+ 64 (random 58))))
        (bind scr #\q 'exit-event-loop)
        (bind scr #\r
          (lambda (win event)
            (setf (getf s2 :fgcolor) :red
                  (getf s3 :fgcolor) :red)))
        (bind scr nil
          (lambda (win event)
            (loop for column from 0 to (1- width) do
                 (loop repeat (nth column speeds) do
                      (let ((pos (nth column positions)))
                        (add win (randch) :y (mod pos height) :x column :style s1)
                        (add win (randch) :y (mod (- pos 1) height) :x column :style s2)
                        (add win (randch) :y (mod (- pos 2) height) :x column :style s2)
                        (add win (randch) :y (mod (- pos 3) height) :x column :style s3)
                        (add win #\space  :y (mod (- pos (floor height 3)) height) :x column :style s3)
                        (refresh win)
                        (setf (nth column positions) (mod (1+ pos) height)))))))))
    (setf (frame-rate scr) 20)
    (run-event-loop scr)))

(defun matrix4 ()
  "Test passing colors with fgcolor without passing them as pairs."
  (with-screen (scr :input-echoing nil :input-blocking nil :cursor-visible nil)
    (let* ((width (width scr))
           (height (height scr))
           (positions (loop repeat width collect (random height)))
           (speeds (loop repeat width collect (random 4))))
      (flet ((randch () (+ 64 (random 58))))
        (bind scr #\q 'exit-event-loop)
        (bind scr nil
          (lambda (win event)
            (loop for column from 0 to (1- width) do
                 (loop repeat (nth column speeds) do
                      (let ((pos (nth column positions)))
                        (setf (attributes win) '(:bold))
                        (setf (fgcolor win) :green)
                        (add win (randch) :y (mod pos height) :fgcolor :white :x column)
                        (add win (randch) :y (mod (- pos 1) height) :x column)
                        (add win (randch) :y (mod (- pos 2) height) :x column)
                        (setf (attributes win) '())
                        (add win (randch) :y (mod (- pos 3) height) :x column)
                        (add win #\space  :y (mod (- pos (floor height 3)) height) :x column)
                        (refresh win)
                        (setf (nth column positions) (mod (1+ pos) height)))))))))
    (setf (frame-rate scr) 20)
    (run-event-loop scr)))

;; initialize ncurses, deinitialize ncurses
;; tests initialize-instance
(defun t00 ()
  (let ((scr (make-instance 'screen)))
    (unwind-protect
         nil
      (close scr))))

;; clos screen, accessors, unwind-protect.
(defun t01 ()
  (unwind-protect
       (let ((scr (make-instance 'screen :enable-colors t)))

         (clear scr)
         (move scr 0 0)
         (add-string scr "hello there!")

         ;; the text will be red on yellow.
         ;; this affects only new characters, not the whole window.
         (setf (color-pair scr) '(:red :yellow))

         (move scr 5 5)
         (add-string scr "dear john!")
         (refresh scr)

         ;; wait for keypress, works only in blocking mode, which is the default.
         (get-char scr)

         ;; set the background character for new characters
         ;; the optional argument nil prevents it from being applied to every window cell.
         ;; a newline sets the background till the end of the line.
         (setf (background scr nil) (make-instance 'complex-char :simple-char #\- :color-pair '(:white :green)))
         (format scr "~%Hello again!~%")
         (refresh scr)
         (get-char scr)

         ;; finally, set the background for the whole window.
         ;; the change is applied only to empty cells and to
         ;; characters that have no already set attributes or colors.
         (setf (background scr) (make-instance 'complex-char :simple-char #\. :color-pair '(:green :white)))
         (refresh scr)
         (get-char scr)
         (setf (background scr) (make-instance 'complex-char :simple-char #\- :color-pair '(:red :white)))
         (refresh scr)

         ;; wait for the next keypress, then end.
         (get-char scr))

    ;; unwind protect makes sure that ncurses is ended at all cost.
    (end-screen)))

;; the same as t01, but hides the window creation and ncurses ending by utilizing the with-screen macro.
(defun t02 ()
  (with-screen (scr :color-pair (list :yellow :red))
    (clear scr)
    (move scr 0 0)
    (add-string scr "hello there!")
    (move scr 3 6)
    (add-string scr "dear john!")
    (setf (color-pair scr) '(:red :yellow))
    (move scr 3 3 :relative t)
    (add-string scr "call me maybe!")
    ;; setting the cursor position directly instead of using move
    (setf (cursor-position scr) (list 9 12))
    (add-string scr "welcome to tijuana")
    (refresh scr)
    (get-char scr)
    
    (setf (background scr) (make-instance 'complex-char :simple-char #\. :color-pair '(:green :white)))
    (refresh scr)
    (get-char scr)

    (setf (background scr) (make-instance 'complex-char :simple-char #\, :color-pair '(:white :green)))
    (refresh scr)
    (get-char scr) ))

(defun t02a ()
  "Separately set the window foreground and background color pairs."
  (with-screen (scr :fgcolor :yellow :bgcolor :red)
    (clear scr)
    (move scr 0 0)
    (add-string scr "hello there!")

    (setf (color-pair scr) '(:red :yellow))
    (move scr 3 6)
    (add-string scr "dear john!")

    (setf (fgcolor scr) :yellow
          (bgcolor scr) :red)
    (move scr 3 3 :relative t)
    (add-string scr "call me maybe!")

    ;; setting the cursor position directly instead of using move
    (setf (cursor-position scr) (list 9 12))
    (add-string scr "welcome to tijuana")
    (refresh scr)
    (get-char scr)

    ;; setting fg to green, will set the bg to the default black
    (setf (background scr) (make-instance 'complex-char :simple-char #\. :fgcolor :green))
    (refresh scr)
    (get-char scr)
    ;; setting bg to green will set the fg to the default white
    (setf (background scr) (make-instance 'complex-char :simple-char #\, :bgcolor :green))

    ;; text will still use the window color pair
    (move scr 3 3 :relative t)
    (add scr "hasta siempre")
    
    (refresh scr)
    (get-char scr) ))

(defun t02b ()
  "Set and get a wide character background."
  (with-screen (scr)
    ;;(de.anvi.croatoan::funcall-make-cchar_t-ptr #'%wbkgrnd (winptr scr) 0 0 0 1)

    ;; if we set char 0 as background, ncurses sets char 32 (space), which is obviously the default char.
    ;; also setting 0 leads to the color pair not being accepted.
    ;; TODO: check that only graphic chars are set.
    (setf (background scr) (make-instance 'complex-char :simple-char #x2592 :color-pair '(:yellow :red)))
    (get-char scr)

    (move scr 0 0)
    ;; the low-level function returns the code.
    (let ((ch (de.anvi.croatoan::get-background-cchar_t scr)))
      (format scr "ch: ~A~%" ch)
      (format scr "~A ~A ~A" (char-code (simple-char ch)) (attributes ch) (color-pair ch)))

    (move scr 2 0)
    ;; the high level interface returns what was set by the high-level setf.
    ;; TODO: when we set background to :board, should it return :board or the numeric code point?
    (let ((ch (background scr)))
      (if ch
          (format scr "~A ~A ~A" (simple-char ch) (attributes ch) (color-pair ch))
          (format scr "ch: ~A" ch)))

    (refresh scr)
    (get-char scr)))

(defun t02c ()
  (with-screen (scr :input-blocking t :input-echoing nil :enable-colors t :use-terminal-colors t)
    ;; simple chars added to a window without a rendered style.
    (add-string scr "Hello there!")
    (fresh-line scr) (refresh scr) (get-char scr)

    ;; color-pair applies to newly added text.
    (setf (color-pair scr) '(:red :yellow))
    (add-string scr "Dear John!")
    (fresh-line scr) (refresh scr) (get-char scr)

    ;; removing the color pair puts back the outut into the default state.
    (setf (color-pair scr) '())
    (add-string scr "Open the pod bay door.")
    (fresh-line scr) (refresh scr) (get-char scr)
    
    ;; the background style renders simple text, but it doesnt change the text with a set color pair
    (setf (background scr) (make-instance 'complex-char :simple-char #\. :fgcolor :black :bgcolor :magenta))
    (add-string scr "I can feel it.")
    (fresh-line scr) (refresh scr) (get-char scr)
    
    ;; remove the background char, set back to the default state.
    (setf (background scr) nil)
    (add-string scr "My mind is going.")
    (fresh-line scr) (refresh scr) (get-char scr)

    ;; the empty cells of the last foreground will be overwritten by the next background call.
    (setf (background scr) (make-instance 'complex-char :simple-char #\_ :color-pair '(:black :yellow)))
    (format scr "I'm sorry, Dave.")
    (fresh-line scr) (refresh scr) (get-char scr)))

;; read and display chars until a q is pressed, blocking version.
(defun t03 ()
  (with-screen (scr :input-echoing nil :input-blocking t)
    (clear scr)
    (add-string scr "Type chars. Type q to quit. ")
    (refresh scr)

    (loop for ch = (get-char scr)
       while (not (equal (code-char ch) #\q))
       do (add-char scr ch))

    (add-string scr "You pressed q. Now press any char to quit.")
    (get-char scr)))

;; read and display chars until a q is pressed, non-blocking version (leads to 100% CPU usage).
;; wait for keyboard using get-char makes no sense in non-blocking code because it doesnt wait.
(defun t03a ()
  (with-screen (scr :input-echoing nil :input-blocking nil)
    (clear scr)
    (add-string scr "Type chars. Type q to quit. ")
    (refresh scr)

    (loop for ch = (get-char scr)
       while (or (= ch -1) (not (equal (code-char ch) #\q)))
       do (unless (= ch -1) (add-char scr ch)))))

;; test which integer is returned by ncurses on a non-blocking nil event.
;; when get-char is used, -1 is returned,
;; when get-wide-char is used, 0 is returned.
(defun t03a2 ()
  (with-screen (scr :input-echoing nil :input-blocking nil)
    (clear scr)
    (add-string scr "Type chars. Type q to quit. ")
    (refresh scr)
    (loop for ch = (get-char scr)
       while (not (equal ch 113)) ; 113 = q
       do (princ ch scr))))

;; read and display chars until a q is pressed, non-blocking version (leads to 100% CPU usage).
;; uses get-event for event handling.
(defun t03b ()
  (with-screen (scr :input-echoing nil :input-blocking nil)
    (clear scr)
    (add-string scr "Type chars. Type q to quit. ")
    (refresh scr)

    ;; TODO: should get-event only return single bytes, or should we merge it with get-wide-event?
    (loop (let ((event (get-event scr)))
            (when event
              (case event
                (#\q (return))
                (otherwise (add-char scr (char-code event)))))))))

;; using the event-case macro to simplify the event loop.
;; do not use ((nil) nil) with input-blocking nil, it leads to 100% CPU usage.
;; if nothing happens in the nil case anyway, we can use blocking.
(defun t03b2 ()
  (with-screen (scr :input-echoing nil :input-blocking t)
    (clear scr)
    (add-string scr "Type chars. Type q to quit. ")
    (refresh scr)
    (event-case (scr event)
      ;; ((nil) nil)
      (#\q (return-from event-case))
      (otherwise (add-char scr (char-code event))))))

;; slightly improved t03b2 pasted as an example to the cliki croatoan page
(defun t03b3 ()
  (with-screen (scr :input-echoing nil :input-blocking t :enable-colors t)
    (clear scr)
    (move scr 2 0)
    (format scr "Type chars. Type q to quit.~%~%")
    (refresh scr)
    (setf (color-pair scr) '(:yellow :red)
          (attributes scr) '(:bold))
    (event-case (scr event)
      (#\q (return-from event-case))
      (otherwise (princ event scr)
                 (refresh scr)))))

;; read and display chars until a q is pressed, blocking + gray stream version.
;; the stream reading function wont work in non-blocking mode and with non-char keys.
(defun t03c ()
  (with-screen (scr :input-echoing nil :input-blocking t)
    (clear scr)
    (princ "Type chars. Type q to quit." scr)
    (refresh scr)

    (loop for ch = (read-char scr)
       while (not (equal ch #\q))
       do (princ ch scr))

    (princ "You pressed q. Now press any char to quit." scr)
    (read-char scr)))

(defun t03d ()
  "Read and display wide (multi-byte) characters until q is pressed."
  (with-screen (scr :input-echoing nil :input-blocking t :enable-colors t :cursor-visible nil)
    (clear scr)
    (refresh scr)
    (loop for ch = (get-wide-char scr)
       while (not (equal (code-char ch) #\q))
       do
         (clear scr)
         ;; display the human-readable version of a wide char.
         (add-wide-char scr ch             :attributes (list :underline) :fgcolor :yellow :bgcolor :red  :y 0 :x 0)
         (add-wide-char scr (code-char ch) :attributes (list :bold)      :color-pair (list :yellow :red) :y 0 :x 2)
         ;; extract the wide char again.
         (let ((ch2 (extract-wide-char scr :y 0 :x 0)))
           ;; display the lisp-readable version of the extracted complex wide char
           (move scr 1 0)
           ;; TODO: prin1, print and ~S should print unreadable #<..>
           ;; only princ and ~A should render complex chars
           (prin1 (simple-char ch2) scr)
           ;; print the slots of the extracted complex wide char
           (princ (attributes ch2) scr)
           (princ (color-pair ch2) scr)
           ;; display the rendered complex wide char again
           (add-wide-char scr ch2 :y 3 :x 0) ))))

;; gray stream version of t03d
;; we can not use ~C and write-char to write complex-chars, but it works for wide chars, which are normal lisp chars.
;; princ and ~A should work, because they rely on specialized print-object.
;; also see t08c
(defun t03d2 ()
  "Use gray stream functions to read and display wide (multi-byte) characters until q is pressed."
  (with-screen (scr :input-echoing nil :input-blocking t :enable-colors t :cursor-visible nil)
    (clear scr)
    (refresh scr)
    (loop for ch = (read-char scr)
       while (not (equal ch #\q))
       do
         (clear scr)
         (move scr 0 0)
         ;; we only can write-char if it is a lisp character.
         (write-char ch scr)
         ;; the extracted "char" is not a lisp character any more, but a complex-char
         (let ((ch2 (extract-wide-char scr :y 0 :x 0))
               (*standard-output* scr))
           ;; this soon will not work with sbcl and complex chars.
           ;; write-char only takes characters, no other objects.
           ;; even if we specialize stream-write-char to complex-chars
           ;;(move scr 1 0)
           ;;(write-char ch2 scr)
           ;; this will work because it uses print-objectm, since print-object can be
           ;; specialized on complex-chars.
           (move scr 1 0)
           (princ ch scr)
           (princ ch)
           (move scr 2 0)
           (princ ch2 scr)
           (princ ch2)
           ;; aestethic ~A can be used, since it uses princ underneath
           ;; standard ~S can not  because it cant be read back in
           ;; character ~C can not be used because it requires characters.
           (move scr 3 0)
           (format scr "~A ~A" ch ch2)
           (move scr 4 0)
           (format t "~A ~A" ch ch2)
           (refresh scr) ))))

;;           | cooked | cbreak | raw
;; ----------+--------+--------+-----
;; buffering | t      | nil    | nil
;; ----------+--------+--------+-----
;; control   | t      | t      | nil 
;;
(defun t03e ()
  "Test switching between input modes and control char processing."
  (with-screen (scr :input-echoing nil :input-blocking t :input-buffering nil :process-control-chars nil)
    (with-accessors ((input-buffering input-buffering-p) (process-control-chars process-control-chars-p)) scr
      (clear scr)
      (format scr "buffering ~A process-control-chars ~A (raw)~%" input-buffering process-control-chars)
      (format scr "ch1: ~A~%" (get-char scr))
      (setf (process-control-chars-p scr) t)
      (format scr "buffering ~A process-control-chars ~A (cbreak)~%" input-buffering process-control-chars)
      (format scr "ch2: ~A~%" (get-char scr))
      (setf (input-buffering-p scr) t)
      (format scr "buffering ~A process-control-chars ~A (cooked)~%" input-buffering process-control-chars)
      (format scr "ch3: ~A~%" (get-char scr))
      ;; ignore the typed enter key
      (get-char scr)
      ;; wait for one keypress before exiting.
      (get-char scr))))

;; take a function given as symbol name and display its docstring. press q to exit.
;; Example: (a:t04 'cdr)
(defun t04 (&optional (name 'car))
  (unwind-protect
       (let ((scr (make-instance 'screen)))
         (clear scr)
         (refresh scr)

         (setf (attributes scr) '(:bold :reverse))
         (add-string scr "Docstring")

         (setf (attributes scr) '())
         (add-string scr " --> ")

         (setf (attributes scr) '(:bold :underline))
         (add-string scr (format nil "~A~%~%" name))

         (setf (attributes scr) '())
         (add-string scr (documentation name 'function))

         (refresh scr)
         (get-char scr))
    (end-screen)))

;; adding and removing attributes.
(defun t04a ()
  (unwind-protect
       (let ((scr (make-instance 'screen :enable-colors t)))
         (clear scr)
         (refresh scr)

         (add-string scr (write-to-string (attributes scr)))
         (add-char scr (char-code #\newline))

         (pushnew :bold (attributes scr))
         (add-string scr (write-to-string (attributes scr)))
         (add-char scr (char-code #\newline))

         (pushnew :underline (attributes scr))
         (add-string scr (write-to-string (attributes scr)))
         (add-char scr (char-code #\newline))

         (pushnew :reverse (attributes scr))
         (add-string scr (write-to-string (attributes scr)))
         (add-char scr (char-code #\newline))

         (setf (attributes scr) (remove :reverse (attributes scr)))
         (add-string scr (write-to-string (attributes scr)))
         (add-char scr (char-code #\newline))

         (setf (attributes scr) (remove :underline (attributes scr)))
         (add-string scr (write-to-string (attributes scr)))
         (add-char scr (char-code #\newline))

         (setf (attributes scr) (remove :bold (attributes scr)))
         (add-string scr (write-to-string (attributes scr)))
         (add-char scr (char-code #\newline))

         (refresh scr)
         (get-char scr))
    (end-screen)))

;; a more concise way to write t04a
(defun t04b ()
  (with-screen (scr :enable-colors t :cursor-visible nil)
    (clear scr)

    (print (attributes scr) scr)

    (add-attributes scr '(:bold))
    (print (attributes scr) scr)

    (add-attributes scr '(:underline))
    (print (attributes scr) scr)

    (add-attributes scr '(:reverse))
    (print (attributes scr) scr)

    (remove-attributes scr '(:bold :underline :reverse))
    (print (attributes scr) scr)

    (add-attributes scr '(:bold :underline :reverse))
    (print (attributes scr) scr)
    
    (setf (attributes scr) nil)
    (print (attributes scr) scr)
    (print "test" scr)

    (move scr 6 10)
    (change-attributes scr 10 '())
    
    (refresh scr)
    (get-char scr)))

;; Make sure we shut down ncurses and dont mess up the terminal when an error is signaled.
(defun t05 ()
  (unwind-protect
       (let ((scr (make-instance 'screen)))
         (add-string scr "press any char to signal an error and go to the debugger in a messed up screen!")
         (refresh scr)
         (get-char scr)
         (error "zu huelf!"))
    ;; this will be executed after we somehow return from the debugger.
    (end-screen)))

;; End ncurses cleanly _before_ getting into the debugger when an error is signalled.
;; do get into the debugger, but only after we are back to the repl.
(defun t06 ()
  (let ((*debugger-hook* #'(lambda (c h) (declare (ignore c)) (declare (ignore h)) (end-screen))))
    (unwind-protect
         (let ((scr (make-instance 'screen)))
           (add-string scr "hello there! press a char to signal an error and go to the debugger without messung up the screen!")
           (refresh scr)
           (get-char scr)
           (error "zu huelf!"))
      (end-screen))))

;; The debugger hook is added now to with-screen.
;; When an error is signalled in a ncurses app, we cleanly exit ncurses first, we dont get
;; into the debugger and we merely print the signalled condition to the REPL.
(defun t06a ()
  (with-screen (scr)
    (add-string scr "hello there! press a char to signal an error. the app will be ended without going to the debugger.")
    (refresh scr)
    (get-char scr)
    (error "zu huelf!")))

;; display a new window on the stadard screen.
;; problem: deleting different windows is not standardized. (problem solved, example obsolete)
(defun t07 ()
  (unwind-protect
       (let ((scr (make-instance 'screen :enable-colors t)))
         (clear scr)
         (add-string scr "Standard screen")
         (refresh scr)
         (get-char scr)

         (let ((win (make-instance 'window :height 15 :width 50 :location '(5 5))))
           (setf (background win) (make-instance 'complex-char :color-pair '(:red :blue)))
           (add-string win "Window 1")
           (refresh win)
           (get-char win)))
           ;(delete-window win) ; this is missing, we have to properly delete windows manually.

    ;; this will be executed after we somhow return from the debugger.
    (end-screen)))

;; close now closes both windows and the main screen.
;; but the creation of a window/screen now has to be outside the unwind-protect form.
(defun t07a ()
  (let ((scr (make-instance 'screen :enable-colors t :cursor-visible nil)))
    ;; since windows are streams now, we can use close on tem too.
    ;; in order to be able to close scr, as compared to end-screen without arguments in t07,
    ;; we have to move unwind-protect inside the let scope.
    (unwind-protect
         (progn
           (clear scr)
           (add-string scr "Standard screen")
           (refresh scr)
           (get-char scr)

           (let ((win (make-instance 'window :height 15 :width 50 :location '(5 5) :draw-border t)))
             (setf (background win) (make-instance 'complex-char :simple-char #\: :color-pair '(nil :blue)))
             (add-string win "Window 1" :y 2 :x 4 :fgcolor :red :bgcolor :yellow)
             (refresh win)
             (get-char win)
             (close win))

           (clear scr)
           (refresh scr)
           (get-char scr))
      ;; close is defined by the gray stream interface.
      (close scr)))) 

;; test the gray streams interface.
(defun t08 ()
  (unwind-protect
       (let ((scr (make-instance 'screen)))
         (clear scr)

         (write-char #\a)     ; writes a to the repl. wont be visible until we quit ncurses.
         
         (write-char #\b scr) ; writes b to scr.
         (terpri scr)
         
         (princ "hello")      ; writes to the repl. wont be visible until we quit ncurses.

         (princ "hello" scr)
         (terpri scr)         ; 3 calls, 3 new lines will be added.
         (terpri scr)
         (terpri scr)

         (princ "there" scr)

         (terpri scr)
         (write-string "dear john" scr :start 0 :end 4)
         (terpri scr)
         (write-string "dear john" scr :start 5)
         
         (fresh-line scr)     ; we call it 3 times, but only one newline will be added.
         (fresh-line scr)
         (fresh-line scr)

         (format scr "--~%~r~%--" 1234) ; each % will add a newline.

         (write-char #\newline scr) ; this is the char that actually gets displayed each time terpri is called.

         (refresh scr)
         (get-char scr))
    (end-screen)))

;; when we define windows as streams, we can rebind the *standard-output* to print to a ncurses window. 
;; this only works with standard lisp output functions, format, write-char, terpri, print, etc.
;; we still have to explicitely ncurses functions clear, close, refresh, etc. though.
(defun t08a ()
  (let ((scr (make-instance 'screen)))
    (unwind-protect
         (progn
           (clear scr)

           ;; writes explicitely to scr
           (write-char #\a scr)
           (terpri scr)
           (princ "hello" scr)
           (terpri scr)
           (format scr "~r" 1234)
           (terpri scr)

           ;; writes to *standard-output*, which here is scr.
           (let ((*standard-output* scr))
             (write-char #\b)
             (terpri)
             (princ "there")
             (terpri)
             (format t "~r" 5678)
             (terpri))

           (refresh scr)
           (get-char scr))
      (close scr))))

(defun t08b ()
  "Use colors on *standard-output* with standard lisp printing functions.

Test whether a window (stream) was closed."
  (let* ((scr (make-instance 'screen :enable-colors t))
         (*standard-output* scr))
    (unwind-protect
         (progn
           (clear scr)
           (setf (background scr) (make-instance 'complex-char :color-pair '(:black :white)))
           (format t "~r" 1984)
           (refresh scr)
           (get-char scr)

           ;; temporarily bind *standard-output* to a window.
           (let* ((win (make-instance 'window :height 15 :width 50 :location '(5 5)))
                  (*standard-output* win))
             (setf (background win) (make-instance 'complex-char :color-pair '(:white :black)))
             (format t "~r" 1985)
             (refresh win)
             (get-char win)
             (format t "~%before close: streamp: ~A open-stream-p: ~A" (streamp win) (open-stream-p win))
             (refresh win)
             (get-char win)
             (format scr "~%after close: close: ~A streamp: ~A open-stream-p: ~A" (close win) (streamp win) (open-stream-p win))
             (refresh scr)
             (get-char scr))

           ;; *standard-output*is now again scr.
           (setf (background scr) (make-instance 'complex-char :color-pair '(:black :white)))
           (terpri)
           (format t "~r" 1984)
           (refresh scr)
           (get-char scr))
      (close scr))))

;; Write complex chars to standard output using standard lisp output functions + gray streams.
;; According to SBCL devs, write-char and ~C are not supposed to work, because according to the
;; standard, only lisp characters should be accepted s arguments by write-char, and any other
;; object like a complex-char should signal an error.
(defun t08c ()
  (with-screen (scr)
    (let ((*standard-output* scr)
          (ch (make-instance 'complex-char :simple-char #\a :attributes '(:bold :underline) :color-pair '(:green :black))))
      (write-char #\a)
      (terpri)
      ;;(write-char ch)
      (terpri)
      (princ ch)
      (terpri)
      (print ch)
      (terpri)
      (format t "~%Format:~%~S~%~A" ch ch)
      (terpri))
    (refresh scr)
    (get-char scr)))

;; box, move
(defun t09 ()
  (let* ((scr (make-instance 'screen :enable-colors t)))
    (unwind-protect
         (progn
           (clear scr)

           (setf (background scr) (make-instance 'complex-char :color-pair '(:black :white)))
           (box scr)
           (move scr 1 1)
           (princ 0 scr)

           (refresh scr)

           (let ((w1 (make-instance 'window :height 10 :width 30 :location '(3 5)))
                 (w2 (make-instance 'window :height 10 :width 30 :location '(6 10)))
                 (w3 (make-instance 'window :height 10 :width 30 :location '(9 15))))

             (setf (background w1) (make-instance 'complex-char :color-pair '(:white :black)))
             (setf (background w2) (make-instance 'complex-char :color-pair '(:black :white)))
             (setf (background w3) (make-instance 'complex-char :color-pair '(:white :black)))

             (box w1)
             (box w2)
             (box w3)

             (move w1 1 1)
             (princ 1 w1)
             (move w2 1 1)
             (princ 2 w2)
             (move w3 1 1)
             (princ 3 w3)

             (refresh w1)
             (refresh w2)
             (refresh w3)

             (get-char w1)

             ;; todo: before we can refresh w2 to raise it, we have to "touch" it.
             ;; if we dont touch it, only the changed parts of it will be redrawn.
             (touch w2)
             (refresh w2)

             (get-char w1)

             ;; move the whole window 3. note that this doesnt refresh the windows below,
             ;; they have to be refreshed separately.
             (setf (location w3) '(9 20))
             (refresh w3)
             (get-char w3)

             (close w1)
             (close w2)
             (close w3))

           (setf (background scr) (make-instance 'complex-char :color-pair '(:black :white)))

           (refresh scr)
           (get-char scr))

      (close scr))))

;; the same as t09, but we can now raise the overlapping windows by hitting 1, 2 or 3.
(defun t09a ()
  (let* ((scr (make-instance 'screen :enable-colors t :input-blocking t :input-echoing nil ;;:use-terminal-colors t
                             :cursor-visible nil)))
    (unwind-protect
         (progn
           (clear scr)

           (box scr)
           (refresh scr)

           ;; the default value for width or height is "to the end of the screen".
           (let ((w1 (make-instance 'window :height 10 :width 30 :location '(3 5)))
                 (w2 (make-instance 'window            :width 30 :location '(6 10)))
                 (w3 (make-instance 'window :height 10           :location '(9 15))))

             (box w1)
             (box w2)
             (box w3)

             ;; after we define a default pair, :terminal -1 refers to these colors.
             (setf (use-terminal-colors-p scr) t)
             ;;(setf (default-color-pair scr) (list :yellow :red))

             (setf (background w1) (make-instance 'complex-char :color-pair '(:default-fg :yellow)))
             ;; window w2 uses the :default fg and bg color of the terminal, because use-terminal-colors is t.
             (setf (background w3) (make-instance 'complex-char :color-pair '(:yellow :terminal)))

             ;; print currently active color pairs to w3
             (format w3 "~A" de.anvi.croatoan::*color-pair-alist*)
             
             ;; TODO: clear, refresh, etc, should take one or more windows as arguments.
             ;; so we can do (refresh w1 w2 w3) instead of:
             (refresh w1)
             (refresh w2)
             (refresh w3)

             (loop (let ((event (get-event scr)))
                     (when event
                       (case event
                         (#\1 (touch w1) (refresh w1))
                         (#\2 (touch w2) (refresh w2))
                         (#\3 (touch w3) (refresh w3))
                         (#\q (return))
                         (otherwise nil)))))

             (close w1)
             (close w2)
             (close w3)))

      (close scr))))

(defun t09b ()
  "Use with-screen, event-case and mapc to simplify t09a."
  (with-screen (scr :input-blocking t :input-echoing nil :enable-colors t :cursor-visible nil)
    (box scr)
    (refresh scr)
    
    (let ((w1 (make-instance 'window :height 10 :width 30 :location '(3 5)  :draw-border t))
          (w2 (make-instance 'window            :width 30 :location '(6 10) :draw-border t))
          (w3 (make-instance 'window :height 10           :location '(9 15) :draw-border t)))

      (setf (background w1) (make-instance 'complex-char :simple-char #\space :color-pair '(:white :black))
            (background w3) (make-instance 'complex-char :simple-char #\space :color-pair '(:white :black)))

      (mapc #'refresh (list w1 w2 w3))

      (event-case (scr event)
        (#\1 (touch w1) (refresh w1))
        (#\2 (touch w2) (refresh w2))
        (#\3 (touch w3) (refresh w3))
        (#\q (return-from event-case))
        (otherwise nil))

      (mapc #'close (list w1 w2 w3))

      ;; return nil explicitely, so it doesnt return the window list.
      nil)))

;; https://www.gnu.org/software/guile-ncurses/manual/html_node/Panels-Basics.html#Panels-Basics
;; https://www.gnu.org/software/guile-ncurses/manual/html_node/The-curses-panel-library.html
(defun t09c ()
  "Use a window stack to manage overlapping windows."
  (with-screen (scr :input-blocking t :input-echoing nil :enable-colors t :enable-function-keys t :cursor-visible nil :stacked t)
    (box scr)
    (setf (background scr) (make-instance 'complex-char :simple-char #\space :color-pair '(:black :white)))
    ;; we have to stack scr because the event loop runs on scr, and this refreshes scr implicitely every time
    ;; and overlaps the other windows
    ;;(setf (stackedp scr) t)

    (let ((winlst nil)
          (n 0))
      ;; create 8 windows (with 8 different background colors),
      ;; add them to the local winlist and to the global stack
      (loop for i from 0 to 7 do
           (push (make-instance 'window :height 10 :width 30 :location (list (+ 3 (* i 1)) (+ 3 (* i 3))) :draw-border t :stacked t)
                 winlst))
      ;; number them and set the background colors
      (loop for i from 0 to 7 do
           (setf (attributes (nth i winlst)) (list :reverse))
           (format (nth i winlst) "~A" i)
           (setf (background (nth i winlst))
                 (make-instance 'complex-char :simple-char #\space :color-pair (list :black (nth i *ansi-color-list*)))))
           ;;(setf (stackedp (nth i winlst)) t)
      (refresh-stack)

      (event-case (scr event)
        ;; type 0-7 to pick a window
        ((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7)
         ;; show the picked window number in the upper right screen corner
         (add-char scr event :y 1 :x 78 :color-pair (list :white :black))
         (setf n (- (char-code event) 48)))
        ;; to raise a window to the top of the stack
        (#\t
         (raise-to-top (nth n winlst))
         (refresh-stack))
        (#\r
         (raise (nth n winlst))
         (refresh-stack))
        (#\l
         (lower (nth n winlst))
         (refresh-stack))
        (#\b
         (lower-to-bottom (nth n winlst))
         (refresh-stack))
        ;; type v to toggle window visibility         
        (#\v
         ;; toggle visibility for window n
         (setf (visiblep (nth n winlst)) (not (visiblep (nth n winlst))))
         (refresh-stack))
        (#\q (return-from event-case))
        (otherwise nil))

      ;; before closing them, remove all windows from the stack, so they can be GCd.
      (empty-stack)
      (mapc #'close winlst)
      ;; return nil explicitely, so it doesnt return the window list.
      nil)))

;; port of kletva/test05.
;; print the screen size.
;; print the keys and key codes of any key pressed.
;; TODO: when we reach the end of the screen, the ~% doesnt print newlines any more and the screen doesnt scroll.
;; see scroll.lisp. a lot of window options, including scrolling, arent closified yet.
;; enable-function-keys t: fkeys have codes 255+
;; enable-function-keys nil: fkeys are multi-char escape codes.
(defun t10 ()
  (let ((scr (make-instance 'screen :input-echoing nil :enable-function-keys t)))
    (unwind-protect
         (progn
           (clear scr)
           
           (format scr "~A lines high, ~A columns wide.~%~%" (height scr) (width scr))
           (refresh scr)

           (loop
              ;; act only on events, do nothing on non-events.
              (when (key-pressed-p scr)
                  (let ((ch (get-char scr)))
                    ;; quit on q, print everything else including function keys.
                    (cond ((equal (code-char ch) #\q) (return))
                          (t (format scr "Code: ~A, Char: ~A~%" ch (code-char ch))))))))
      (close scr))))

;; cleaner key printing.
;; prints key names instead of numeric char codes.
(defun t10a ()
  (with-screen (scr :input-echoing nil :input-blocking t :enable-function-keys t
                    :input-buffering nil :process-control-chars nil :enable-newline-translation t)
    (setf (scrolling-enabled-p scr) t)
    (format scr "~A lines high, ~A columns wide.~%~%" (height scr) (width scr))
    (loop (let ((event (get-event scr)))
            (when event
              (case event
                (#\q (return))
                
                ;; non-printable ascii chars. (#\space and #\newline are standard, all others non-standard)
                (#\escape (format scr "escape char ESC ^[ \e~%"))
                (#\tab (format scr "horizontal tab char HT \t~%"))
                (#\space (format scr "space char~%"))
                
                ;; NL can be either LF \n,CR \r,or CRLF \r\n, depending on the system. it is LF on ubuntu.
                ;; NL is the standard, system independent, portable way.
                (#\linefeed (format scr "enter/linefeed LF ^J \n char~%"))
                (#\return (format scr "enter/return CR ^M \r char~%"))
                (#\newline (format scr "enter/newline char~%")) ; = #\linefeed, code 10.

                ;; use %nonl to prevent ^M (CR) from automatically being translated to ^J (LF)
                (#\n (setf (newline-translation-enabled-p scr)
                           (not (newline-translation-enabled-p scr)))
                     (if (newline-translation-enabled-p scr)
                         (format scr "%nl newline translation enabled: RET => NL (= LF)~%")
                         (format scr "%nonl newline translation disabled: RET => CR~%")))

                (#\rubout (format scr "rubout char ^?~%")) ;; DEL, ^?, delete char 127
                (#\backspace (format scr "backspace char ^H~%")) ;; BS, \b, ^H, code 8, not the same as the :backspace key

                (:backspace (format scr "backspace key <--~%")) 
                (otherwise (format scr "Event: ~A~%" event))))))))

;; demonstrate scrolling and scrolling regions.
(defun t11 ()
  (let ((scr (make-instance 'screen)))
    (unwind-protect
         (progn
           (clear scr)
           (refresh scr)

           ;; (move scr 5 5)
           (setf (cursor-position scr) '(5 5))

           ;; see inopts.
           (setf 

            ;; this is sufficient to make the whole window scroll.
            ;;(scrolling-enabled-p scr t)
            ;;(%scrollok (winptr scr) t)
            (scrolling-enabled-p scr) t 

            ;; to make only a few lines (5 to 10) scroll, we have to set a line-based region.
            ;;(set-scrolling-region scr 5 10)
            ;;(%wsetscrreg (winptr scr) 5 10)
            (scrolling-region scr) '(5 10))

           (loop for i from 0 to 30 do
                (format scr "~A~%" i)
                (refresh scr)
                (sleep 0.2)))

      (close scr))))

;; Display available ACS (alternative character set) pseudo-graphical characters.
(defun t12 ()
  (let ((scr (make-instance 'screen)))
    (unwind-protect
         (progn

(add-string scr "ACS_ULCORNER ") (add-char scr (acs :upper-left-corner    )) (add-string scr " upper left corner") (new-line scr) 
(add-string scr "ACS_LLCORNER ") (add-char scr (acs :lower-left-corner    )) (add-string scr " lower left corner ") (new-line scr)
(add-string scr "ACS_URCORNER ") (add-char scr (acs :upper-right-corner   )) (add-string scr " upper right corner ") (new-line scr)
(add-string scr "ACS_LRCORNER ") (add-char scr (acs :lower-right-corner   )) (add-string scr " lower right corner ") (new-line scr)
(add-string scr "ACS_LTEE     ") (add-char scr (acs :tee-pointing-right   )) (add-string scr " tee pointing right ") (new-line scr)
(add-string scr "ACS_RTEE     ") (add-char scr (acs :tee-pointing-left    )) (add-string scr " tee pointing left ") (new-line scr)
(add-string scr "ACS_BTEE     ") (add-char scr (acs :tee-pointing-up      )) (add-string scr " tee pointing up ") (new-line scr)
(add-string scr "ACS_TTEE     ") (add-char scr (acs :tee-pointing-down    )) (add-string scr " tee pointing down ") (new-line scr)
(add-string scr "ACS_HLINE    ") (add-char scr (acs :horizontal-line      )) (add-string scr " horizontal line ") (new-line scr)
(add-string scr "ACS_VLINE    ") (add-char scr (acs :vertical-line        )) (add-string scr " vertical line ") (new-line scr)
(add-string scr "ACS_PLUS     ") (add-char scr (acs :crossover-plus       )) (add-string scr " large plus or crossover ") (new-line scr)
(add-string scr "ACS_S1       ") (add-char scr (acs :scan-line-1          )) (add-string scr " scan line 1 ") (new-line scr)
(add-string scr "ACS_S3       ") (add-char scr (acs :scan-line-3          )) (add-string scr " scan line 3 ") (new-line scr)
(add-string scr "ACS_S7       ") (add-char scr (acs :scan-line-7          )) (add-string scr " scan line 7 ") (new-line scr)
(add-string scr "ACS_S9       ") (add-char scr (acs :scan-line-9          )) (add-string scr " scan line 9 ") (new-line scr)
(add-string scr "ACS_DIAMOND  ") (add-char scr (acs :diamond-symbol       )) (add-string scr " diamond ") (new-line scr)
(add-string scr "ACS_BOARD    ") (add-char scr (acs :board                )) (add-string scr " board of squares ") (new-line scr)
(add-string scr "ACS_CKBOARD  ") (add-char scr (acs :checker-board        )) (add-string scr " checker board (stipple) ") (new-line scr)
(add-string scr "ACS_DEGREE   ") (add-char scr (acs :degree-symbol        )) (add-string scr " degree symbol ") (new-line scr)
(add-string scr "ACS_PLMINUS  ") (add-char scr (acs :plus-minus           )) (add-string scr " plus/minus ") (new-line scr)
(add-string scr "ACS_BULLET   ") (add-char scr (acs :bullet-symbol        )) (add-string scr " bullet ") (new-line scr)
(add-string scr "ACS_LARROW   ") (add-char scr (acs :arrow-pointing-left  )) (add-string scr " arrow pointing left ") (new-line scr)
(add-string scr "ACS_RARROW   ") (add-char scr (acs :arrow-pointing-right )) (add-string scr " arrow pointing right ") (new-line scr)
(add-string scr "ACS_DARROW   ") (add-char scr (acs :arrow-pointing-down  )) (add-string scr " arrow pointing down ") (new-line scr)
(add-string scr "ACS_UARROW   ") (add-char scr (acs :arrow-pointing-up    )) (add-string scr " arrow pointing up ") (new-line scr)
(add-string scr "ACS_LANTERN  ") (add-char scr (acs :lantern-symbol       )) (add-string scr " lantern symbol ") (new-line scr)
(add-string scr "ACS_BLOCK    ") (add-char scr (acs :solid-square-block   )) (add-string scr " solid square block ") (new-line scr)
(add-string scr "ACS_LEQUAL   ") (add-char scr (acs :less-than-or-equal   )) (add-string scr " less/equal ") (new-line scr)
(add-string scr "ACS_GEQUAL   ") (add-char scr (acs :greater-than-or-equal)) (add-string scr " greater/equal ") (new-line scr)
(add-string scr "ACS_PI       ") (add-char scr (acs :pi                   )) (add-string scr " Pi ") (new-line scr)
(add-string scr "ACS_NEQUAL   ") (add-char scr (acs :not-equal            )) (add-string scr " not equal ") (new-line scr)
(add-string scr "ACS_STERLING ") (add-char scr (acs :uk-pound-sterling    )) (add-string scr " UK pound sign") (new-line scr)

           (get-char scr))
      (close scr))))

;; after adding gray streams and adding acs to add-char.
;; http://melvilletheatre.com/articles/ncurses-extended-characters/index.html
(defun t12a ()
  (with-screen (scr)
    (add-char scr :upper-left-corner    ) (format scr " ACS_ULCORNER / upper left corner      ~%")
    (add-char scr :lower-left-corner    ) (format scr " ACS_LLCORNER / lower left corner      ~%")
    (add-char scr :upper-right-corner   ) (format scr " ACS_URCORNER / upper right corner     ~%")
    (add-char scr :lower-right-corner   ) (format scr " ACS_LRCORNER / lower right corner     ~%")   
    (add-char scr :tee-pointing-right   ) (format scr " ACS_LTEE     / tee pointing right     ~%")
    (add-char scr :tee-pointing-left    ) (format scr " ACS_RTEE     / tee pointing left      ~%")   
    (add-char scr :tee-pointing-up      ) (format scr " ACS_BTEE     / tee pointing up        ~%")
    (add-char scr :tee-pointing-down    ) (format scr " ACS_TTEE     / tee pointing down      ~%")
    (add-char scr :horizontal-line      ) (format scr " ACS_HLINE    / horizontal line        ~%")   
    (add-char scr :vertical-line        ) (format scr " ACS_VLINE    / vertical line          ~%")
    (add-char scr :crossover-plus       ) (format scr " ACS_PLUS     / large plus or crossover~%")
    (add-char scr :scan-line-1          ) (format scr " ACS_S1       / scan line 1            ~%")
    (add-char scr :scan-line-3          ) (format scr " ACS_S3       / scan line 3            ~%")
    (add-char scr :scan-line-7          ) (format scr " ACS_S7       / scan line 7            ~%")
    (add-char scr :scan-line-9          ) (format scr " ACS_S9       / scan line 9            ~%")
    (add-char scr :diamond-symbol       ) (format scr " ACS_DIAMOND  / diamond                ~%")
    (add-char scr :board                ) (format scr " ACS_BOARD    / board of squares       ~%")
    (add-char scr :checker-board        ) (format scr " ACS_CKBOARD  / checker board (stipple)~%")
    (add-char scr :degree-symbol        ) (format scr " ACS_DEGREE   / degree symbol          ~%")
    (add-char scr :plus-minus           ) (format scr " ACS_PLMINUS  / plus/minus             ~%")
    (add-char scr :bullet-symbol        ) (format scr " ACS_BULLET   / bullet                 ~%")
    (add-char scr :arrow-pointing-left  ) (format scr " ACS_LARROW   / arrow pointing left    ~%")
    (add-char scr :arrow-pointing-right ) (format scr " ACS_RARROW   / arrow pointing right   ~%")
    (add-char scr :arrow-pointing-down  ) (format scr " ACS_DARROW   / arrow pointing down    ~%")
    (add-char scr :arrow-pointing-up    ) (format scr " ACS_UARROW   / arrow pointing up      ~%")
    (add-char scr :lantern-symbol       ) (format scr " ACS_LANTERN  / lantern symbol         ~%")
    (add-char scr :solid-square-block   ) (format scr " ACS_BLOCK    / solid square block     ~%")
    (add-char scr :less-than-or-equal   ) (format scr " ACS_LEQUAL   / less/equal             ~%")
    (add-char scr :greater-than-or-equal) (format scr " ACS_GEQUAL   / greater/equal          ~%")
    (add-char scr :pi                   ) (format scr " ACS_PI       / Pi                     ~%")
    (add-char scr :not-equal            ) (format scr " ACS_NEQUAL   / not equal              ~%")
    (add-char scr :uk-pound-sterling    ) (format scr " ACS_STERLING / UK pound sign          ~%")

    (refresh scr)
    (get-char scr)))

;; Smaller ACS example to explain how the ACS system works.
(defun t12b ()
  (with-screen (scr)
    ;; acs takes a keyword :ulcorner, translates it to a char #\l,
    ;; ncurses translates it at run-time to a terminal-dependent
    ;; integer value that doesnt correspond to a char code.
    (format scr "~A ~A ~A " #\l (char-code #\l) (acs :upper-left-corner))
    ;; We cant output it in a string, but have to use add-char.
    (add-char scr 4194412)
    (terpri scr)
    (refresh scr)
    (get-char scr)))

;; Show the longer symbol names next to the ACS chars in a more readable table view.
(defun t12c ()
  (with-screen (scr)
    (loop for i from 0 for symbol in
         '(:upper-left-corner
           :lower-left-corner
           :upper-right-corner
           :lower-right-corner
           :tee-pointing-right
           :tee-pointing-left
           :tee-pointing-up
           :tee-pointing-down
           :horizontal-line
           :vertical-line)
       do (progn (move scr (+ 2 (* i 2)) 2)
                 (add-char scr symbol)
                 (format scr "  ~S~%~%" symbol)))
    
    (loop for i from 0 for symbol in
         '(:crossover-plus
           :scan-line-1
           :scan-line-3
           :scan-line-7
           :scan-line-9
           :diamond-symbol
           :board
           :checker-board
           :degree-symbol
           :plus-minus
           :bullet-symbol)
       do (progn (move scr (+ 2 (* i 2)) 30)
                 (add-char scr symbol)
                 (format scr "  ~S~%~%" symbol)))

    (loop for i from 0 for symbol in
         '(:arrow-pointing-left
           :arrow-pointing-right
           :arrow-pointing-down
           :arrow-pointing-up
           :lantern-symbol
           :solid-square-block
           :less-than-or-equal
           :greater-than-or-equal
           :pi
           :not-equal
           :uk-pound-sterling)
       do (progn (move scr (+ 2 (* i 2)) 55)
                 (add-char scr symbol)
                 (format scr "  ~S~%~%" symbol)))
    (refresh scr)
    (get-char scr)))

(defun t12c2 ()
  "ACS chars not available on all terminals: thick and double line drawing characters.

Tested with xterm, gnome-terminal, st. Doesn't work in the linux console, aterm, eterm."
  (with-screen (scr)
    (loop for i from 0 for symbol in
         '(:thick-upper-left-corner
           :thick-lower-left-corner
           :thick-upper-right-corner
           :thick-lower-right-corner
           :thick-tee-pointing-right
           :thick-tee-pointing-left
           :thick-tee-pointing-up
           :thick-tee-pointing-down
           :thick-horizontal-line
           :thick-vertical-line
           :thick-crossover-plus)
       do (progn (move scr (+ 1 (* i 2)) 2)
                 (add-char scr symbol)
                 (format scr "  ~S~%~%" symbol)))

    (loop for i from 0 for symbol in
         '(:double-upper-left-corner
           :double-lower-left-corner
           :double-upper-right-corner
           :double-lower-right-corner
           :double-tee-pointing-right
           :double-tee-pointing-left
           :double-tee-pointing-up
           :double-tee-pointing-down
           :double-horizontal-line
           :double-vertical-line
           :double-crossover-plus)
       do (progn (move scr (+ 1 (* i 2)) 40)
                 (add-char scr symbol)
                 (format scr "  ~S~%~%" symbol)))
    (refresh scr)
    (get-char scr)))

(defun t12d ()
  "Print all graphic alternative chars supported by the non-wide acs_map[128] array.

Only the chars 33 to 126 are graphic, and not all are accessible through the
keywords provided by ncurses, and the supported chars are terminal dependent."
  (with-screen (scr)
    (let* ((ptr (foreign-symbol-pointer "acs_map"))
           (code (mem-aref ptr :unsigned-int (char-code #\l))))
      (format scr "~A ~A ~A ~A " #\l (char-code #\l) (acs :upper-left-corner) code)
      ;; l 108 4194412 4194412 ***
      ;; 4194412 is nothing more than 108 with the A_ALTCHARSET bit turned on.
      (add-char scr code)
      (add-char scr (acs :upper-left-corner))
      (add-char scr #\l :attributes (list :altcharset))
      (terpri scr)
      ;; so we do not really need to access acs_map, like acs is doing.
      ;; we just need to translate chars like #\l to :upper-left-corner
      (loop for i from 33 to 126 do
           (add-char scr (code-char i))
           (add-char scr #\space))
      (terpri scr)
      (loop for i from 33 to 126 do
           (add-char scr (mem-aref ptr :unsigned-int i))
           (add-char scr #\space))
      (refresh scr)
      (get-char scr))))

;; Demonstrate flash and beep alerts.
;; It depends on the terminal emulator whether they will work for you.
;; They both worked in xterm for me.
(defun t13 ()
  (with-screen (scr :input-echoing nil :input-blocking nil :enable-function-keys t :cursor-visible nil)
    (loop
       (let ((event (get-event scr)))
         (if event
             (case event
               (#\b (alert :beep))
               (#\f (alert :flash))
               (#\q (return)))
             (sleep 0.1))))))

;; minimal setting to get the mouse working.
;; reads and prints a single mouse event.
(defun t14 ()
  (let ((scr (make-instance 'screen :input-echoing nil :input-blocking t :enable-function-keys t)))
    (unwind-protect 
         (progn
           (%mousemask #b00000111111111111111111111111111 (null-pointer)) ; activate all mouse events.
           (get-char scr) ; here you have to click to generate a mouse event.
           (with-foreign-object (me '(:struct mevent)) ; create a pointer to the struct mevent.
             (%getmouse me) ; save the mouse event struct to the pointed position.
             (princ (mem-ref me '(:struct mevent)) scr) ; dereference the pointer, return a plist of the struct.
             (get-char scr)))
      (close scr))))

;; (cffi:convert-to-foreign '(id 1 x 1 y 2 z 3 bstate 2) '(:struct mevent))
;; (setf ev (convert-from-foreign (mem-ref bstate '(:struct mevent)) '(:struct mevent)))

;; mouse events are now detected in the event loop.
;; print the y x coordinates and the detected event.
(defun t14a ()
  (with-screen (scr :input-echoing nil :input-blocking t :enable-function-keys t :cursor-visible nil)
    (set-mouse-event '(:button-1-clicked :button-2-clicked :button-3-clicked))
    (event-case (scr event y x)
      ((:button-1-clicked :button-2-clicked :button-3-clicked) (format scr "~3A ~3A ~A~%" y x event))
      (#\q (return-from event-case)))))

;; left click prints a 1, right click prints a 3.
(defun t14b ()
  (with-screen (scr :input-echoing nil :input-blocking t :enable-function-keys t :cursor-visible nil)
    (set-mouse-event '(:button-1-clicked :button-3-clicked))
    (event-case (scr event mouse-y mouse-x)
      (:button-1-clicked (move scr mouse-y mouse-x) (princ "1" scr))
      (:button-3-clicked (move scr mouse-y mouse-x) (princ "3" scr))
      (#\q (return-from event-case)))))

(defun t14c ()
  "Print all mouse events."
  (with-screen (scr :input-echoing nil :input-blocking t :enable-function-keys t :cursor-visible nil)
    (%mousemask #b00000111111111111111111111111111 (null-pointer))
    (event-case (scr event y x)
      (#\q (return-from event-case))
      (t (format scr "~3A ~3A ~A~%" y x event)) )))

;; resize event: the standard screen size is resized automatically.
(defun t15 ()
  (with-screen (scr :input-echoing nil :input-blocking nil :enable-function-keys t :cursor-visible nil :enable-colors t)
    (add-string scr "Current standard screen geometry (Y x X):" :y 0 :x 0)
    (loop
       (let ((event (get-event scr)))
         (if event
             (case event
               (:resize (move scr 1 0)
                        (format scr "~A Y lines x ~A X columns." (height scr) (width scr))
                        ;; the environment variables get updated on a resize event too.
                        (format scr "~%~A Y lines x ~A X cols." %LINES %COLS)
                        (refresh scr))
               (#\q (return)))
             (progn
               (sleep 0.1)))))))

;; resize event: arrange window _locations_ relative to the screen size.
(defun t15a ()
  (with-screen (scr :input-echoing nil :input-blocking nil :enable-function-keys t :cursor-visible nil :enable-colors t)
    (add-string scr "Current standard screen geometry (Y x X):" :y 0 :x 0)
    (setf (background scr) (make-instance 'complex-char :simple-char #\. :color-pair '(:green :white)))
    (let ((time 0)
          ;; place a window in the center of the screen.
          (win (make-instance 'window :height 5 :width 10 :location (list (round (/ (height scr) 2))
                                                                          (round (/ (width scr) 2))))))
      (loop
         (let ((event (get-event scr)))
           (if event
               (case event
                 (:resize ;; if the scren is resized, relocate the window to the new center.
                          (move-window win (round (/ (height scr) 2)) (round (/ (width scr) 2))) 
                          ;; better differentiation of types with methods.
                          (move win 0 0)
                          (format win "Y:~A X:~A" (height scr) (width scr))
                          ;; repaint all windows completely by touching them before refreshing.
                          ;; overlapping windows have to be refreshed in reverse stacking order.
                          (mapc #'(lambda (w) (touch w) (refresh w)) 
                                (list scr win)))
                 (#\q (return)))
               (progn
                 (sleep 0.01)
                 (move win 1 0)
                 (incf time 0.01)
                 (format win "~A" time)
                 (mapc #'(lambda (w) (touch w) (refresh w)) 
                       (list scr win)) ))))
      (close win))))

;; resize event: arrange window _sizes_ relative to the screen size.
(defun t15b ()
  (with-screen (scr :input-echoing nil :input-blocking nil :enable-function-keys t :cursor-visible nil :enable-colors t)
    (add-string scr "Current standard screen geometry (Y x X):" :y 0 :x 0)
    (setf (background scr) (make-instance 'complex-char :color-pair '(:black :white)))
    (let ((time 0)
          ;; make the window slightly smaller than the standard screen.
          (win (make-instance 'window :height (- (height scr) 4) :width (- (width scr) 6) :location '(2 3))))
      (loop
         (let ((event (get-event scr)))
           (if event
               (case event
                 (:resize ;; resize the window on every termina resize.
                          (resize win (- (height scr) 4) (- (width scr) 6))
                          (move win 0 0)
                          (format win "Y:~A X:~A" (height scr) (width scr))
                          ;; when updating several overlapping windows, using mark-for-refresh and 
                          ;; batch-refresh instead of several calls to refresh prevents flickering.
                          (mapc #'(lambda (w) (touch w) (mark-for-refresh w)) (list scr win))
                          (refresh-marked))
                 (#\q (return)))
               (progn
                 (sleep 0.01)
                 (move win 1 0)
                 (incf time 0.01)
                 (format win "~A" time)
                 (mapc #'(lambda (w) (touch w) (mark-for-refresh w)) (list scr win))
                 (refresh-marked) ))))
      (close win))))

;; test get-string: read a string, then output it.
;; the only function key we can use during input is backspace.
(defun t16 ()
  (unwind-protect
       (let ((scr (make-instance 'screen)))
         (clear scr)
         (move scr 0 0)
         (add-string scr "Type your name: ")
         (refresh scr)
         (let ((str (get-string scr 10)))
           (add-string scr "Your name is: ")
           (add-string scr str))
         (refresh scr)
         (get-char scr))
    (end-screen)))

;; read a single line of Lisp input (30 chars max) from the last line, 
;; evaluate it and print the result to the output window above.
(defun t16a ()
  (with-screen (scr :input-echoing t :input-blocking t :enable-function-keys t :cursor-visible nil :enable-colors nil)
    (let ((out (make-instance 'window :height (1- (height scr)) :width (width scr) :location '(0 0)))
          (in (make-instance 'window :height 1 :width (width scr) :location (list (1- (height scr)) 0))))

      (print (eval (read-from-string (get-string in 30))) out)
      (refresh out)

      ;; blocking is t, so wait till the next keypress before exiting.
      (get-char in) 

      ;; close windows and window streams.
      (close in)
      (close out))))

;; add a loop to the input, making it a simple repl.
(defun t16b ()
  (with-screen (scr :input-echoing t :input-blocking t :enable-function-keys t :cursor-visible t :enable-colors nil)
    (let ((out (make-instance 'window :height (1- (height scr)) :width (width scr) :location '(0 0)))
          (in (make-instance 'window :height 1 :width (width scr) :location (list (1- (height scr)) 0))))
      (loop
         (let ((str (get-string in 30)))
           ;; if the input line is empty (length = 0), do nothing.
           (when (> (length str) 0)
                ;; a single blocking q exits the loop.
                (when (string= str "q") (return))
                ;; after the input is read, clear the input line.
                (clear in)
                (print (eval (read-from-string str)) out)
                (refresh out))))
      (close in)
      (close out))))

;; ncurses' get-string only allows the backspace key.
;; extend the functionality of the input line of the simple repl.
(defun t16c ()
  (with-screen (scr :input-echoing nil :cursor-visible t :enable-colors t)
    (let* ((wout (make-instance 'window :height (1- (height scr)) :width (width scr) :location '(0 0) :enable-scrolling t))
           ;; input blocking is a property of every single window, not just of the global screen.
           (win (make-instance 'window :height 1 :width (width scr) :location (list (1- (height scr)) 0)
                               :enable-function-keys t :input-blocking t))
           (*standard-output* wout)
           (n 0)) ; no of chars in the input line.
      (event-case (win event)
        (:left 
         (when (> (cadr (cursor-position win)) 0)
           (move win 0 -1 :relative t)))
        (:right 
         (when (< (cadr (cursor-position win)) n)
           (move win 0 1 :relative t)))
        (#\newline ; RET key, C-j, C-m
           (when (> n 0) ; only print when the line is not empty.
             (let* ((strin (extract-wide-string win :n n :y 0 :x 0)) 
                    (strout (eval (read-from-string strin))))
               (print (length strin))
               (princ strin)
               (terpri)
               (add-string wout (format nil "=> ~A~%~%" strout)))
             (setf n 0)
             (clear win) ; empty the input line after evaluation.
             (refresh wout)))
        (:dc ; DEL key
         (when (> n (cadr (cursor-position win)))
           (decf n)
           (delete-char win)))
        (:ic ; INS / Einfg key
         (format t "(insert-mode-p win) => ~A~%" (insert-mode-p win))
         (setf (insert-mode-p win) (not (insert-mode-p win)))
         (format t "(insert-mode-p win) => ~A~%" (insert-mode-p win))
         (refresh wout))
        (:backspace ; BS key
         (when (> (cadr (cursor-position win)) 0)
           (decf n)
           (move win 0 -1 :relative t)
           (delete-char win)))
        (#\q (return-from event-case))
        ((nil) ; when no key is hit at all
         ;; when there is no event, get-event will return nil.
         ;; this is the place for no-event code.
         ;; instead of doing nothing, set blocking to t.
         nil)
        ;; non-function keys, i.e. normal character keys
        (otherwise
         (when (and (characterp event)
                    (< (cadr (cursor-position win)) (1- (width win))))
           (incf n)
           ;; insert-mode-p does not insert if we do not use gray stream functions
           ;;(add-wide-char win event))))
           (write-char event win)))) ; calls stream-write-char
           ;; (princ event win) ; calls print-object
      (close win)
      (close wout))))

;; buffer: (3 2 1)
;; screen: 123
;; in the buffer, elements are added to the left and counted from the left.
;; on the screen, the list is displayed in reverse.
(defun t16d ()
  "Use an input buffer instead of extracting the string from the window. Create windows using the with-windows macro."
  (with-screen (scr :input-echoing nil :cursor-visible t :enable-colors t)
    (with-windows ((wout :height (1- (height scr)) :width (width scr) :location '(0 0) :enable-scrolling t)
                   (win  :height 1                  :width (width scr) :location (list (1- (height scr)) 0) :enable-function-keys t :input-blocking t))
      (let ((*standard-output* wout)
            (inbuf nil) ; input buffer character list
            (inptr 0))  ; position of the next character in the buffer
        (event-case (win event)
          (#\q (return-from event-case))
          (:left
           (when (> inptr 0) (decf inptr))
           (move win 0 inptr))
          (:right
           (when (< inptr (length inbuf)) (incf inptr))
           (move win 0 inptr))
          (#\newline
           (when (> (length inbuf) 0)
             (format t "~A~%" (coerce (reverse inbuf) 'string)) (refresh wout)
             (setf inbuf nil inptr 0)
             (clear win)
             (move win 0 inptr)
             (refresh win)))
          (:dc
           (when (> (length inbuf) inptr)
             (setf inbuf (remove-nth (- (length inbuf) (1+ inptr)) inbuf))
             (clear win)
             (add-string win (coerce (reverse inbuf) 'string))
             (move win 0 inptr)
             (refresh win)))
          (:backspace
           (when (> inptr 0)
             (decf inptr)
             (setf inbuf (remove-nth (- (length inbuf) 1 inptr) inbuf))
             (clear win)
             (add-string win (coerce (reverse inbuf) 'string))
             (move win 0 inptr)
             (refresh win)))
          (:ic
           (format t "(insert-mode-p win) => ~A~%" (insert-mode-p win))
           (setf (insert-mode-p win) (not (insert-mode-p win)))
           (format t "(insert-mode-p win) => ~A~%" (insert-mode-p win))
           (refresh wout))
          (otherwise
           (if (= inptr (length inbuf))
               (setf inbuf (cons event inbuf))
               (if (insert-mode-p win)
                   (setf inbuf (insert-nth (- (length inbuf) inptr) event inbuf))
                   (setf inbuf (replace-nth (- (length inbuf) (1+ inptr)) event inbuf))))
           (incf inptr)
           (clear win)
           (add-string win (coerce (reverse inbuf) 'string))
           (move win 0 inptr)
           (refresh win)))))))

(defun t16e ()
  "Edit a single input field, not part of a form."
  (with-screen (scr :input-echoing nil :cursor-visible t :enable-colors t :enable-function-keys t :input-blocking t)
    (let ((*standard-output* scr)
          (s1 (list :attributes '(:underline)))
          (s2 (list :simple-char #\.))
          (field (make-instance 'field :location (list 3 20) :width 10 :window scr)))

      (setf (style field) (list :foreground s1 :background s2))

      (bind field #\newline 'de.anvi.croatoan::debug-print-field-buffer)
      
      ;; pressing ^A (for "accept") exits the edit mode (for now)
      ;; the field variables are not reset when edit is exited, we can access them after the edit.
      (if (edit field)
          ;; accept returns t
          (progn
            (clear scr)
            ;; display the contents of the input buffer of the field
            (format t "buffer: ~A~%" (buffer field))
            (format t "string: ~A" (value field)))
          ;; when cancel returns nil
          (progn
            (clear scr)
            (format t "field edit canceled.")))
          
      (refresh scr)

      ;; wait for keypress, then exit
      (get-char scr) )))

(defun t16f ()
  "Group several input fields and buttons to a form."
  (with-screen (scr :input-echoing nil :cursor-visible t :enable-colors t :enable-function-keys t :input-blocking t)
    (let* ((s1 (list :fgcolor nil :bgcolor nil :attributes nil))
           (s2 (list :simple-char #\_))
           (s3 (list :fgcolor :yellow :bgcolor :red :attributes '(:underline :bold :italic)))
           (s4 (list :fgcolor :blue :bgcolor :white :attributes nil))

           ;; a style is a plist interpreted by the element drawing functions.
           (s5 (list :foreground s1 :background s2 :selected-foreground s3 :selected-background s4))

           (field1 (make-instance 'field :location (list 3 20) :width 10 :style s5 :max-buffer-length 5))
           (field2 (make-instance 'field :location (list 5 20) :width 10 :style s5))
           (field3 (make-instance 'field :location (list 7 20) :width 10 :style s5 :max-buffer-length 15))

           (s6 (list :foreground s1 :selected-foreground s4))

           (button1 (make-instance 'button :location (list 10 10) :name "Hello"  :style s6))
           (button2 (make-instance 'button :location (list 10 20) :name "Accept" :style s6))
           (button3 (make-instance 'button :location (list 10 30) :name "Cancel" :style s6))

           ;; a window is associated with the parent form, and can be accessed by the elements.           
           (form (make-instance 'form :elements (list field1 field2 field3 button1 button2 button3) :window scr)))

      ;; for debugging, return prints the content of the buffer and then deletes the buffer
      (bind form :f4 'de.anvi.croatoan::debug-print-field-buffer)
      (bind (find-keymap 'field-map) :f3 'de.anvi.croatoan::debug-print-field-buffer)

      ;; Functions to be called when the button is activated by #\newline or #\space.
      (setf (callback button1) (lambda (b e) (save-excursion scr (move scr 0 0) (format scr "Hello there"))))
      (setf (callback button2) 'accept)
      (setf (callback button3) 'cancel)

      ;; pressing ^A or C-a (for "accept") exits the edit mode
      ;; TAB, up and down cycles the fields and buttons
      (if (prog1 (edit form) (clear scr))
          ;; display the contents of the input buffer of all fields of the form
          ;; use field-buffer-to-string to get the contents of the field buffer as a string instead of a list of chars.
          (loop for el in (elements form)
             do (when (typep el 'field)
                  (format scr "~A ~A ~%" (buffer el) (value el))))
          ;; user did not accept the form (default) or called cancel-form.
          (format scr "nil"))

      (refresh scr)
      ;; wait for keypress, then exit
      (get-char scr) )))

(defun t16g ()
  "Use the element default style of the form."
  (with-screen (scr :input-echoing nil :cursor-visible t :enable-colors t :enable-function-keys t :input-blocking t)
    (let* (;; character styles that can be referenced in element styles
           (ch1 (list :fgcolor :blue :bgcolor :black))
           (ch2 (list :simple-char #\_))
           (ch3 (list :fgcolor :yellow :bgcolor :red :attributes '(:bold :italic)))
           (ch4 (list :simple-char #\_ :fgcolor :white :bgcolor :blue))
           (ch5 (list :fgcolor :yellow :simple-char #\.))
           
           ;; element styles reference previousy defined character styles.
           (s1 (list :foreground ch1 :background ch2 :selected-foreground ch3 :selected-background ch4))
           (s2 (list :foreground ch4 :selected-foreground ch3))
           (s3 (list :foreground ch1 :background ch5))
           
           ;; the form style consists of default styles of form elements.
           (sf1 (list 'field s1 'button s2 'label s3 'checkbox s1))
           
           (field1 (make-instance 'field :name :f1 :title "Forename" :location (list 3 20) :width 15 :max-buffer-length 5))
           (field2 (make-instance 'field :name :f2 :title "Surname"  :location (list 5 20) :width 15))
           (field3 (make-instance 'field :name :f3                   :location (list 7 20) :width 15 :max-buffer-length 20))

           (cb1 (make-instance 'checkbox :name :c1 :title "Employed" :location (list 9 20)))

           (m1 (make-instance 'checklist :name :m1 :title "checklist" :items (list 'a 'b 'c 'd 'e 'f) :layout (list 2 3)
                              :max-item-length 6 :menu-location (list 11 20) :window scr))

           (label1 (make-instance 'label :name :l1 :reference :f1 :width 18 :location (list 3 1)))
           (label2 (make-instance 'label :name :l2 :reference :f2 :width 18 :location (list 5 1)))
           (label3 (make-instance 'label :name :l3 :title "Age"             :location (list 7 1)))
           (label4 (make-instance 'label :name :l4 :reference :c1 :width 18 :location (list 9 1)))
           (label5 (make-instance 'label :name :l5 :reference :m1 :width 18 :location (list 11 1)))
           
           (button1 (make-instance 'button :name :b1 :title "Hello"  :location (list 14 10)))
           (button2 (make-instance 'button :name :b2 :title "Accept" :location (list 14 20)))
           (button3 (make-instance 'button :name :b3 :title "Cancel" :location (list 14 30)))

           (form (make-instance 'form
                                :elements (list field1 field2 field3 cb1 label1 label2 label3 label4 label5
                                                m1 button1 button2 button3)
                                :style sf1 :window scr)))

      ;; for debugging, return prints the content of the buffer and then deletes the buffer
      (bind form :f4 'crt::debug-print-field-buffer)

      ;; Functions to be called when the button is activated by #\newline or #\space.
      (setf (callback button1) (lambda (b e) (save-excursion scr (move scr 0 0) (format scr "Hello there"))))
      (setf (callback button2) 'accept)
      (setf (callback button3) 'cancel)

      ;; The set value shouldnt be longer than the max-buffer-length
      (setf (value field2) "hello"
            (value field3) "dear john")

      ;; Access fields by their name instead of looping through the elements list.
      (if (edit form)
          ;; edit returned t, which means the user accepted the form
          (progn
            (clear scr)
            (mapc #'(lambda (name)
                      (let ((element (find-element form name)))
                        (format scr "~5A ~10A ~20A~%" name (title element) (value element))))
                  (list :f1 :f2 :f3))
            ;; display the state of the checkbox
            (format scr "~5A ~10A ~20A~%" (name cb1) (title cb1) (checkedp cb1))
            (format scr "~5A ~10A ~20A~%" (name m1) (title m1) (value m1)))

          ;; edit returned nil, which means the user canceled the form
          (progn
            (clear scr)
            (format scr "nil")))
      
      (refresh scr)
      ;; wait for keypress, then exit
      (get-char scr) )))

(defun t16h ()
  "Create a form window."
  (with-screen (scr :input-echoing nil :cursor-visible t :enable-colors t :enable-function-keys t :input-blocking t)
    (let* ((ch1 (list :fgcolor :black :bgcolor :white))
           (ch2 (list :simple-char #\_ :fgcolor :black :bgcolor :white))
           (ch3 (list :fgcolor :yellow :bgcolor :red :attributes '(:underline :bold :italic)))
           (ch4 (list :simple-char #\_ :fgcolor :white :bgcolor :blue))

           (style1 (list :foreground ch1 :background ch2 :selected-foreground ch3 :selected-background ch4))
           (style2 (list :foreground ch4 :selected-foreground ch3))
           (style3 (list 'field style1 'button style2))
           
           (field1 (make-instance 'field :name :f1 :title "Forename" :location (list 3 3) :width 20 :max-buffer-length 5))
           (field2 (make-instance 'field :name :f2 :title "Surname"  :location (list 5 3) :width 20))
           (field3 (make-instance 'field :name :f3 :title "Age"      :location (list 7 3) :width 20 :max-buffer-length 20))
           
           (button1 (make-instance 'button :name :b1 :title "Say Hello" :location (list 10 7)))
           (button2 (make-instance 'button :name :b1 :title "Cancel"    :location (list 10 20)))
           (button3 (make-instance 'button :name :b2 :title "Accept"    :location (list 10 30)))
           
           (form (make-instance 'form-window :elements (list field1 field2 field3 button1 button2 button3)
                                :style style3 :enable-function-keys t :input-blocking t :title "form window"
                                :draw-border t :height 15 :width 50 :location (list 5 15))))

      (setf (background scr) (make-instance 'complex-char :simple-char #\space))
      (setf (background form) (make-instance 'complex-char :simple-char #\space :color-pair '(:black :white)))
      (refresh scr)

      ;; Functions to be called when the button is activated by #\newline or #\space.
      (setf (callback button1) (lambda (b e) (save-excursion scr (move scr 0 0) (format scr "Hello there") (refresh scr))))
      (setf (callback button2) 'cancel)
      (setf (callback button3) 'accept)

      ;; The set value shouldnt be longer than the max-buffer-length (not checked yet)
      (setf (value field1) "hello"
            (value field2) "there"
            (value field3) "dear john")

      (event-case (scr event)
        (#\q (return-from event-case))
        ;; display the form window input box by pressing a.
        (#\a
         (if (edit form)
             ;; edit returns t if the form is accepted
             (progn
               (clear scr)
               ;; Access fields by their name instead of looping through the elements list.
               (mapc #'(lambda (name)
                         (let ((field (find-element form name)))
                           (format scr "~5A ~10A ~20A~%" name (title field) (value field))))
                     (list :f1 :f2 :f3))
               (refresh scr))

             ;; and nil if it is cancelled.
             (progn
               (clear scr)
               (princ nil scr)
               (refresh scr)))))

        ;; close the ncurses windows before exiting.
        (close form))))

(defun t16i ()
  "A simple input form window."
  (let ((value nil))
    (with-screen (scr :input-echoing nil :cursor-visible t :enable-colors t :enable-function-keys t :input-blocking t)
      (let* ((ch1 (list :fgcolor :black :bgcolor :white))
             (ch2 (list :simple-char #\_ :fgcolor :black :bgcolor :white))
             (ch3 (list :fgcolor :yellow :bgcolor :red :attributes '(:underline :bold :italic)))
             (ch4 (list :simple-char #\_ :fgcolor :white :bgcolor :blue))
             (ch5 (list :simple-char #\. :fgcolor :black :bgcolor :white))
             
             (style1 (list :foreground ch1 :background ch2 :selected-foreground ch3 :selected-background ch4))
             (style2 (list :foreground ch1 :selected-foreground ch4))
             (style3 (list :foreground ch1 :background ch5))

             ;; form element default styles
             (style4 (list 'field style1 'button style2 'label style3))
             
             (label1 (make-instance 'label :name :l1 :reference :f1 :width 20 :location (list 2 1)))
             (field1 (make-instance 'field :name :f1 :title "Forename" :location (list 2 22) :width 20 :max-buffer-length 25))

             (label2 (make-instance 'label :name :l2 :reference :f2 :width 20 :location (list 4 1)))
             (field2 (make-instance 'field :name :f2 :title "Surname" :location (list 4 22) :width 20 :max-buffer-length 25))

             (button1 (make-instance 'button :name :b3 :title "Reset"  :location (list 6 15)))
             (button2 (make-instance 'button :name :b2 :title "Cancel" :location (list 6 25)))
             (button3 (make-instance 'button :name :b2 :title "Accept" :location (list 6 34)))

             (form (make-instance 'form-window :elements (list field1 field2 label1 label2 button1 button2 button3)
                                  :style style4 :enable-function-keys t :input-blocking t :title "form window"
                                  :draw-border t :height 10 :width 50 :location (list 5 15))))

        (setf (background form) (make-instance 'complex-char :simple-char #\space :color-pair '(:black :white)))
        (refresh scr)

        (setf (callback button1) 'reset-form)
        (setf (callback button2) 'cancel)
        (setf (callback button3) 'accept)

        (if (edit form)
            (setq value (pairlis (list (title field1) (title field2))
                                 (list (value field1) (value field2))))
            (setq value nil))
        (close form)))

    ;; return the input values
    value))

;; creating sub-windows and how they share memory with the parent window.
;; leaving out the size of a window maxes it out to the right (win1) and to the bottom (win1, win3)
(defun t17 ()
  (with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
    (let* ((win1 (make-instance 'window :location '(2 2) :draw-border t))
           (win2 (make-instance 'sub-window :parent win1 :height 5 :width 20 :location '(4 4) :draw-border t))
           (win3 (make-instance 'sub-window :parent win1           :width 20 :location '(4 4) :draw-border t :relative t)))
      (princ "win1" win1)
      (princ "win2" win2)
      (princ "win3 relative" win3)
      (mapc #'(lambda (w) (refresh w)) (list win1 win2 win3))
      (get-char win3)
      (mapc #'(lambda (w) (close w)) (list win2 win3))
      (refresh win1)
      ;; observe that the content from win 2 and 3 is still in win1 after they have been closed.
      (get-char win1)
      (close win1) )))

;; by default, the sub-window displays the part of the parent window it overlaps with.
;; we can change which part of the parent is displayed by changing the sub-windows source-location
;; we can change where it is displayed by changing the sub-windows location.
(defun t17a ()
  (with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
    (let ((win (make-instance 'sub-window :parent scr :height 5 :width 20 :location '(2 2) :draw-border t :relative t)))
      ;; initial content written to subwin and thus to scr.
      (move win 1 1) (princ "subwin" win)

      ;; create two content areas in the parent window.
      (move scr 2 25) (princ "area1" scr)
      (move scr 3 29) (princ "area1" scr)
      (move scr 4 33) (princ "area1" scr)
      (move scr 5 37) (princ "area1" scr)
      (move scr 6 41) (princ "area1" scr)

      (move scr 2 50) (princ "area2" scr)
      (move scr 3 54) (princ "area2" scr)
      (move scr 4 58) (princ "area2" scr)
      (move scr 5 62) (princ "area2" scr)
      (move scr 6 66) (princ "area2" scr)

      (mapc #'(lambda (w) (touch w) (refresh w)) (list scr win))
      (get-char scr)

      ;; map area1 to subwin location
      ;; the mapping only _displays_ the content from the source area.
      ;; the mapping it doesnt _write_ it onto the displayed area of scr.
      (setf (source-location win) '(2 25)) ; (%mvderwin (winptr win) 2 25)
      (mapc #'(lambda (w) (touch w) (refresh w)) (list scr win))
      (get-char scr)

      ;; map area2 to subwin location
      (setf (source-location win) '(2 50))
      (mapc #'(lambda (w) (touch w) (refresh w)) (list scr win))
      (get-char scr)

      ;;; now move subwin location
      ;; it still maps area2, but now to the new location.
      ;; the original content written to the subwin (and thus to scr because they share memory)
      ;; is now visible in scr, since the subwin overlay has moved.
      (setf (location win) '(10 2))
      (mapc #'(lambda (w) (touch w) (refresh w)) (list scr win))
      (get-char scr)

      ;; map area1 again, but now to the new location.
      (setf (source-location win) '(2 25))
      (mapc #'(lambda (w) (touch w) (refresh w)) (list scr win))
      (get-char scr)

      ;; writing to a sub-window writes to the _mapped_ location of the parent window
      ;; _not_ to the parent location below the subwindow.
      (clear win)
      (move win 0 0) (princ "writing to sub-win" win)
      (move win 1 0) (princ "writes to parent win" win)
      (mapc #'(lambda (w) (touch w) (refresh w)) (list scr win))
      (get-char scr)

      ;; delete the subwindow win
      ;; the mapped content isnt displayed any more.
      ;; now the content before the mapping should be displayed again.
      (mapc #'(lambda (w) (close w)) (list win))
      (refresh scr)
      (get-char scr) )))

;; Display misc system information
;; https://github.com/rudolfochrist/dotfiles/blob/master/.rc.lisp
(defun t18 ()
  (with-screen (scr)
    (format scr "Lisp implementation type:    ~A~%" (lisp-implementation-type))
    (format scr "Lisp implementation version: ~A~%" (lisp-implementation-version))
    (format scr "Machine type (Arch)          ~A~%" (machine-type))
    (format scr "Machine version (CPU)        ~A~%" (machine-version))
    (format scr "Software type (OS)           ~A~%" (software-type))
    (format scr "Software version (OS)        ~A~%" (software-version))
    (format scr "Machine instance (hostname)  ~A~%" (machine-instance))
    (format scr "User HOME                    ~A~%" (user-homedir-pathname))
    (format scr "Ncurses version:             ~A~%" (de.anvi.ncurses:%curses-version))
    (format scr "Terminal:                    ~A~%" (%termname))
    (format scr "Colors supported:            ~A~%" (%has-colors))
    (format scr "Color change supported:      ~A~%" (%can-change-color))
    (format scr "No of supported colors:      ~A~%" %colors)
    (format scr "No of supported color pairs  ~A~%" %color-pairs)
    (format scr "Can insert/delete chars:     ~A~%" (%has-ic))
    (format scr "Can insert/delete lines:     ~A~%" (%has-il))
    #+sbcl
    (progn
      (format scr "Terminal:                    ~A~%" (sb-ext:posix-getenv "TERM"))
      (format scr "Locale:                      ~A~%" (sb-ext:posix-getenv "LANG"))
      (format scr "Hostname:                    ~A~%" (sb-ext:posix-getenv "HOSTNAME"))
      (format scr "Username:                    ~A~%" (sb-ext:posix-getenv "USER"))
      (format scr "Operating system:            ~A~%" (sb-ext:posix-getenv "OSTYPE"))
      (format scr "CPU Type:                    ~A~%" (sb-ext:posix-getenv "MACHTYPE")))
    (format scr "uname -a:~%~A" (uiop:run-program "uname -a" :output :string))
    (refresh scr)
    (get-char scr)))

;; http://stackoverflow.com/questions/38684906/how-to-get-package-documentation-in-quicklisp
(defun t18a ()
  "Display misc quicklisp information."
  #+quicklisp
  (with-screen (scr)
    (let* ((dist (ql-dist:dist "quicklisp")) ; current dist
           ;; output to an empty broadcast stream is discraded like redirecting to /dev/null
           (*standard-output* (make-broadcast-stream))
           ;; Quicklisp prints its "Fetching xyz" messages to *trace-output*
           (*trace-output* *standard-output*)
           (*error-output* *standard-output*)
           (installed-systems (mapcar #'ql-dist:name (ql-dist:installed-systems dist))))
      (format scr "Quicklisp dist name          ~A~%" (ql-dist:name dist))
      (format scr "Quicklisp dist version       ~A~%" (ql-dist:version dist))
      (format scr "Quicklisp available version  ~A~%" (caar (ql-dist:available-versions dist)))
      (format scr "~%Quicklisp installed releases ~{~A, ~}~%" (mapcar #'ql-dist:name (ql-dist:installed-releases dist)))
      (format scr "~%Quicklisp installed systems  ~{~A, ~}~%~%" installed-systems)
      (loop
         for i in installed-systems
         for sys = (asdf:find-system i nil)
         do 
           (when sys
             (format scr "~27@A | ~A~%"
                     i
                     (remove #\newline (asdf:system-description sys))))))
    (refresh scr)
    (get-char scr))
  #-quicklisp
  (princ "Quicklisp not installed."))

;; print a simple menu, let the user choose an item by arrow keys, return the chosen item.
;; https://www.gnu.org/software/guile-ncurses/manual/html_node/A-simple-key-usage-example.html
(defun t19 ()
  (with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
    (flet ((draw-menu (win choices i)
             (clear win)
             (loop for j from 0 to (1- (length choices)) do
                  (move win j 0)
                  (format win "~A~A" (nth j choices) (if (= i j) "*" ""))
                  (when (= i j)
                    (move win j 0)
                    (change-attributes win 9 '(:reverse)))
                  (refresh win))))
      (let* ((choices '("Choice 0" "Choice 1" "Choice 2" "Choice 3" "Choice 4" "Choice 5" "Choice 6"))
             (n (length choices))
             (i 0)) ; current choice
        (draw-menu scr choices i)
        (event-case (scr event)
          (:up (setf i (mod (1- i) n)) (draw-menu scr choices i))
          (:down (setf i (mod (1+ i) n)) (draw-menu scr choices i))
          (#\newline (return-from event-case (nth i choices)))
          (#\q (return-from event-case)))))))

;; an even simpler 2-item menu is a yes-no-dialog.
(defun t19a ()
  (with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
    (flet ((draw-menu (win choices i)
             (move win 2 0) (clear win :target :end-of-line)
             (loop for j from 0 to (1- (length choices)) do
                  (move win 2 (* 10 j))
                  (format win "~A~A" (car (nth j choices)) (if (= i j) "*" ""))
                  (refresh win))))
      (let* ((choices '(("Yes" . t) ("No" . nil)))
             (n (length choices))
             (i 0)) ; current choice
        (move scr 0 0) (format scr "User, do you want?") (refresh scr)
        (draw-menu scr choices i)
        (event-case (scr event)
          ((:up :down :left :right #\tab) (setf i (mod (1+ i) n)) (draw-menu scr choices i))
          (#\newline (return-from event-case (cdr (nth i choices)))))))))

(defun t19b ()
  "Use the menu class, draw-menu and select functions."
  (with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
    (let* ((choices '("Choice 0" "Choice 11" "Choice 222" "Choice 3333" "Choice 44444" "Choice 555555" "Choice 6666666"))
           (menu (make-instance 'menu-window :items choices :location (list 0 20) :title "t19b"
                                :cyclic-selection t :draw-border t :enable-function-keys t)))
      (let ((result (select menu)))
        (format scr "You chose ~A" result)
        (touch scr)
        (refresh scr)
        (get-char scr))
      (close menu))))

(defun t19b2 ()
  "Use the select function with independent windows and menus"
  (with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
    (let* ((items1 '("Choice 0" "Choice 11" :choice22 "Choice 3333" "Choice 44444" "Choice 555555"
                     "Choice 6666666" "Choice 7" "Choice 88" "Choice 999"))
           (menu1 (make-instance 'menu :items items1 :name "sub-menu 1" :max-item-length 50 :menu-location (list 5 10)))
           (items2 (list "Item 0" menu1 "Item 1" "Item 2" "Item 3" "Item 4" "Item 5" "Item 6" "Item 7" "Item 8" "Item 9"))
           (menu2 (make-instance 'menu :items items2 :name "sub-menu 2" :max-item-length 50 :menu-location (list 5 10)))
           (items3 (list "Item 00" menu2 "Item 01" "Item 02" "Item 03" "Item 04" "Item 5" "Item 6" "Item 7" "Item 8" "Item 9"))
           (menu3 (make-instance 'menu :items items3 :name "t19b2b" :max-item-length 50 :menu-location (list 5 10))))
      ;; associate the same window with all three menus.
      (setf (window menu1) scr
            (window menu2) scr
            (window menu3) scr)
      ;; select an item and return it.
      (select menu3))))

(defun t19c ()
  "Improved t19b, the menu can be called repeatedly with the key a."
  (with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
    (let* ((choices '("Choice 0" "Choice 11" "Choice 222" "Choice 3333" "Choice 44444" "Choice 555555"
                      "Choice 6666666" "Choice 7" "Choice 88" "Choice 999"))
           (menu (make-instance 'menu-window :items choices :location (list 0 25) :scrolled-layout (list 6 1)
                                :title "t19c" :draw-border t :enable-function-keys t)))
      (event-case (scr event)
        ;; "a" draws the menu and enters a new menu-only event loop
        (#\a (let ((result (select menu)))
               (format scr "You chose ~A~%" result)
               ;; we have to touch scr in order to make the menu disappear.
               (touch scr)
               (refresh scr)))
        (#\q (return-from event-case)))
      (close menu))))

(defun t19c2 ()
  "Test the menu-item class for submenus."
  (with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
    (let* ((fun1 (make-instance 'menu-item :name "fun1" :value (lambda () (clear scr))))
           (choices (list "Choice 0" 'choice11 fun1 "Choice 222" "Choice 3333" "Choice 44444" "Choice 555555"
                          "Choice 6666666" "Choice 7" "Choice 88" "Choice 999"))
           ;; First, create a menu
           ;; TODO: how to determine the location of the sub-menu depending on the parent menu?
           (sub-menu2 (make-instance 'menu-window
                                     :items choices ;; here we only have strings
                                     :location (list 2 57) :scrolled-layout (list 6 1)
                                     ;; for hex triplets to work, we need to start sbcl with:TERM=xterm-256color lisp.sh
                                     ;;:color-pair (list :black #x666666)
                                     :bgcolor :red
                                     :name "submenu2" :title t :draw-border t :enable-function-keys t :visible nil))
           ;; then add that sub-menu menu as an item to the next menu, and so on.
           (sub-menu1 (make-instance 'menu-window
                                     :items (cons sub-menu2 choices) ;; first item is a submenu
                                     :location (list 1 41) :scrolled-layout (list 6 1)
                                     ;;:color-pair (list :black #x999999)
                                     :fgcolor :green
                                     :name "submenu1" :title nil :draw-border t :enable-function-keys t :visible nil))
           ;; finally, create the main menu containing sub-menu1 as an item
           (menu      (make-instance 'menu-window
                                     :items (cons sub-menu1 choices)  ;; first item is a submenu
                                     :location (list 0 25) :scrolled-layout (list 6 1)
                                     ;;:color-pair (list :black #xcccccc)
                                     :fgcolor :blue :bgcolor :yellow
                                     :name "menu" :title nil :draw-border nil :enable-function-keys t :visible nil)))
      ;; add the menus and submenus to a window stack
      ;; scr has to be stacked too so we can make the menus disappear.
      (setf (stackedp scr) t
            (stackedp menu) t
            (stackedp sub-menu1) t
            (stackedp sub-menu2) t)

      (setf (background scr) (make-instance 'complex-char :simple-char :board :color-pair (list :black :white)))

      (refresh-stack)
      (event-case (scr event)
        ;; "a" draws the menu and enters a new menu-only event loop by calling select
        (#\a (let ((result (select menu)))
               (format scr "You chose ~A~%" result)
               (format scr "~A~%" (color-pair menu))
               (format scr "Stack ~A~%" (length de.anvi.croatoan::*window-stack*))
               (refresh-stack) ))
        ;; TODO: doesnt repaint the border, because the border already has attributes.
        ;; setting a background doesnt change existing attributes, only existing backgrounds.
        (#\b (setf (color-pair menu) (list :red :black)))
        ;; "q" exits the function and all menus and submenus.
        (#\q (return-from event-case)))
      (close menu)
      (close sub-menu1)
      (close sub-menu2))))

(defun t19c3 ()
  "Menu with checkbox items."
  (with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
    (let* ((choices '("Choice 0" "Choice 11" "Choice 222" "Choice 3333" "Choice 44444" "Choice 555555"
                      "Choice 6666666" "Choice 7" "Choice 88" "Choice 999"))
           (menu (make-instance 'menu-window :items choices :location (list 0 25) :scrolled-layout (list 6 1)
                                :title "t19c" :draw-border t :enable-function-keys t
                                :menu-type :checklist
                                :max-item-length 20
                                :color-pair (list :yellow :red) )))
      (event-case (scr event)
        ;; "a" draws the menu and enters a new menu-only event loop
        (#\a (let ((result (select menu)))
               (format scr "You chose ~A~%" (mapcar #'value result))
               ;; we have to touch scr in order to make the menu disappear.
               (touch scr)
               (refresh scr)))
        (#\q (return-from event-case)))
      (close menu))))

(defun t19d ()
  "Use the arrow keys to pick a value from an 2D array menu, given as a layout parameter."
  (with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
    (let* ((items (loop for i below 200 collect (format nil "Item ~A" i)))
           (menu (make-instance 'menu-window
                                :items items :location (list 0 0) :layout (list 20 10) :scrolled-layout (list 10 4)
                                :cyclic-selection nil :max-item-length 9 :title "t19d" :draw-border t :enable-function-keys t)))
      (event-case (scr event)
        ;; "a" draws the menu and enters a new menu-only event loop
        (#\a (let ((result (select menu)))
               (format scr "You chose ~A~%" result)
               ;; we have to touch scr in order to make the menu disappear.
               (touch scr)
               (refresh scr)))
        (#\q (return-from event-case)))
      (close menu))))

(defun t19e ()
  "A one-line menu without a title and border resembling a menu bar."
  (with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
    (let* ((items '("Item 0" "Item 1" "Item 2" "Item 3" "Item 4" "Item 5" "Item 6" "Item 7" "Item 8" "Item 9"))
           (menu (make-instance 'menu-window :input-blocking t :items items :location (list 0 0)
                                :layout (list 1 (length items))
                                :scrolled-layout (list 1 6)
                                ;;:color-pair (list :black :yellow)
                                :max-item-length 10 :width (width scr) :draw-border t :enable-function-keys t)))
      ;; start the output below the menu
      (move scr 4 0)
      ;; exit the infinite loop by exiting the menu with q.
      (loop named menu-case
         do
           (let ((result (select menu)))
             (unless result (return-from menu-case))
             (format scr "You chose ~A~%" result)
             (refresh scr)))
      (close menu))))

(defun t19e2 ()
  "A menu bar and submenus."
  (with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
    (let* ((items1 (list "Choice 0" "Choice11" "Choice 222" "Choice 3333" "Choice 44444" "Choice 555555"
                          "Choice 6666666" "Choice 7" "Choice 88" "Choice 999"))
           (sub-menu1 (make-instance 'menu-window :items items1 :location (list 2 30) :scrolled-layout (list 6 1)
                                     :title nil :name "submenu1" :draw-border t :enable-function-keys t :visible nil :menu-type :selection))
           (sub-menu2 (make-instance 'menu-window :items items1 :location (list 2 45) :scrolled-layout (list 6 1)
                                     :title nil :name "submenu2" :draw-border t :enable-function-keys t :visible nil :menu-type :checklist))
           (fun1 (make-instance 'menu-item :name "fun1" :value (lambda () (clear scr))))
           (items2 (list "Item 0" fun1 sub-menu1 sub-menu2))
           (menu (make-instance 'menu-window :input-blocking t :items items2 :location (list 0 0) :layout (list 1 (length items2))
                                :max-item-length 15 :width (width scr) :draw-border t :enable-function-keys t)))
      (setf (stackedp scr) t
            (stackedp menu) t
            (stackedp sub-menu1) t
            (stackedp sub-menu2) t)
      (move scr 4 0) ;; start the output at line 4, below the menu bar.    
      (refresh-stack)
      (loop named menu-case
         do (let ((result (select menu)))
              (unless result (return-from menu-case))
              (format scr "You chose ~A~%" result)
              (refresh-stack)))
      (close menu)
      (close sub-menu1)
      (close sub-menu2) )))

(defun t19f ()
  "A more fancy version of t19a, a yes-no dialog using the class dialog-window."
  (with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
    (let* ((items (list "Yes" "No" "OK" 'cancel))
           (menu (make-instance 'dialog-window
                                :input-blocking t
                                :items items
                                ;; when center is t for a dialog window, we do not need to pass the location explicitely.
                                ;;:location (list 5 15)
                                :center t
                                :layout (list 1 4)
                                :max-item-length 12
                                :current-item-mark "> "
                                :color-pair (list :yellow :red)
                                :width 60
                                :draw-border t
                                :enable-function-keys t
                                :name "t19f"
                                :title t
                                ;; if the title is given as a string, it overrides the default title = name
                                ;; :title "this is a selection dialog"
                                :message-height 2
                                :message-text "Press <- or -> to choose. Enter to confirm choice.~%Press q to exit.")))

      (setf (background scr) (make-instance 'complex-char :simple-char :board :color-pair (list :black :white)))
      
      (refresh scr)
      (loop named menu-case
         do (let ((result (select menu)))
              (unless result (return-from menu-case))
              (format scr "You chose ~A~%" result)
              (refresh scr)))
      (close menu))))

(defun t19g ()
  "A checkbox dialog."
  (with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t :use-terminal-colors t)
    (let* ((items (list "Yes" "No" "OK" 'cancel "Maybe"))
           (menu (make-instance 'dialog-window
                                :input-blocking t
                                :items items
                                ;; when a menu or dialog type is a checklist, items can be checked and unchecked with x/space.
                                :menu-type :checklist
                                ;; a dialog window can be automatically centered in the terminal window.
                                :center t
                                :layout (list 5 1)
                                ;; TODO: the size of the dialog window should depend on scrolled layout when it is defined.
                                ;;:scrolled-layout (list 3 1)
                                :max-item-length 56
                                :color-pair (list :yellow :red)
                                ;; we do not need an item mark in a checklist
                                :current-item-mark ""
                                :width 60 :draw-border t :enable-function-keys t
                                :title "this is a checkbox dialog"
                                :message-height 2
                                :message-text "Press <- or -> to choose. Enter to confirm choice.~%Press q to exit.")))
      ;; #x2592 = :board
      (setf (background scr) (make-instance 'complex-char :simple-char #x2592 :color-pair (list :white :black)))
      
      (refresh scr)
      (loop named menu-case
         do (let ((result (select menu)))
              ;; TODO: returning an empty list exits the loop.
              (unless result (return-from menu-case))
              (format scr "You chose ~A~%" (mapcar #'value result))
              (refresh scr)))
      (close menu))))

;; Passing the color attribute directly to a character.
(defun t20 ()
  "Display a randomly created carpet of the seven default ANSI colors, except for black."
  (with-screen (scr :input-echoing nil :input-blocking nil :enable-colors t :cursor-visible nil)
    (let ((colors '(:red :green :yellow :blue :magenta :cyan :white)))
      (event-case (scr event)
        (#\q (return-from event-case))
        ((nil)
         (sleep 0.01)
         (echo scr #\space
               :y (random (height scr))
               :x (random (width scr))
               :color-pair (list nil (nth (random 7) colors))))))))

(defun t20a ()
  "Display a randomly created carpet of the 16 default xterm colors, except for black.

This only works with TERM=xterm-256color in xterm and gnome-terminal."
  (with-screen (scr :input-echoing nil :input-blocking nil :enable-colors t :cursor-visible nil)
    (let ((colors '(       :maroon :green :olive  :navy :purple  :teal :silver
                    :gray  :red    :lime  :yellow :blue :magenta :cyan :white)))
      (bind scr #\q 'exit-event-loop)
      (bind scr nil
            (lambda (win e)
              (echo win #\space
                    :y (random (height win))
                    :x (random (width win))
                    :bgcolor (nth (random 15) colors))))
      (setf (frame-rate scr) 1000)
      (run-event-loop scr))))

;; it is just a coincidence that "echo-char" works here.
;; echo-char is a chtype-function, and chtype allows only 1 byte for color pairs, which means 256 color pairs.
;; it works only because the foreground color is always black in the example.
;; echo-wide-char has to be used.
(defun t20b ()
  "Display the 256 supported colors. This only works with TERM=xterm-256color in xterm and gnome-terminal."
  (with-screen (scr :input-echoing nil :input-blocking t :enable-colors t :cursor-visible nil)
    ;; 0-15: 8 ANSI colors and 8 bold ANSI colors
    ;; only those first 16 colors are named.
    ;; note that the naming of the first 8 ANSI colors is not the same as the first 8 xterm colors.
    (loop for i from 0 to 7 do
         (loop for j from 0 to 1 do
              (loop for k from 0 to 2 do
                   (echo scr #\space
                         :y j
                         :x (+ (* i 3) k)
                         :color-pair (list :black
                                           (list :number (+ i (* j 8))))) )))
    ;; 16-231: 6x6x6 color cube
    (loop for n from 0 to 1 do
         (loop
            for m from 0 to 2
            for a1 = (+ 16 (* m 36))
            for a2 = (+ 16 (* m 36) 5)
            for a3 = (* m 19)
            do (loop for i from a1 to a2 do
                    (loop for j from 0 to 5 do
                         (loop for k from 0 to 2 do
                              (echo scr #\space
                                    :y (+ 3 j (* n 7))
                                    :x (+ (* (- i a1) 3) k a3)
                                    :color-pair (list :black
                                                      (list :number (+ (* n 108) i (* j 6)))) ))))))
    ;; 232-255: 24 shades of gray, without black and white
    (loop for i from 232 to 255 do
         (loop for k from 0 to 2 do
              (echo scr #\space
                    :y 17
                    :x (+ (* (- i 232) 3) k)
                    :color-pair (list :black
                                      (list :number i)))))
    (get-char scr)))

(defun t20c ()
  "Display the 256 supported colors. This only works with TERM=xterm-256color in xterm and gnome-terminal."
  (with-screen (scr :input-echoing nil :input-blocking t :enable-colors t :cursor-visible nil)
    ;; 0-15: 8 ANSI colors and 8 bold ANSI colors
    (loop for i from 0 to 7 do
         (loop for j from 0 to 1 do
              (loop for k from 0 to 2 do
                   (add scr #\space
                        :y j
                        :x (+ (* i 3) k)
                        :bgcolor (list :number (+ i (* j 8))))) ))
    ;; 16-231: 6x6x6 color cube
    (loop for n from 0 to 1 do
         (loop
            for m from 0 to 2
            for a1 = (+ 16 (* m 36))
            for a2 = (+ 16 (* m 36) 5)
            for a3 = (* m 19)
            do (loop for i from a1 to a2 do
                    (loop for j from 0 to 5 do
                         (loop for k from 0 to 2 do
                              (add scr #\space
                                   :y (+ 3 j (* n 7))
                                   :x (+ (* (- i a1) 3) k a3)
                                   :bgcolor (list :number (+ (* n 108) i (* j 6)))) )))))
    ;; 232-255: 24 shades of gray, without black and white
    (loop for i from 232 to 255 do
         (loop for k from 0 to 2 do
              (add scr #\space
                   :y 17
                   :x (+ (* (- i 232) 3) k)
                   :bgcolor (list :number i))))
    (refresh scr)
    (get-char scr)))

;; Tests for insert-char, insert-string, extract-char.
(defun t21 ()
  (with-screen (scr :cursor-visible nil)
    (move scr 0 0) (add-char scr #\a)
    ;; overwrite b over a
    (move scr 0 0)
    (add-char scr
              (make-instance 'complex-char :simple-char #\b :attributes (list :bold :underline) :color-pair '(:blue :yellow)))
    (echo scr
          (make-instance 'complex-char :simple-char #\e :attributes (list :underline) :color-pair '(:blue :yellow)))
    ;; insert pi before b
    (move scr 0 0) (insert-char scr :pi :color-pair '(:yellow :red))
    ;; insert d before pi
    (move scr 0 0) (insert-string scr "d ")
    ;; change the attributes of the d
    (move scr 0 0) (change-attributes scr 1 '(:underline) :color-pair '(:green :black))
    ;; extract the complex d from the window, then print its properties.
    (let ((e (extract-char scr)))
      (move scr 1 0)
      ;; format uses print-object specialized on complex-chars
      (format scr "~S~%~S ~S ~S" e (simple-char e) (attributes e) (color-pair e))
      ;; print back the extracted char
      (move scr 3 0)
      (add-char scr (simple-char e) :attributes (attributes e) :color-pair (color-pair e))
      ;; directly print the complex char
      (add-char scr e :y 5 :x 10 :n -1)
      ;; print strings with attributes and colors
      ;; attributes dont work (yet) when a long string gets wrapped around the last column.
      (add-string scr "string with attributes" :y 11 :x 10 :attributes '(:underline) :color-pair '(:black :green))
      ;; test adding both chars and strings with a single wrapper routine.
      (add scr #\a :y 13 :x 0 :attributes '(:underline) :color-pair '(:yellow :red) :n -1)
      (add scr "string with attributes" :y 15 :x 0 :n 11 :attributes '(:underline :bold) :color-pair '(:yellow :green)))
    ;; test print-object specialisation on complex-string
    (let ((str (make-instance 'complex-string :string "complex-string" :color-pair '(:blue :white) :attributes '(:underline))))
      (format scr "~%~S~%" str)
      (add-string scr str :y 18 :x 73 :n -1)
      (add scr str :y 19 :x 70 :n -1))
    ;; extract the first line as a complex-string, then reprint it at line 22
    (let ((str (extract-complex-string scr :y 0 :x 0 :n 4)))
      (move scr 22 1)
      (add scr str :n -1))
    ;; extract the first line as a simple string, then reprint it at line 23
    (let ((str (extract-string scr :y 0 :x 0 :n 5)))
      (move scr 23 1)
      (add scr str))
    (refresh scr)
    (get-char scr)))

;; temporarily end the main screen, write something to the repl, and then return to the screen.
;; the predicate closed-p can be used to check whether the screen has been temporarily closed.
(defun t22 ()
  (let ((scr (make-instance 'screen :input-blocking t :input-echoing nil)))
    (unwind-protect
         (progn
           (clear scr)
           ;; should yield NIL, since we didnt endwin yet
           (format scr "1. screen before ending: ~A~%" (closed-p scr))
           (get-char scr)

           (close scr)

           ;; this shouldnt be visible, but it is visible after the later refresh
           ;; obviously ncurses doesnt destroy the screen window after it is closed
           (format scr "2. screen after ending, we dont see it till after the refresh: ~A~%" (closed-p scr))

           ;; should yield T, because we closed the screen
           (format t "3. in the repl, after ending the screen: ~A~%" (closed-p scr))
           (force-output *standard-output*)
           (sleep 5)

           (refresh scr)
           ;; should yield NIL, since we refreshed
           (format scr "4. screen after refreshing: ~A~%" (closed-p scr))
           
           (get-char scr))
      (close scr))))

(defun t23 ()
  "Use save-excursion to return the cursor to its initial location."
  (with-screen (scr :input-echoing nil :input-blocking t :cursor-visible t :enable-colors t)
    (move scr 0 0)
    (princ "1. hello" scr)
    (save-excursion scr
      (move scr 5 5)
      (princ "2. there" scr))
    (princ "3. dear john" scr)
    (get-char scr)))

(defun t24 ()
  "Test usage of insert-line and delete-line."
  (with-screen (scr :input-echoing nil :input-blocking t :cursor-visible t :enable-colors t)
    (loop for i from 0 to (- (height scr) 1)
       do
         (move scr i 0)
         (format scr "~A" i))
    (refresh scr)
    (event-case (scr event)
      ((:up :down) (move-direction scr event) (refresh scr))
      (#\d (delete-line scr) (refresh scr))
      (#\i (insert-line scr) (refresh scr))
      (#\q (return-from event-case)))
    (get-char scr)))

(defun t25 ()
  "Test initialisation and refreshing of pads and sub-pads."
  (with-screen (scr :input-blocking t :cursor-visible nil :enable-colors t)
    (let* ((p (make-instance 'pad :height 100 :width 100))
          (sp (make-instance 'sub-pad :parent p :height 5 :width 10 :location (list 10 10))))

      ;; populate the pad with numbers.
      (loop for j from 0 to 99
         do (loop for i from 0 to 99
               do
                 (move p j i)
                 (format p "~D" (mod (* i j) 10))))

      ;; populate the sub-pad with letters.
      (loop for j from 0 to 4
         do (loop for i from 0 to 9
               do
                 (move sp j i)
                 (add-char sp #\X) ))

      ;; we have to modify the sub-window attributes first because apparently once
      ;; chars have attributes, they can not be changed by subsequent background changes.
      (setf (background sp) (make-instance 'complex-char :color-pair '(:white :red))
            (background p)  (make-instance 'complex-char :color-pair '(:green :white)))

      (let ((pad-min-y     0)
            (pad-min-x     0)
            (screen-min-y  0)
            (screen-min-x  0)
            (screen-max-y  5)
            (screen-max-x 10))

      ;; the background screen has to be touched and refreshed on every move,
      ;; otherwise we will see parts of the previously displayed pad still there.
      (touch scr)
      (refresh scr)
      (refresh p pad-min-y pad-min-x screen-min-y screen-min-x screen-max-y screen-max-x)

      (event-case (scr event)
        (:up    (decf pad-min-y) (decf screen-min-y) (decf screen-max-y) (touch scr) (refresh scr)
                (refresh p pad-min-y pad-min-x screen-min-y screen-min-x screen-max-y screen-max-x))
        (:down  (incf pad-min-y) (incf screen-min-y) (incf screen-max-y) (touch scr) (refresh scr)
                (refresh p pad-min-y pad-min-x screen-min-y screen-min-x screen-max-y screen-max-x))
        (:left  (decf pad-min-x) (decf screen-min-x) (decf screen-max-x) (touch scr) (refresh scr)
                (refresh p pad-min-y pad-min-x screen-min-y screen-min-x screen-max-y screen-max-x))
        (:right  (incf pad-min-x) (incf screen-min-x) (incf screen-max-x) (touch scr) (refresh scr)
                 (refresh p pad-min-y pad-min-x screen-min-y screen-min-x screen-max-y screen-max-x))
        (#\q   (return-from event-case))
        (otherwise nil)))

      (close p)
      (close sp))))

(defun t26 ()
  "Test accessors of window and cursor locations."
  (with-screen (scr :input-echoing nil :input-blocking t :cursor-visible t :enable-colors t :stacked t)
    (let ((win (make-instance 'window :height 5 :width 20 :location (list 0 0) :stacked t)))

      (setf (background scr) (make-instance 'complex-char :color-pair '(:white :red))
            (background win) (make-instance 'complex-char :color-pair '(:black :yellow)))

      (refresh-stack)
      (get-char scr)
      
      (setf (location win) (list 2 4))
      (refresh-stack)
      (get-char scr)

      (setf (location-y win) 4)
      (refresh-stack)
      (get-char scr)

      (setf (location-x win) 8)
      (refresh-stack)
      (get-char scr)

      (setf (cursor-position win) (list 0 0))
      (princ "a" win)
      (refresh-stack)
      (get-char scr)
      
      (setf (cursor-position-y win) 2)
      (princ "b" win)
      (refresh-stack)
      (get-char scr)

      (setf (cursor-position-x win) 4)
      (princ "c" win)
      (refresh-stack)
      (get-char scr))))

(defun t27 ()
  "Use run-event-loop and bind instead of event-case to handle keyboard events."
  (with-screen (scr :input-echoing nil :input-blocking t)

    ;; q ends the loop.
    (bind scr #\q 'exit-event-loop)

    ;; The event handler function has to take two arguments, the window and the event.

    ;; a and s add a string to the window.
    (bind scr #\a (lambda (win event) (format win "Hello there.~%")))
    (bind scr #\s (lambda (win event) (format win "Dear John.~%")))

    ;; d clears the window.
    (bind scr #\d (lambda (win event) (clear win)))

    ;; u unbinds a
    (bind scr #\u (lambda (win event) (unbind scr #\a)))

    (clear scr)
    (add-string scr "Type a, s or d. Type q to quit.")
    (refresh scr)

    (run-event-loop scr)))

(defun t28-hello (win event)
  (format win "Hello there ~A.~%" event))

(defun t28-clear (win event)
  (declare (ignore event))
  (clear win))

(defparameter *t28-map*
  (make-instance 'keymap :bindings-plist
    (list
     #\q  'exit-event-loop
     #\a  't28-hello
     #\d  't28-clear))
  "Define a keymap separately and then set it as a window's event handlers before running the event loop.")

(defun t28 ()
  "Use run-event-loop and a pre-defined event handler alist. Use a default handler."
  (with-screen (scr :input-echoing nil :input-blocking t)
    ;; add the separately defined keymap to the window object.
    (setf (keymap scr) *t28-map*)

    ;; add another event handler
    (bind scr #\s (lambda (win event) (format win "Dear John ~A~%" event)))

    ;; t is the default handler for all events without defined handlers.
    ;; The default event handler should not be used to handle the nil event when input-blocking is nil
    (bind *t28-map* t (lambda (win event) (format win "Default event handler ~A~%" event)))

    (clear scr)
    (add-string scr "Type a, s or d. Type q to quit.")
    (refresh scr)

    (run-event-loop scr)))

(defun t28a ()
  "Test the use of the run-event-loop with non-blocking events."
  (with-screen (scr :input-echoing nil :input-blocking nil)

    (setf (keymap scr) *t28-map*)

    (bind scr #\s (lambda (win event) (format win "Dear John ~A~%" event)))

    ;; The handler function for the nil event will be called between keyboard events.
    (bind scr nil (lambda (win event) (format win "sleep ~A " event)))

    (clear scr)
    (add-string scr "Type a, s or d. Type q to quit.")
    (refresh scr)

    ;; Set the rate at which the nil event will be handled in fps (frames per second).
    ;; For the same effect as frame-rate of 1, you can set :input-blocking to 1000 ms.
    (setf (frame-rate scr) 1)

    (run-event-loop scr)))

(defun draw-t29-shapes (window shapes &optional squarify)
  "Draw a list of shapes to window."
  (clear window)
  (unless (listp shapes) (setf shapes (list shapes)))
  (dolist (s shapes)
    (draw-shape window s squarify)))

;; Assumes an 80x24 terminal
(defun t29 ()
  "Draw an ASCII-art tree to illustrate the use of shapes."
  (with-screen (scr :input-blocking t :enable-colors t :input-echoing nil :cursor-visible nil :input-buffering nil)
    (let* ((leaf-char   (make-instance 'complex-char :simple-char #\O :color-pair '(:green :black)))
           (trunk-char  (make-instance 'complex-char :simple-char #\H :color-pair '(:white :black)))
           (ground-char (make-instance 'complex-char :simple-char #\i :color-pair '(:green :black)))
           (sun-char    (make-instance 'complex-char :simple-char #\o :color-pair '(:yellow :black)))

           (tree-trunk  (rectangle 19 27 5 4       :filled t :char trunk-char))
           (upper-crown (triangle 4 29 12 22 12 35 :filled t :char leaf-char))
           (lower-crown (triangle 8 29 18 20 18 37 :filled t :char leaf-char))
           (tree-crown  (merge-shapes upper-crown lower-crown))
           (ground      (line 23 0 23 80 :char ground-char))
           (sun         (circle 5 6 2 :char sun-char :filled t)))

      (draw-t29-shapes scr (list tree-trunk tree-crown ground sun) t)
      ;; wait for a keypress
      (get-char scr)

      ;; redraw with squarify nil
      (draw-t29-shapes scr (list tree-trunk tree-crown ground sun) nil)
      (get-char scr) )))

(defun t30 ()
  "Test color pair completion for style parameters."
  (with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
    (with-windows ((w1 :height 5 :width 5 :location '(0 0))
                   (w2 :height 5 :width 5 :location '(0 5))
                   (w3 :height 5 :width 5 :location '(0 10)))
      (setf (background w1) (make-instance 'complex-char :color-pair '(:black :cyan))
            (background w2) (make-instance 'complex-char :color-pair '(:black :magenta))
            (background w3) (make-instance 'complex-char :color-pair '(:black :white)))
      ;; the character style contains only one color
      ;; the fg/bg of the target window is used to complete the color pair.
      (let ((s1 (list :attributes '(:bold) :fgcolor :red))
            (s2 (list :attributes '(:bold) :bgcolor :yellow)))
        (add w1 #\a :y 2 :x 2 :style s1)
        (add w2 #\b :y 2 :x 2 :style s1)
        (add w3 #\c :y 2 :x 2 :style s2))
      (refresh w1)
      (refresh w2)
      (refresh w3)
      (get-char w1))))

;; the ncurses window background property and the color-pair apparently set the same internal variable
;; setting one to nil sets the other to nil.
;; croatoan does not do that. here, the window color if it is set overrides the background property.
;; if a color parameter is directly passed, it overrides both the window color-pair and background.
(defun t31 ()
  "Test color pair completion for fgcolor and bgcolor parameters."
  (with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
    (add-string scr "default colors" :y 0 :x 0) (refresh scr) (get-char scr)

    (setf (color-pair scr) '(:yellow :blue))
    (add-string scr "color-pair yellow on blue" :y 1 :x 0) (refresh scr) (get-char scr)

    ;; text is not printed red on white because the color-pair overrides the background color.
    (setf (background scr nil) (make-instance 'complex-char :simple-char #\- :color-pair '(:red :white)))
    (add-string scr "                          background red on white" :y 2 :x 0) (refresh scr) (get-char scr) 

    (setf (color-pair scr) '(:blue :yellow))
    (add-string scr "color-pair blue on yellow" :y 3 :x 0) (refresh scr) (get-char scr)

    ;; text is not printed red on white because the color-pair overrides the background color.
    (setf (background scr nil) (make-instance 'complex-char :simple-char #\space :color-pair '(:white :red)))
    (add-string scr "                          background white on red" :y 4 :x 0) (refresh scr) (get-char scr)

    ;; now when we set the color-pair to nil, the text is printed in background colors.
    (setf (color-pair scr) nil)
    (add-string scr "color-pair nil" :y 5 :x 0) (refresh scr) (get-char scr)

    ;; when we then also set the background to nil, the text is displayed in default white on black.
    (setf (background scr nil) nil)
    (add-string scr "                          background nil" :y 6 :x 0) (refresh scr) (get-char scr)

    (add-string scr "fgcolor red bgcolor cyan" :y 7 :x 0 :fgcolor :red :bgcolor :cyan) (refresh scr) (get-char scr)
    (add-string scr "fgcolor red (bgcolor default)" :y 8 :x 0 :fgcolor :red)  (refresh scr) (get-char scr)
    (add-string scr "(fgcolor default) bgcolor cyan" :y 9 :x 0 :bgcolor :cyan) (refresh scr) (get-char scr)

    (setf (color-pair scr) '(:yellow :blue))
    ;; the color parameters override the color-pair window property
    (add-string scr "color pair yellow on blue, then fgcolor red" :y 10 :x 0 :fgcolor :red) (refresh scr) (get-char scr)
    (add-string scr "color pair yellow on blue, then bgcolor cyan" :y 11 :x 0 :bgcolor :cyan) (refresh scr) (get-char scr)

    ;; the background is ignored because color-pair is set
    (setf (background scr nil) (make-instance 'complex-char :simple-char #\. :color-pair '(:white :red)))
    (add-string scr "fgcolor red" :y 12 :x 0 :fgcolor :red)  (refresh scr) (get-char scr)
    (add-string scr "bgcolor cyan" :y 13 :x 0 :bgcolor :cyan)  (refresh scr) (get-char scr)

    ;; when the pair is set to nil, the background is used again.
    (setf (color-pair scr) nil)
    (add-string scr "fgcolor magenta" :y 14 :x 0 :fgcolor :magenta)  (refresh scr) (get-char scr)
    (add-string scr "bgcolor cyan" :y 15 :x 0 :bgcolor :cyan)  (refresh scr) (get-char scr) ))

