(in-package :de.anvi.ncurses)

;;; ncurses
;;; CRT screen handling and optimization package
;;; http://invisible-island.net/ncurses/man/ncurses.3x.html

;; The wide multi-byte library is preferred and will be loaded when the underlying lisp system supports unicode.
#+(or sb-unicode unicode openmcl-unicode-strings)
(define-foreign-library libncursesw
    (:darwin (:or "libncursesw.6.dylib" "libncursesw.5.dylib" "libncursesw.dylib" "libcurses.dylib"))
    (:unix   (:or "libncursesw.so.6.1" "libncursesw.so.6.0" "libncursesw.so.6" "libncursesw.so.5.9" "libncursesw.so.5" "libncursesw.so"))
    (t       (:default "libncursesw")))

#+(or sb-unicode unicode openmcl-unicode-strings)
(use-foreign-library libncursesw)

;; Attempt to use the legacy single-byte library only when the lisp implementation doesnt support unicode.
#-(or sb-unicode unicode openmcl-unicode-strings)
(define-foreign-library libncurses
    (:darwin (:or "libncurses.6.dylib" "libncurses.5.dylib" "libncurses.dylib" "libcurses.dylib"))
    (:unix   (:or "libncurses.so.6.1" "libncurses.so.6.0" "libncurses.so.6" "libncurses.so.5.9" "libncurses.so.5" "libncurses.so"))
    (t       (:default "libncurses")))

#-(or sb-unicode unicode openmcl-unicode-strings)
(use-foreign-library libncurses)

;;; ------------------------------------------------------------------

;; Every function that can take a win, should take a win.
;; We dont want functions that implicitly operate on a global stdscr.

;; Function should return lisp-style bools, not C type int 0 or 1.
;; cffi automatically does this conversion.

;;; ------------------------------------------------------------------

;; General ncurses library implementation info:

;; data types:

;; http://pubs.opengroup.org/onlinepubs/7908799/xcurses/curses.h.html
;; http://pubs.opengroup.org/onlinepubs/7908799/xcurses/implement.html
;; http://pubs.opengroup.org/onlinepubs/7908799/cursesix.html

;; http://refspecs.linuxbase.org/LSB_3.0.0/LSB-Core-generic/LSB-Core-generic/libncurses.html
;; http://refspecs.linuxbase.org/LSB_3.0.0/LSB-Core-generic/LSB-Core-generic/libncurses-ddefs.html

;; http://h71000.www7.hp.com/doc/83final/5763/5763pro_016.html

;; http://docs.python.org/release/2.4.3/lib/curses-window-objects.html

;; http://web.ist.utl.pt/tiago.dionizio/lua/lcurses.html

;;; ------------------------------------------------------------------

;;; Types

;; Basic types as seen in the ncurses function prototypes.
;; These will only be used inside cffi wrappers.

;; not used, cffi automatically translates a boolean type
;; typedef unsigned char bool;
(defctype bool :unsigned-char)

#|
--enable-lp64
Allows an application to define _LP64 to declare chtype and mmask_t as
simply unsigned rather than the configured types
using the --with-chtype and --with-mmask_t options.

#if 1 && defined(_LP64)
typedef unsigned chtype;
typedef unsigned mmask_t;
#else
typedef uint32_t chtype;
typedef uint32_t mmask_t;
#endif
|#

;; default ubuntu build options, they build for API5.
;; --disable-lp64 --with-chtype='long' --with-mmask-t='long'

;; the default type on ubuntu isnt "unsigned int" but "long", which means signed long.

;; We will use the ABI6 default uint32_t instead of long.

;; TODO: use ABI6 values for chtype, cchar_t and mmask: --with-chtype=uint32_t
;;(defctype chtype :unsigned-int)
(defctype chtype :uint32)

;; wide character type, defined in:
;; /usr/lib/gcc/x86_64-linux-gnu/5/include/stddef.h
;; it has to be able to contain the 21-bit 10FFFF, which is the max allowed unicode point.
(defctype wchar_t :int32)

