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

;;; Administrivia

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

(in-package swank/mezzano)

;;; swank-mop

(import-swank-mop-symbols :mezzano.clos '(:class-default-initargs
                                          :class-direct-default-initargs
                                          :specializer-direct-methods
                                          :generic-function-declarations))

(defun swank-mop:specializer-direct-methods (obj)
  (declare (ignore obj))
  '())

(defun swank-mop:generic-function-declarations (gf)
  (declare (ignore gf))
  '())

(defimplementation gray-package-name ()
  "MEZZANO.GRAY")

;;;; TCP server

(defclass listen-socket ()
  ((%listener :initarg :listener)))

(defimplementation create-socket (host port &key backlog)
  (make-instance 'listen-socket
                 :listener (mezzano.network.tcp:tcp-listen
                            host
                            port
                            :backlog (or backlog 10))))

(defimplementation local-port (socket)
  (mezzano.network.tcp:tcp-listener-local-port (slot-value socket '%listener)))

(defimplementation close-socket (socket)
  (mezzano.network.tcp:close-tcp-listener (slot-value socket '%listener)))

(defimplementation accept-connection (socket &key external-format
                                             buffering timeout)
  (declare (ignore external-format buffering timeout))
  (loop
    (let ((value (mezzano.network.tcp:tcp-accept (slot-value socket '%listener)
                                                 :wait-p nil)))
      (if value
          (return value)
          ;; Poke standard-input every now and then to keep the console alive.
          (progn (listen)
                 (sleep 0.05))))))

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

;;;; Unix signals
;;;; ????

(defimplementation getpid ()
  0)

;;;; Compilation

(defun signal-compiler-condition (condition severity)
  (signal 'compiler-condition
          :original-condition condition
          :severity severity
          :message (format nil "~A" condition)
          :location nil))

(defimplementation call-with-compilation-hooks (func)
  (handler-bind
      ((error
        (lambda (c)
          (signal-compiler-condition c :error)))
       (warning
        (lambda (c)
          (signal-compiler-condition c :warning)))
       (style-warning
        (lambda (c)
          (signal-compiler-condition c :style-warning))))
    (funcall func)))

(defimplementation swank-compile-string (string &key buffer position filename
                                                policy)
  (declare (ignore buffer policy))
  (let* ((*load-pathname* (ignore-errors (pathname filename)))
         (*load-truename* (when *load-pathname*
                            (ignore-errors (truename *load-pathname*))))
         (sys.int::*top-level-form-number* `(:position ,position)))
    (with-compilation-hooks ()
      (eval (read-from-string (concatenate 'string "(progn " string " )")))))
  t)

(defimplementation swank-compile-file (input-file output-file load-p
                                                  external-format
                                                  &key policy)
  (with-compilation-hooks ()
    (multiple-value-prog1
        (compile-file input-file
                      :output-file output-file
                      :external-format external-format)
      (when load-p
        (load output-file)))))

(defimplementation find-external-format (coding-system)
  (if (or (equal coding-system "utf-8")
          (equal coding-system "utf-8-unix"))
      :default
      nil))

;;;; Debugging

;; Definitely don't allow this.
(defimplementation install-debugger-globally (function)
  (declare (ignore function))
  nil)

(defvar *current-backtrace*)

(defimplementation call-with-debugging-environment (debugger-loop-fn)
  (let ((*current-backtrace* '()))
    (let ((prev-fp nil))
      (sys.int::map-backtrace
       (lambda (i fp)
         (push (list (1- i) fp prev-fp) *current-backtrace*)
         (setf prev-fp fp))))
    (setf *current-backtrace* (reverse *current-backtrace*))
    ;; Drop the topmost frame, which is finished call to MAP-BACKTRACE.
    (pop *current-backtrace*)
    ;; And the next one for good measure.
    (pop *current-backtrace*)
    (funcall debugger-loop-fn)))

(defimplementation compute-backtrace (start end)
  (subseq *current-backtrace* start end))

(defimplementation print-frame (frame stream)
  (format stream "~S" (sys.int::function-from-frame frame)))

(defimplementation frame-source-location (frame-number)
  (let* ((frame (nth frame-number *current-backtrace*))
         (fn (sys.int::function-from-frame frame)))
    (function-location fn)))

(defimplementation frame-locals (frame-number)
  (loop
     with frame = (nth frame-number *current-backtrace*)
     for (name id location repr) in (sys.int::frame-locals frame)
     collect (list :name name
                   :id id
                   :value (sys.int::read-frame-slot frame location repr))))

(defimplementation frame-var-value (frame-number var-id)
  (let* ((frame (nth frame-number *current-backtrace*))
         (locals (sys.int::frame-locals frame))
         (info (nth var-id locals)))
    (if info
        (destructuring-bind (name id location repr)
            info
          (declare (ignore id))
          (values (sys.int::read-frame-slot frame location repr) name))
        (error "Invalid variable id ~D for frame number ~D."
               var-id frame-number))))

;;;; Definition finding

(defun top-level-form-position (pathname tlf)
  (ignore-errors
    (with-open-file (s pathname)
      (loop
         repeat tlf
         do (with-standard-io-syntax
              (let ((*read-suppress* t)
                    (*read-eval* nil))
                (read s nil))))
      (let ((default (make-pathname :host (pathname-host s))))
        (make-location `(:file ,(enough-namestring s default))
                       `(:position ,(1+ (file-position s))))))))

(defun function-location (function)
  "Return a location object for FUNCTION."
  (let* ((info (sys.int::function-debug-info function))
         (pathname (sys.int::debug-info-source-pathname info))
         (tlf (sys.int::debug-info-source-top-level-form-number info)))
    (cond ((and (consp tlf)
                (eql (first tlf) :position))
           (let ((default (make-pathname :host (pathname-host pathname))))
             (make-location `(:file ,(enough-namestring pathname default))
                            `(:position ,(second tlf)))))
          (t
           (top-level-form-position pathname tlf)))))

(defun method-definition-name (name method)
  `(defmethod ,name
       ,@(mezzano.clos:method-qualifiers method)
     ,(mapcar (lambda (x)
                (typecase x
                  (mezzano.clos:class
                   (mezzano.clos:class-name x))
                  (mezzano.clos:eql-specializer
                   `(eql ,(mezzano.clos:eql-specializer-object x)))
                  (t x)))
              (mezzano.clos:method-specializers method))))

(defimplementation find-definitions (name)
  (let ((result '()))
    (labels
        ((frob-fn (dspec fn)
           (let ((loc (function-location fn)))
             (when loc
               (push (list dspec loc) result))))
         (try-fn (name)
           (when (valid-function-name-p name)
             (when (and (fboundp name)
                        (not (and (symbolp name)
                                  (or (special-operator-p name)
                                      (macro-function name)))))
               (let ((fn (fdefinition name)))
                 (cond ((typep fn 'mezzano.clos:standard-generic-function)
                        (dolist (m (mezzano.clos:generic-function-methods fn))
                          (frob-fn (method-definition-name name m)
                                   (mezzano.clos:method-function m))))
                       (t
                        (frob-fn `(defun ,name) fn)))))
             (when (compiler-macro-function name)
               (frob-fn `(define-compiler-macro ,name)
                        (compiler-macro-function name))))))
      (try-fn name)
      (try-fn `(setf name))
      (try-fn `(sys.int::cas name))
      (when (and (symbolp name)
                 (get name 'sys.int::setf-expander))
        (frob-fn `(define-setf-expander ,name)
                 (get name 'sys.int::setf-expander)))
      (when (and (symbolp name)
                 (macro-function name))
        (frob-fn `(defmacro ,name)
                 (macro-function name))))
    result))

;;;; XREF
;;; Simpler variants.

(defun find-all-frefs ()
  (let ((frefs (make-array 500 :adjustable t :fill-pointer 0))
        (keep-going t))
    (loop
       (when (not keep-going)
         (return))
       (adjust-array frefs (* (array-dimension frefs 0) 2))
       (setf keep-going nil
             (fill-pointer frefs) 0)
       ;; Walk the wired area looking for FREFs.
       (sys.int::walk-area
        :wired
        (lambda (object address size)
          (when (sys.int::function-reference-p object)
            (when (not (vector-push object frefs))
              (setf keep-going t))))))
    (remove-duplicates (coerce frefs 'list))))

(defimplementation list-callers (function-name)
  (let ((fref-for-fn (sys.int::function-reference function-name))
        (callers '()))
    (loop
       for fref in (find-all-frefs)
       for fn = (sys.int::function-reference-function fref)
       for name = (sys.int::function-reference-name fref)
       when fn
       do
         (cond ((typep fn 'standard-generic-function)
                (dolist (m (mezzano.clos:generic-function-methods fn))
                  (let* ((mf (mezzano.clos:method-function m))
                         (mf-frefs (get-all-frefs-in-function mf)))
                    (when (member fref-for-fn mf-frefs)
                      (push `((defmethod ,name
                                  ,@(mezzano.clos:method-qualifiers m)
                                ,(mapcar #'specializer-name
                                         (mezzano.clos:method-specializers m)))
                              ,(function-location mf))
                            callers)))))
               ((member fref-for-fn
                        (get-all-frefs-in-function fn))
                (push `((defun ,name) ,(function-location fn)) callers))))
    callers))

(defun specializer-name (specializer)
  (if (typep specializer 'standard-class)
      (mezzano.clos:class-name specializer)
      specializer))

(defun get-all-frefs-in-function (function)
  (when (sys.int::funcallable-std-instance-p function)
    (setf function (sys.int::funcallable-std-instance-function function)))
  (when (sys.int::closure-p function)
    (setf function (sys.int::%closure-function function)))
  (loop
     for i below (sys.int::function-pool-size function)
     for entry = (sys.int::function-pool-object function i)
     when (sys.int::function-reference-p entry)
     collect entry
     when (compiled-function-p entry) ; closures
     append (get-all-frefs-in-function entry)))

(defimplementation list-callees (function-name)
  (let* ((fn (fdefinition function-name))
         ;; Grovel around in the function's constant pool looking for
         ;; function-references.  These may be for #', but they're
         ;; probably going to be for normal calls.
         ;; TODO: This doesn't work well on interpreted functions or
         ;; funcallable instances.
         (callees (remove-duplicates (get-all-frefs-in-function fn))))
    (loop
       for fref in callees
       for name = (sys.int::function-reference-name fref)
       for fn = (sys.int::function-reference-function fref)
       when fn
       collect `((defun ,name) ,(function-location fn)))))

;;;; Documentation

(defimplementation arglist (name)
  (let ((macro (when (symbolp name)
                 (macro-function name)))
        (fn (if (functionp name)
                name
                (ignore-errors (fdefinition name)))))
    (cond
      (macro
       (get name 'sys.int::macro-lambda-list))
      (fn
       (cond
         ((typep fn 'mezzano.clos:standard-generic-function)
          (mezzano.clos:generic-function-lambda-list fn))
         (t
          (function-lambda-list fn))))
      (t :not-available))))

(defun function-lambda-list (function)
  (sys.int::debug-info-lambda-list
   (sys.int::function-debug-info function)))

(defimplementation type-specifier-p (symbol)
  (cond
    ((or (get symbol 'sys.int::type-expander)
         (get symbol 'sys.int::compound-type)
         (get symbol 'sys.int::type-symbol))
     t)
    (t :not-available)))

(defimplementation function-name (function)
  (sys.int::function-name function))

(defimplementation valid-function-name-p (form)
  "Is FORM syntactically valid to name a function?
   If true, FBOUNDP should not signal a type-error for FORM."
  (flet ((length=2 (list)
           (and (not (null (cdr list))) (null (cddr list)))))
    (or (symbolp form)
        (and (consp form) (length=2 form)
             (or (eq (first form) 'setf)
                 (eq (first form) 'sys.int::cas))
             (symbolp (second form))))))

(defimplementation describe-symbol-for-emacs (symbol)
  (let ((result '()))
    (when (boundp symbol)
      (setf (getf result :variable) nil))
    (when (and (fboundp symbol)
               (not (macro-function symbol)))
      (setf (getf result :function)
            (function-docstring symbol)))
    (when (fboundp `(setf ,symbol))
      (setf (getf result :setf)
            (function-docstring `(setf ,symbol))))
    (when (get symbol 'sys.int::setf-expander)
      (setf (getf result :setf) nil))
    (when (special-operator-p symbol)
      (setf (getf result :special-operator) nil))
    (when (macro-function symbol)
      (setf (getf result :macro) nil))
    (when (compiler-macro-function symbol)
      (setf (getf result :compiler-macro) nil))
    (when (type-specifier-p symbol)
      (setf (getf result :type) nil))
    (when (find-class symbol nil)
      (setf (getf result :class) nil))
    result))

(defun function-docstring (function-name)
  (let* ((definition (fdefinition function-name))
         (debug-info (sys.int::function-debug-info definition)))
    (sys.int::debug-info-docstring debug-info)))

;;;; Multithreading

;; FIXME: This should be a weak table.
(defvar *thread-ids-for-emacs* (make-hash-table))
(defvar *next-thread-id-for-emacs* 0)
(defvar *thread-id-for-emacs-lock* (mezzano.supervisor:make-mutex
                                    "SWANK thread ID table"))

(defimplementation spawn (fn &key name)
  (mezzano.supervisor:make-thread fn :name name))

(defimplementation thread-id (thread)
  (mezzano.supervisor:with-mutex (*thread-id-for-emacs-lock*)
    (let ((id (gethash thread *thread-ids-for-emacs*)))
      (when (null id)
        (setf id (incf *next-thread-id-for-emacs*)
              (gethash thread *thread-ids-for-emacs*) id
              (gethash id *thread-ids-for-emacs*) thread))
      id)))

(defimplementation find-thread (id)
  (mezzano.supervisor:with-mutex (*thread-id-for-emacs-lock*)
    (gethash id *thread-ids-for-emacs*)))

(defimplementation thread-name (thread)
  (mezzano.supervisor:thread-name thread))

(defimplementation thread-status (thread)
  (format nil "~:(~A~)" (mezzano.supervisor:thread-state thread)))

(defimplementation current-thread ()
  (mezzano.supervisor:current-thread))

(defimplementation all-threads ()
  (mezzano.supervisor:all-threads))

(defimplementation thread-alive-p (thread)
  (not (eql (mezzano.supervisor:thread-state thread) :dead)))

(defimplementation interrupt-thread (thread fn)
  (mezzano.supervisor:establish-thread-foothold thread fn))

(defimplementation kill-thread (thread)
  ;; Documentation says not to execute unwind-protected sections, but there's
  ;; no way to do that.
  ;; And killing threads at arbitrary points without unwinding them is a good
  ;; way to hose the system.
  (mezzano.supervisor:terminate-thread thread))

(defvar *mailbox-lock* (mezzano.supervisor:make-mutex "mailbox lock"))
(defvar *mailboxes* (list))

(defstruct (mailbox (:conc-name mailbox.))
  thread
  (mutex (mezzano.supervisor:make-mutex))
  (queue '() :type list))

(defun mailbox (thread)
  "Return THREAD's mailbox."
  ;; Use weak pointers to avoid holding on to dead threads forever.
  (mezzano.supervisor:with-mutex (*mailbox-lock*)
    ;; Flush forgotten threads.
    (setf *mailboxes*
          (remove-if-not #'sys.int::weak-pointer-value *mailboxes*))
    (loop
       for entry in *mailboxes*
       do
         (multiple-value-bind (key value livep)
             (sys.int::weak-pointer-pair entry)
           (when (eql key thread)
             (return value)))
       finally
         (let ((mb (make-mailbox :thread thread)))
           (push (sys.int::make-weak-pointer thread mb) *mailboxes*)
           (return mb)))))

(defimplementation send (thread message)
  (let* ((mbox (mailbox thread))
         (mutex (mailbox.mutex mbox)))
    (mezzano.supervisor:with-mutex (mutex)
      (setf (mailbox.queue mbox)
            (nconc (mailbox.queue mbox) (list message))))))

(defvar *receive-if-sleep-time* 0.02)

(defimplementation receive-if (test &optional timeout)
  (let* ((mbox (mailbox (current-thread)))
         (mutex (mailbox.mutex mbox)))
    (assert (or (not timeout) (eq timeout t)))
    (loop
       (check-slime-interrupts)
       (mezzano.supervisor:with-mutex (mutex)
         (let* ((q (mailbox.queue mbox))
                (tail (member-if test q)))
           (when tail
             (setf (mailbox.queue mbox) (nconc (ldiff q tail) (cdr tail)))
             (return (car tail))))
         (when (eq timeout t) (return (values nil t))))
       (sleep *receive-if-sleep-time*))))

(defvar *registered-threads* (make-hash-table))
(defvar *registered-threads-lock*
  (mezzano.supervisor:make-mutex "registered threads lock"))

(defimplementation register-thread (name thread)
  (declare (type symbol name))
  (mezzano.supervisor:with-mutex (*registered-threads-lock*)
    (etypecase thread
      (null
       (remhash name *registered-threads*))
      (mezzano.supervisor:thread
       (setf (gethash name *registered-threads*) thread))))
  nil)

(defimplementation find-registered (name)
  (mezzano.supervisor:with-mutex (*registered-threads-lock*)
    (values (gethash name *registered-threads*))))

(defimplementation wait-for-input (streams &optional timeout)
  (loop
       (let ((ready '()))
         (dolist (s streams)
           (when (or (listen s)
                     (and (typep s 'mezzano.network.tcp::tcp-stream)
                          (mezzano.network.tcp::tcp-connection-closed-p s)))
             (push s ready)))
         (when ready
           (return ready))
         (when (check-slime-interrupts)
           (return :interrupt))
         (when timeout
           (return '()))
         (sleep 1)
         (when (numberp timeout)
           (decf timeout 1)
           (when (not (plusp timeout))
             (return '()))))))

;;;;  Locks

(defstruct recursive-lock
  mutex
  (depth 0))

(defimplementation make-lock (&key name)
  (make-recursive-lock
   :mutex (mezzano.supervisor:make-mutex name)))

(defimplementation call-with-lock-held (lock function)
  (cond ((mezzano.supervisor:mutex-held-p
          (recursive-lock-mutex lock))
         (unwind-protect
              (progn (incf (recursive-lock-depth lock))
                     (funcall function))
           (decf (recursive-lock-depth lock))))
        (t
         (mezzano.supervisor:with-mutex ((recursive-lock-mutex lock))
           (multiple-value-prog1
               (funcall function)
             (assert (eql (recursive-lock-depth lock) 0)))))))

;;;; Character names

(defimplementation character-completion-set (prefix matchp)
  ;; TODO: Unicode characters too.
  (loop
     for names in sys.int::*char-name-alist*
     append
       (loop
          for name in (rest names)
          when (funcall matchp prefix name)
          collect name)))

;;;; Inspector

(defmethod emacs-inspect ((o function))
  (case (sys.int::%object-tag o)
    (#.sys.int::+object-tag-function+
     (label-value-line*
      (:name (sys.int::function-name o))
      (:arglist (arglist o))
      (:debug-info (sys.int::function-debug-info o))))
    (#.sys.int::+object-tag-closure+
     (append
      (label-value-line :function (sys.int::%closure-function o))
      `("Closed over values:" (:newline))
      (loop
         for i below (sys.int::%closure-length o)
         append (label-value-line i (sys.int::%closure-value o i)))))
    (t
     (call-next-method))))

(defmethod emacs-inspect ((o sys.int::weak-pointer))
  (label-value-line*
   (:key (sys.int::weak-pointer-key o))
   (:value (sys.int::weak-pointer-value o))))

(defmethod emacs-inspect ((o sys.int::function-reference))
  (label-value-line*
   (:name (sys.int::function-reference-name o))
   (:function (sys.int::function-reference-function o))))

(defmethod emacs-inspect ((object structure-object))
  (let ((class (class-of object)))
    `("Class: " (:value ,class) (:newline)
                ,@(swank::all-slots-for-inspector object))))

(in-package :swank)

(defmethod all-slots-for-inspector ((object structure-object))
  (let* ((class           (class-of object))
         (direct-slots    (swank-mop:class-direct-slots class))
         (effective-slots (swank-mop:class-slots class))
         (longest-slot-name-length
          (loop for slot :in effective-slots
                maximize (length (symbol-name
                                  (swank-mop:slot-definition-name slot)))))
         (checklist
          (reinitialize-checklist
           (ensure-istate-metadata object :checklist
                                   (make-checklist (length effective-slots)))))
         (grouping-kind
          ;; We box the value so we can re-set it.
          (ensure-istate-metadata object :grouping-kind
                                  (box *inspector-slots-default-grouping*)))
         (sort-order
          (ensure-istate-metadata object :sort-order
                                  (box *inspector-slots-default-order*)))
         (sort-predicate (ecase (ref sort-order)
                           (:alphabetically #'string<)
                           (:unsorted (constantly nil))))
         (sorted-slots (sort (copy-seq effective-slots)
                             sort-predicate
                             :key #'swank-mop:slot-definition-name))
         (effective-slots
          (ecase (ref grouping-kind)
            (:all sorted-slots)
            (:inheritance (stable-sort-by-inheritance sorted-slots
                                                      class sort-predicate)))))
    `("--------------------"
      (:newline)
      " Group slots by inheritance "
      (:action ,(ecase (ref grouping-kind)
                       (:all "[ ]")
                       (:inheritance "[X]"))
               ,(lambda ()
                        ;; We have to do this as the order of slots will
                        ;; be sorted differently.
                        (fill (checklist.buttons checklist) nil)
                        (setf (ref grouping-kind)
                              (ecase (ref grouping-kind)
                                (:all :inheritance)
                                (:inheritance :all))))
               :refreshp t)
      (:newline)
      " Sort slots alphabetically  "
      (:action ,(ecase (ref sort-order)
                       (:unsorted "[ ]")
                       (:alphabetically "[X]"))
               ,(lambda ()
                        (fill (checklist.buttons checklist) nil)
                        (setf (ref sort-order)
                              (ecase (ref sort-order)
                                (:unsorted :alphabetically)
                                (:alphabetically :unsorted))))
               :refreshp t)
      (:newline)
      ,@ (case (ref grouping-kind)
           (:all
            `((:newline)
              "All Slots:"
              (:newline)
              ,@(make-slot-listing checklist object class
                                   effective-slots direct-slots
                                   longest-slot-name-length)))
           (:inheritance
            (list-all-slots-by-inheritance checklist object class
                                           effective-slots direct-slots
                                           longest-slot-name-length)))
      (:newline)
      (:action "[set value]"
               ,(lambda ()
                        (do-checklist (idx checklist)
                          (query-and-set-slot class object
                                              (nth idx effective-slots))))
               :refreshp t)
      "  "
      (:action "[make unbound]"
               ,(lambda ()
                        (do-checklist (idx checklist)
                          (swank-mop:slot-makunbound-using-class
                           class object (nth idx effective-slots))))
               :refreshp t)
      (:newline))))
