;;;; -*- indent-tabs-mode: nil -*-
;;;
;;; swank-mkcl.lisp --- SLIME backend for MKCL.
;;;
;;; This code has been placed in the Public Domain.  All warranties
;;; are disclaimed.
;;;

;;; Administrivia

(defpackage swank/mkcl
  (:use cl swank/backend))

(in-package swank/mkcl)

;;(declaim (optimize (debug 3)))

(defvar *tmp*)

(defimplementation gray-package-name ()
  '#:gray)

(eval-when (:compile-toplevel :load-toplevel)

  (swank/backend::import-swank-mop-symbols :clos
    ;;  '(:eql-specializer
    ;;    :eql-specializer-object
    ;;    :generic-function-declarations
    ;;    :specializer-direct-methods
    ;;    :compute-applicable-methods-using-classes)
    nil
    ))


;;; UTF8

(defimplementation string-to-utf8 (string)
  (mkcl:octets (si:utf-8 string)))

(defimplementation utf8-to-string (octets)
  (string (si:utf-8 octets)))


;;;; TCP Server

(eval-when (:compile-toplevel :load-toplevel)
  ;; At compile-time we need access to the sb-bsd-sockets package for the
  ;; the following code to be read properly.
  ;; It is a bit a shame we have to load the entire module to get that.
  (require 'sockets))


(defun resolve-hostname (name)
  (car (sb-bsd-sockets:host-ent-addresses
        (sb-bsd-sockets:get-host-by-name name))))

(defimplementation create-socket (host port &key backlog)
  (let ((socket (make-instance 'sb-bsd-sockets:inet-socket
			       :type :stream
			       :protocol :tcp)))
    (setf (sb-bsd-sockets:sockopt-reuse-address socket) t)
    (sb-bsd-sockets:socket-bind socket (resolve-hostname host) port)
    (sb-bsd-sockets:socket-listen socket (or backlog 5))
    socket))

(defimplementation local-port (socket)
  (nth-value 1 (sb-bsd-sockets:socket-name socket)))

(defimplementation close-socket (socket)
  (sb-bsd-sockets:socket-close socket))

(defun accept (socket)
  "Like socket-accept, but retry on EINTR."
  (loop (handler-case
            (return (sb-bsd-sockets:socket-accept socket))
          (sb-bsd-sockets:interrupted-error ()))))

(defimplementation accept-connection (socket
                                      &key external-format
                                      buffering timeout)
  (declare (ignore timeout))
  (sb-bsd-sockets:socket-make-stream (accept socket)
                                     :output t ;; bogus
                                     :input t ;; bogus
                                     :buffering buffering ;; bogus
                                     :element-type (if external-format
                                                       'character 
                                                     '(unsigned-byte 8))
                                     :external-format external-format
                                     ))

(defimplementation preferred-communication-style ()
  :spawn
  )

(defvar *external-format-to-coding-system*
  '((:iso-8859-1
     "latin-1" "latin-1-unix" "iso-latin-1-unix" 
     "iso-8859-1" "iso-8859-1-unix")
    (:utf-8 "utf-8" "utf-8-unix")))

(defun external-format (coding-system)
  (or (car (rassoc-if (lambda (x) (member coding-system x :test #'equal))
                      *external-format-to-coding-system*))
      (find coding-system (si:all-encodings) :test #'string-equal)))

(defimplementation find-external-format (coding-system)
  #+unicode (external-format coding-system)
  ;; Without unicode support, MKCL uses the one-byte encoding of the
  ;; underlying OS, and will barf on anything except :DEFAULT.  We
  ;; return NIL here for known multibyte encodings, so
  ;; SWANK:CREATE-SERVER will barf.
  #-unicode (let ((xf (external-format coding-system)))
              (if (member xf '(:utf-8))
                  nil
                :default)))



;;;; Unix signals

(defimplementation install-sigint-handler (handler)
  (let ((old-handler (symbol-function 'si:terminal-interrupt)))
    (setf (symbol-function 'si:terminal-interrupt)
          (if (consp handler)
              (car handler)
              (lambda (&rest args)
                (declare (ignore args))
                (funcall handler)
                (continue))))
    (list old-handler)))


(defimplementation getpid ()
  (mkcl:getpid))

(defimplementation set-default-directory (directory)
  (mk-ext::chdir (namestring directory))
  (default-directory))

(defimplementation default-directory ()
  (namestring (mk-ext:getcwd)))

(defmacro progf (plist &rest forms)
  `(let (_vars _vals)
     (do ((p ,plist (cddr p)))
         ((endp p))
         (push (car p) _vars)
         (push (cadr p) _vals))
     (progv _vars _vals ,@forms)
     )
  )

(defvar *inferior-lisp-sleeping-post* nil)

(defimplementation quit-lisp ()
  (progf (ignore-errors (eval (read-from-string "swank::*saved-global-streams*"))) ;; restore original IO streams.
         (when *inferior-lisp-sleeping-post* (mt:semaphore-signal *inferior-lisp-sleeping-post*))
         ;;(mk-ext:quit :verbose t)
         ))


;;;; Compilation

(defvar *buffer-name* nil)
(defvar *buffer-start-position*)
(defvar *buffer-string*)
(defvar *compile-filename*)

(defun signal-compiler-condition (&rest args)
  (signal (apply #'make-condition 'compiler-condition args)))

#|
(defun handle-compiler-warning (condition)
  (signal-compiler-condition
   :original-condition condition
   :message (format nil "~A" condition)
   :severity :warning
   :location
   (if *buffer-name*
       (make-location (list :buffer *buffer-name*)
                      (list :offset *buffer-start-position* 0))
       ;; ;; compiler::*current-form*
       ;; (if compiler::*current-function*
       ;;     (make-location (list :file *compile-filename*)
       ;;                    (list :function-name   
       ;;                          (symbol-name
       ;;                           (slot-value compiler::*current-function*
       ;;                                       'compiler::name))))
       (list :error "No location found.")
           ;; )
       )))
|#

#|
(defun condition-location (condition)
  (let ((file     (compiler:compiler-message-file condition))
        (position (compiler:compiler-message-file-position condition)))
    (if (and position (not (minusp position)))
        (if *buffer-name*
            (make-buffer-location *buffer-name*
                                  *buffer-start-position*
                                  position)
            (make-file-location file position))
        (make-error-location "No location found."))))
|#

(defun condition-location (condition)
  (if *buffer-name*
      (make-location (list :buffer *buffer-name*)
                     (list :offset *buffer-start-position* 0))
       ;; ;; compiler::*current-form*   ;
       ;; (if compiler::*current-function* ;
       ;;     (make-location (list :file *compile-filename*) ;
       ;;                    (list :function-name ;
       ;;                          (symbol-name ;
       ;;                           (slot-value compiler::*current-function* ;
       ;;                                       'compiler::name)))) ;
    (if (typep condition 'compiler::compiler-message)
        (make-location (list :file (namestring (compiler:compiler-message-file condition)))
                       (list :end-position (compiler:compiler-message-file-end-position condition)))
      (list :error "No location found."))
    )
  )

(defun handle-compiler-message (condition)
  (unless (typep condition 'compiler::compiler-note)
    (signal-compiler-condition
     :original-condition condition
     :message (princ-to-string condition)
     :severity (etypecase condition
                 (compiler:compiler-fatal-error :error)
                 (compiler:compiler-error       :error)
                 (error                  :error)
                 (style-warning          :style-warning)
                 (warning                :warning))
     :location (condition-location condition))))

(defimplementation call-with-compilation-hooks (function)
  (handler-bind ((compiler:compiler-message #'handle-compiler-message))
    (funcall function)))

(defimplementation swank-compile-file (input-file output-file
                                                  load-p external-format
                                                  &key policy)
  (declare (ignore policy))
  (with-compilation-hooks ()
    (let ((*buffer-name* nil)
          (*compile-filename* input-file))
      (handler-bind (#|
                     (compiler::compiler-note
                      #'(lambda (n)
                          (format t "~%swank saw a compiler note: ~A~%" n) (finish-output) nil))
                     (compiler::compiler-warning
                      #'(lambda (w)
                          (format t "~%swank saw a compiler warning: ~A~%" w) (finish-output) nil))
                     (compiler::compiler-error
                      #'(lambda (e)
                          (format t "~%swank saw a compiler error: ~A~%" e) (finish-output) nil))
                     |#
                     )
        (multiple-value-bind (output-truename warnings-p failure-p)
             (compile-file input-file :output-file output-file :external-format external-format)
           (values output-truename warnings-p
                   (or failure-p
                       (and load-p (not (load output-truename))))))))))

(defimplementation swank-compile-string (string &key buffer position filename policy)
  (declare (ignore filename policy))
  (with-compilation-hooks ()
    (let ((*buffer-name* buffer)
          (*buffer-start-position* position)
          (*buffer-string* string))
      (with-input-from-string (s string)
        (when position (file-position position))
        (compile-from-stream s)))))

(defun compile-from-stream (stream)
  (let ((file (mkcl:mkstemp "TMP:MKCL-SWANK-TMPXXXXXX"))
        output-truename
        warnings-p
        failure-p
        )
    (with-open-file (s file :direction :output :if-exists :overwrite)
      (do ((line (read-line stream nil) (read-line stream nil)))
	  ((not line))
	(write-line line s)))
    (unwind-protect
        (progn
          (multiple-value-setq (output-truename warnings-p failure-p)
               (compile-file file))
          (and (not failure-p) (load output-truename)))
      (when (probe-file file) (delete-file file))
      (when (probe-file output-truename) (delete-file output-truename)))))


;;;; Documentation

(defun grovel-docstring-for-arglist (name type)
  (flet ((compute-arglist-offset (docstring)
           (when docstring
             (let ((pos1 (search "Args: " docstring)))
               (if pos1
                   (+ pos1 6)
                   (let ((pos2 (search "Syntax: " docstring)))
                     (when pos2
                       (+ pos2 8))))))))
    (let* ((docstring (si::get-documentation name type))
           (pos (compute-arglist-offset docstring)))
      (if pos
          (multiple-value-bind (arglist errorp)
              (ignore-errors
                (values (read-from-string docstring t nil :start pos)))
            (if (or errorp (not (listp arglist)))
                :not-available
                arglist
                ))
          :not-available ))))

(defimplementation arglist (name)
  (cond ((and (symbolp name) (special-operator-p name))
         (let ((arglist (grovel-docstring-for-arglist name 'function)))
           (if (consp arglist) (cdr arglist) arglist)))
        ((and (symbolp name) (macro-function name))
         (let ((arglist (grovel-docstring-for-arglist name 'function)))
           (if (consp arglist) (cdr arglist) arglist)))
        ((or (functionp name) (fboundp name))
         (multiple-value-bind (name fndef)
           (if (functionp name)
               (values (function-name name) name)
             (values name (fdefinition name)))
           (let ((fle (function-lambda-expression fndef)))
             (case (car fle)
                   (si:lambda-block (caddr fle))
                   (t (typecase fndef
                        (generic-function (clos::generic-function-lambda-list fndef))
                        (compiled-function (grovel-docstring-for-arglist name 'function))
                        (function :not-available)))))))
        (t :not-available)))

(defimplementation function-name (f)
  (si:compiled-function-name f)
  )

(eval-when (:compile-toplevel :load-toplevel)
  ;; At compile-time we need access to the walker package for the
  ;; the following code to be read properly.
  ;; It is a bit a shame we have to load the entire module to get that.
  (require 'walker))

(defimplementation macroexpand-all (form)
  (walker:macroexpand-all form))

(defimplementation describe-symbol-for-emacs (symbol)
  (let ((result '()))
    (dolist (type '(:VARIABLE :FUNCTION :CLASS))
      (let ((doc (describe-definition symbol type)))
        (when doc
          (setf result (list* type doc result)))))
    result))

(defimplementation describe-definition (name type)
  (case type
    (:variable (documentation name 'variable))
    (:function (documentation name 'function))
    (:class (documentation name 'class))
    (t nil)))

;;; Debugging

(eval-when (:compile-toplevel :load-toplevel)
  (import
   '(si::*break-env*
     si::*ihs-top*
     si::*ihs-current*
     si::*ihs-base*
     si::*frs-base*
     si::*frs-top*
     si::*tpl-commands*
     si::*tpl-level*
     si::frs-top
     si::ihs-top
     si::ihs-fun
     si::ihs-env
     si::sch-frs-base
     si::set-break-env
     si::set-current-ihs
     si::tpl-commands)))

(defvar *backtrace* '())

(defun in-swank-package-p (x)
  (and
   (symbolp x)
   (member (symbol-package x)
           (list #.(find-package :swank)
                 #.(find-package :swank/backend)
                 #.(ignore-errors (find-package :swank-mop))
                 #.(ignore-errors (find-package :swank-loader))))
   t))

(defun is-swank-source-p (name)
  (setf name (pathname name))
  #+(or)
  (pathname-match-p
   name
   (make-pathname :defaults swank-loader::*source-directory*
                  :name (pathname-name name)
                  :type (pathname-type name)
                  :version (pathname-version name)))
  nil)

(defun is-ignorable-fun-p (x)
  (or
   (in-swank-package-p (frame-name x))
   (multiple-value-bind (file position)
       (ignore-errors (si::compiled-function-file (car x)))
     (declare (ignore position))
     (if file (is-swank-source-p file)))))

(defmacro find-ihs-top (x)
  (declare (ignore x))
  '(si::ihs-top))

(defimplementation call-with-debugging-environment (debugger-loop-fn)
  (declare (type function debugger-loop-fn))
  (let* (;;(*tpl-commands* si::tpl-commands)
         (*ihs-base* 0)
         (*ihs-top* (find-ihs-top 'call-with-debugging-environment))
         (*ihs-current* *ihs-top*)
         (*frs-base* (or (sch-frs-base 0 #|*frs-top*|# *ihs-base*) (1+ (frs-top))))
         (*frs-top* (frs-top))
         (*read-suppress* nil)
         ;;(*tpl-level* (1+ *tpl-level*))
         (*backtrace* (loop for ihs from 0 below *ihs-top*
                            collect (list (si::ihs-fun ihs)
                                          (si::ihs-env ihs)
                                          nil))))
    (declare (special *ihs-current*))
    (loop for f from *frs-base* to *frs-top*
          do (let ((i (- (si::frs-ihs f) *ihs-base* 1)))
               (when (plusp i)
                 (let* ((x (elt *backtrace* i))
                        (name (si::frs-tag f)))
                   (unless (mkcl:fixnump name)
                     (push name (third x)))))))
    (setf *backtrace* (remove-if #'is-ignorable-fun-p (nreverse *backtrace*)))
    (setf *tmp* *backtrace*)
    (set-break-env)
    (set-current-ihs)
    (let ((*ihs-base* *ihs-top*))
      (funcall debugger-loop-fn))))

(defimplementation call-with-debugger-hook (hook fun)
  (let ((*debugger-hook* hook)
        (*ihs-base* (find-ihs-top 'call-with-debugger-hook)))
    (funcall fun)))

(defimplementation compute-backtrace (start end)
  (when (numberp end)
    (setf end (min end (length *backtrace*))))
  (loop for f in (subseq *backtrace* start end)
        collect f))

(defimplementation format-sldb-condition (condition)
  "Format a condition for display in SLDB."
  ;;(princ-to-string condition)
  (format nil "~A~%In thread: ~S" condition mt:*thread*)
  )

(defun frame-name (frame)
  (let ((x (first frame)))
    (if (symbolp x)
      x
      (function-name x))))

(defun function-position (fun)
  (multiple-value-bind (file position)
      (si::compiled-function-file fun)
    (and file (make-location
               `(:file ,(if (stringp file) file (namestring file)))
               ;;`(:position ,position)
               `(:end-position , position)))))

(defun frame-function (frame)
  (let* ((x (first frame))
         fun position)
    (etypecase x
      (symbol (and (fboundp x)
                   (setf fun (fdefinition x)
                         position (function-position fun))))
      (function (setf fun x position (function-position x))))
    (values fun position)))

(defun frame-decode-env (frame)
  (let ((functions '())
        (blocks '())
        (variables '()))
    (setf frame (si::decode-ihs-env (second frame)))
    (dolist (record frame)
      (let* ((record0 (car record))
	     (record1 (cdr record)))
	(cond ((or (symbolp record0) (stringp record0))
	       (setq variables (acons record0 record1 variables)))
	      ((not (mkcl:fixnump record0))
	       (push record1 functions))
	      ((symbolp record1)
	       (push record1 blocks))
	      (t
	       ))))
    (values functions blocks variables)))

(defimplementation print-frame (frame stream)
  (let ((function (first frame)))
    (let ((fname
;;;           (cond ((symbolp function) function)
;;;                 ((si:instancep function) (slot-value function 'name))
;;;                 ((compiled-function-p function)
;;;                  (or (si::compiled-function-name function) 'lambda))
;;;                 (t :zombi))
           (si::get-fname function)
           ))
      (if (eq fname 'si::bytecode)
          (format stream "~A [Evaluation of: ~S]"
                  fname (function-lambda-expression function))
        (format stream "~A" fname)
        )
      (when (si::closurep function)
        (format stream
                ", closure generated from ~A"
                (si::get-fname (si:closure-producer function)))
        )
      )
    )
  )

(defimplementation frame-source-location (frame-number)
  (nth-value 1 (frame-function (elt *backtrace* frame-number))))

(defimplementation frame-catch-tags (frame-number)
  (third (elt *backtrace* frame-number)))

(defimplementation frame-locals (frame-number)
  (loop for (name . value) in (nth-value 2 (frame-decode-env (elt *backtrace* frame-number)))
        with i = 0
        collect (list :name name :id (prog1 i (incf i)) :value value)))

(defimplementation frame-var-value (frame-number var-id)
  (cdr (elt (nth-value 2 (frame-decode-env (elt *backtrace* frame-number))) var-id)))

(defimplementation disassemble-frame (frame-number)
  (let ((fun (frame-fun (elt *backtrace* frame-number))))
    (disassemble fun)))

(defimplementation eval-in-frame (form frame-number)
  (let ((env (second (elt *backtrace* frame-number))))
    (si:eval-in-env form env)))

#|
(defimplementation gdb-initial-commands ()
  ;; These signals are used by the GC.
  #+linux '("handle SIGPWR  noprint nostop"
            "handle SIGXCPU noprint nostop"))

(defimplementation command-line-args ()
  (loop for n from 0 below (si:argc) collect (si:argv n)))
|#

;;;; Inspector

(defmethod emacs-inspect ((o t))
  ; ecl clos support leaves some to be desired
  (cond
    ((streamp o)
     (list*
      (format nil "~S is an ordinary stream~%" o)
      (append
       (list
        "Open for "
        (cond
          ((ignore-errors (interactive-stream-p o)) "Interactive")
          ((and (input-stream-p o) (output-stream-p o)) "Input and output")
          ((input-stream-p o) "Input")
          ((output-stream-p o) "Output"))
        `(:newline) `(:newline))
       (label-value-line*
        ("Element type" (stream-element-type o))
        ("External format" (stream-external-format o)))
       (ignore-errors (label-value-line*
                       ("Broadcast streams" (broadcast-stream-streams o))))
       (ignore-errors (label-value-line*
                       ("Concatenated streams" (concatenated-stream-streams o))))
       (ignore-errors (label-value-line*
                       ("Echo input stream" (echo-stream-input-stream o))))
       (ignore-errors (label-value-line*
                       ("Echo output stream" (echo-stream-output-stream o))))
       (ignore-errors (label-value-line*
                       ("Output String" (get-output-stream-string o))))
       (ignore-errors (label-value-line*
                       ("Synonym symbol" (synonym-stream-symbol o))))
       (ignore-errors (label-value-line*
                       ("Input stream" (two-way-stream-input-stream o))))
       (ignore-errors (label-value-line*
                       ("Output stream" (two-way-stream-output-stream o)))))))
    ((si:instancep o) ;;t
     (let* ((cl (si:instance-class o))
            (slots (clos::class-slots cl)))
       (list* (format nil "~S is an instance of class ~A~%"
                       o (clos::class-name cl))
               (loop for x in slots append
                    (let* ((name (clos::slot-definition-name x))
                           (value (if (slot-boundp o name)
                                      (clos::slot-value o name)
                                      "Unbound"
                                      )))
                      (list
                       (format nil "~S: " name)
                       `(:value ,value)
                       `(:newline)))))))
    (t (list (format nil "~A" o)))))

;;;; Definitions

(defimplementation find-definitions (name)
  (if (fboundp name)
      (let ((tmp (find-source-location (symbol-function name))))
        `(((defun ,name) ,tmp)))))

(defimplementation find-source-location (obj)
  (setf *tmp* obj)
  (or
   (typecase obj
     (function
      (multiple-value-bind (file pos) (ignore-errors (si::compiled-function-file obj))
        (if (and file pos) 
            (make-location
              `(:file ,(if (stringp file) file (namestring file)))
              `(:end-position ,pos) ;; `(:position ,pos)
              `(:snippet
                ,(with-open-file (s file)
                                 (file-position s pos)
                                 (skip-comments-and-whitespace s)
                                 (read-snippet s))))))))
   `(:error (format nil "Source definition of ~S not found" obj))))

;;;; Profiling


(eval-when (:compile-toplevel :load-toplevel)
  ;; At compile-time we need access to the profile package for the
  ;; the following code to be read properly.
  ;; It is a bit a shame we have to load the entire module to get that.
  (require 'profile))


(defimplementation profile (fname)
  (when fname (eval `(profile:profile ,fname))))

(defimplementation unprofile (fname)
  (when fname (eval `(profile:unprofile ,fname))))

(defimplementation unprofile-all ()
  (profile:unprofile-all)
  "All functions unprofiled.")

(defimplementation profile-report ()
  (profile:report))

(defimplementation profile-reset ()
  (profile:reset)
  "Reset profiling counters.")

(defimplementation profiled-functions ()
  (profile:profile))

(defimplementation profile-package (package callers methods)
  (declare (ignore callers methods))
  (eval `(profile:profile ,(package-name (find-package package)))))


;;;; Threads

(defvar *thread-id-counter* 0)

(defvar *thread-id-counter-lock*
  (mt:make-lock :name "thread id counter lock"))

(defun next-thread-id ()
  (mt:with-lock (*thread-id-counter-lock*)
    (incf *thread-id-counter*))
  )

(defparameter *thread-id-map* (make-hash-table))
(defparameter *id-thread-map* (make-hash-table))

(defvar *thread-id-map-lock*
  (mt:make-lock :name "thread id map lock"))

(defparameter +default-thread-local-variables+
  '(*macroexpand-hook*
    *default-pathname-defaults*
    *readtable*
    *random-state*
    *compile-print*
    *compile-verbose*
    *load-print*
    *load-verbose*
    *print-array*
    *print-base*
    *print-case*
    *print-circle*
    *print-escape*
    *print-gensym*
    *print-length*
    *print-level*
    *print-lines*
    *print-miser-width*
    *print-pprint-dispatch*
    *print-pretty*
    *print-radix*
    *print-readably*
    *print-right-margin*
    *read-base*
    *read-default-float-format*
    *read-eval*
    *read-suppress*
    ))
  
(defun thread-local-default-bindings ()
  (let (local)
    (dolist (var +default-thread-local-variables+ local)
      (setq local (acons var (symbol-value var) local))
      )))

;; mkcl doesn't have weak pointers
(defimplementation spawn (fn &key name initial-bindings)
  (let* ((local-defaults (thread-local-default-bindings))
         (thread 
          ;;(mt:make-thread :name name)
          (mt:make-thread :name name
                          :initial-bindings (nconc initial-bindings 
                                                   local-defaults))
           )
         (id (next-thread-id)))
    (mt:with-lock (*thread-id-map-lock*)
      (setf (gethash id *thread-id-map*) thread)
      (setf (gethash thread *id-thread-map*) id))
    (mt:thread-preset
     thread
     #'(lambda ()
         (unwind-protect
              (progn
                ;;(format t "~&Starting thread: ~S.~%" name) (finish-output)
                (mt:thread-detach nil)
                (funcall fn))
           (progn
             ;;(format t "~&Wrapping up thread: ~S.~%" name) (finish-output)
             (mt:with-lock (*thread-id-map-lock*)
               (remhash thread *id-thread-map*)
               (remhash id *thread-id-map*))
             ;;(format t "~&Finished thread: ~S~%" name) (finish-output)
             ))))
    (mt:thread-enable thread)
    (mt:thread-yield)
    thread
    ))

(defimplementation thread-id (thread)
  (block thread-id
    (mt:with-lock (*thread-id-map-lock*)
      (or (gethash thread *id-thread-map*)
          (let ((id (next-thread-id)))
            (setf (gethash id *thread-id-map*) thread)
            (setf (gethash thread *id-thread-map*) id)
            id)))))

(defimplementation find-thread (id)
  (mt:with-lock (*thread-id-map-lock*)
    (gethash id *thread-id-map*)))

(defimplementation thread-name (thread)
  (mt:thread-name thread))

(defimplementation thread-status (thread)
  (if (mt:thread-active-p thread)
      "RUNNING"
      "STOPPED"))

(defimplementation make-lock (&key name)
  (mt:make-lock :name name :recursive t))

(defimplementation call-with-lock-held (lock function)
  (declare (type function function))
  (mt:with-lock (lock) (funcall function)))

(defimplementation current-thread ()
  mt:*thread*)

(defimplementation all-threads ()
  (mt:all-threads))

(defimplementation interrupt-thread (thread fn)
  (mt:interrupt-thread thread fn))

(defimplementation kill-thread (thread)
  (mt:interrupt-thread thread #'mt:terminate-thread)
  )

(defimplementation thread-alive-p (thread)
  (mt:thread-active-p thread))

(defvar *mailbox-lock* (mt:make-lock :name "mailbox lock"))
(defvar *mailboxes* (list))
(declaim (type list *mailboxes*))

(defstruct (mailbox (:conc-name mailbox.))
  thread
  locked-by
  (mutex (mt:make-lock :name "thread mailbox"))
  (semaphore (mt:make-semaphore))
  (queue '() :type list))

(defun mailbox (thread)
  "Return THREAD's mailbox."
  (mt:with-lock (*mailbox-lock*)
    (or (find thread *mailboxes* :key #'mailbox.thread)
        (let ((mb (make-mailbox :thread thread)))
          (push mb *mailboxes*)
          mb))))

(defimplementation send (thread message)
  (handler-case
      (let* ((mbox (mailbox thread))
         (mutex (mailbox.mutex mbox)))
;;     (mt:interrupt-thread
;;      thread
;;      (lambda ()
;;        (mt:with-lock (mutex)
;;          (setf (mailbox.queue mbox)
;;                (nconc (mailbox.queue mbox) (list message))))))

;;     (format t "~&! thread = ~S~% thread = ~S~% message = ~S~%"
;;             mt:*thread* thread message) (finish-output)
    (mt:with-lock (mutex)
      (setf (mailbox.locked-by mbox) mt:*thread*)
      (setf (mailbox.queue mbox)
            (nconc (mailbox.queue mbox) (list message)))
      ;;(format t "*") (finish-output)
      (handler-case
          (mt:semaphore-signal (mailbox.semaphore mbox))
        (condition (condition)
          (format t "Something went bad with semaphore-signal ~A" condition) (finish-output)
          ;;(break)
          ))
      (setf (mailbox.locked-by mbox) nil)
      )
    ;;(format t "+") (finish-output)
    )
    (condition (condition)
      (format t "~&Error in send: ~S~%" condition) (finish-output))
    )
  )

;; (defimplementation receive ()
;;   (block got-mail
;;     (let* ((mbox (mailbox mt:*thread*))
;;            (mutex (mailbox.mutex mbox)))
;;       (loop
;;          (mt:with-lock (mutex)
;;            (if (mailbox.queue mbox)
;;                (return-from got-mail (pop (mailbox.queue mbox)))))
;;          ;;interrupt-thread will halt this if it takes longer than 1sec
;;          (sleep 1)))))


(defimplementation receive-if (test &optional timeout)
  (handler-case
  (let* ((mbox (mailbox (current-thread)))
         (mutex (mailbox.mutex mbox))
         got-one)
    (assert (or (not timeout) (eq timeout t)))
    (loop
       (check-slime-interrupts)
       ;;(format t "~&: ~S~%" mt:*thread*) (finish-output)
       (handler-case
        (setq got-one (mt:semaphore-wait (mailbox.semaphore mbox) 2))
        (condition (condition)
           (format t "~&In (swank-mkcl) receive-if: Something went bad with semaphore-wait ~A~%" condition)
           (finish-output)
           nil
           )
        )
       (mt:with-lock (mutex)
         (setf (mailbox.locked-by mbox) mt:*thread*)
         (let* ((q (mailbox.queue mbox))
                (tail (member-if test q)))
           (when tail 
             (setf (mailbox.queue mbox) (nconc (ldiff q tail) (cdr tail)))
             (setf (mailbox.locked-by mbox) nil)
             ;;(format t "~&thread ~S received: ~S~%" mt:*thread* (car tail))
             (return (car tail))))
         (setf (mailbox.locked-by mbox) nil)
         )

       ;;(format t "/ ~S~%" mt:*thread*) (finish-output)
       (when (eq timeout t) (return (values nil t)))
;;        (unless got-one
;;          (format t "~&In (swank-mkcl) receive-if: semaphore-wait timed out!~%"))
       )
    )
    (condition (condition)
      (format t "~&Error in (swank-mkcl) receive-if: ~S, ~A~%" condition condition) (finish-output)
      nil
      )
    )
  )


(defmethod stream-finish-output ((stream stream))
  (finish-output stream))


;;

;;#+windows
(defimplementation doze-in-repl ()
  (setq *inferior-lisp-sleeping-post* (mt:make-semaphore))
  ;;(loop (sleep 1))
  (mt:semaphore-wait *inferior-lisp-sleeping-post*)
  (mk-ext:quit :verbose t)
  )