;; typedef unsigned int wint_t;
;; used in get_wch.lisp
;; wint_t needs to be a signed int to represent a negative WEOF value.
(defctype wint_t :int32)

;; typedef chtype attr_t;
;; TODO: rename this to attr_t
(defctype attr :uint32
  "The 32 bit integral type attr_t holds an OR-ed set of attributes.")
(defctype attr_t :uint32
  "The 32 bit integral type attr_t holds an OR-ed set of attributes.")

;; winptr = *WINDOW
;; TODO: define window struct
;; (defctype ptr-window (:pointer (:struct window)))
(defctype window :pointer)

;; scrptr = *SCREEN
(defctype screen :pointer)

;; fileptr = *FILE
(defctype file :pointer)

;;; C structures

#|

Excerpt from /usr/include/ncurses.h

/*
 * cchar_t stores an array of CCHARW_MAX wide characters.  The first is
 * normally a spacing character.  The others are non-spacing.  If those
 * (spacing and nonspacing) do not fill the array, a null L'\0' follows. 
 * Otherwise, a null is assumed to follow when extracting via getcchar().
 */
#define CCHARW_MAX	5
typedef struct
{
    attr_t	attr;
    wchar_t	chars[CCHARW_MAX];
#if 0
#undef NCURSES_EXT_COLORS
#define NCURSES_EXT_COLORS 20110404
    int		ext_color;	/* color pair, must be more than 16-bits */
#endif
}
cchar_t;

|#

;; For non-extended colors, the color pair is OR-ed into the attr value, like it has been done
;; with color pairs in chtypes. This is ABI5.

;;(defcstruct cchar_t
;;  "A C struct containing contains a wide char and an integer containing the color pair and attributes."
;;  (cchar-attr   attr)
;;  (cchar-chars  wchar_t :count 5))

;; For extended colors, we get a new struct slot. This is ABI6, which is the default since ncurses 6.0, 20150808.

;; Intended to be used with setcchar.
;; TODO: write meaningsful docstrings here.
(defcstruct cchar_t
  "C struct containing a wide char, a color pair and attributes."
  (cchar-attr   attr_t)
  (cchar-chars  wchar_t :count 5)
  (cchar-colors :int))

(defctype ptr-cchar_t (:pointer (:struct cchar_t)))

;; Intended to be used with convert-to-foreign plist translation.
;; For some reasons, plists with pointers dont work, so we have to pass by value.
;; TODO: we dont need this any more, everything works now with the cchar_t struct.
(defcstruct cchar
  "C struct containing a wide char, a color pair and attributes."
  (cchar-attr   attr_t)
  (cchar-chars  wchar_t)
  (cchar-colors :int))

;; the default on ubuntu 12.04, 16.04 is --enable-ext-colors,
;; so is probably on debian, so we need to include "int ext_color" by default in
;; the cchar struct.

;; convert-to-foreign and convert-from-foreign do not return structs,
;; but pointers to structs.

;;; mouse.lisp

;; typedef unsigned long mmask_t;
;; 
;; typedef struct {
;;     short id;         /* ID to distinguish multiple devices */
;;     int x, y, z;      /* event coordinates */
;;     mmask_t bstate;   /* button state bits */
;; } MEVENT;

;; TODO: ABI6: --with-mmask_t=uint32_t
;;(defctype mmask_t :unsigned-int)
(defctype mmask_t :uint32)

(defcstruct mevent
  "C struct containing mouse coordinates and button state."
  (id :short)
  (x :int)
  (y :int)
  (z :int)
  (bstate mmask_t))

;;; ------------------------------------------------------------------

;;; Constants

;; #defines taken from ncurses.h, should not be user visible.
;; wherever cffi defines a boolean, we can use t and nil instead of TRUE and FALSE.
;;(defconstant TRUE 1)
;;(defconstant FALSE 0)
;;(defconstant ERROR -1)
;;(defconstant OK 0)
