;; -*- lexical-binding: t; -*-
(require 'sly-stickers "contrib/sly-stickers")
(require 'sly-tests "lib/sly-tests")
(require 'cl-lib)
(require 'ert-x)

(defvar sly-stickers--test-debug nil)

(defun sly-stickers--call-with-fixture (function forms sticker-prefixes)
  (let ((file (make-temp-file "sly-stickers--fixture"))
        (sly-flash-inhibit t)
        ;; important HACK so this doesn't fail with the `sly-retro'
        ;; contrib.
        (sly-net-send-translator nil))
    (sly-eval-async '(cl:ignore-errors (cl:delete-package :slynk-stickers-fixture)))
    (sly-sync-to-top-level 1)
    (unwind-protect
        (with-current-buffer
            (find-file file)
          (lisp-mode)
          (insert (mapconcat #'pp-to-string
                             (append '((defpackage :slynk-stickers-fixture (:use :cl))
                                       (in-package :slynk-stickers-fixture))
                                     forms)
                             "\n"))
          (write-file file)
          (cl-loop for prefix in sticker-prefixes
                   do
                   (goto-char (point-max))
                   (search-backward prefix)
                   (call-interactively 'sly-stickers-dwim))
          (funcall function)
          (sly-sync-to-top-level 1))
      (if sly-stickers--test-debug
          (sly-message "leaving file %s" file)
        (let ((visitor (find-buffer-visiting file)))
          (when visitor (kill-buffer visitor)))
        (delete-file file))
      )))

(cl-defmacro sly-stickers--with-fixture ((forms sticker-prefixes) &rest body)
  (declare (indent defun) (debug (sexp &rest form)))
  `(sly-stickers--call-with-fixture (lambda () ,@body) ,forms ,sticker-prefixes))

(defun sly-stickers--topmost-sticker ()
  (car (sly-button--overlays-at
        (point) (lambda (o) (eq (button-type o) 'sly-stickers-sticker)))))

(defun sly-stickers--base-face (sticker)
  (let ((face (overlay-get sticker 'face)))
    (if (atom face)
        face
      (plist-get face :inherit))))

(defun sly-stickers--face-p (face)
  (let* ((sticker (sly-stickers--topmost-sticker))
         (actual (sly-stickers--base-face sticker)))
    (eq face actual)))

(define-sly-ert-test stickers-basic-navigation ()
  "Test that setting stickers and navigating to them works"
  (sly-stickers--with-fixture ('((defun foo () (bar (baz)))
                                 (defun quux () (coiso (cena))))
                               '("(bar" "(baz" "(coiso"))
    (goto-char (point-min))
    (ert-simulate-command '(sly-stickers-next-sticker 1))
    (save-excursion
      (should (equal (read (current-buffer)) '(bar (baz)))))
    (ert-simulate-command '(sly-stickers-next-sticker 1))
    (save-excursion
      (should (equal (read (current-buffer)) '(baz))))
    (ert-simulate-command '(sly-stickers-next-sticker 1))
    (save-excursion
      (should (equal (read (current-buffer)) '(coiso (cena)))))
    (should (eq 'sly-stickers-placed-face
                (sly-stickers--base-face (sly-stickers--topmost-sticker))))))

(define-sly-ert-test stickers-should-stick ()
  "Test trying to compile the buffer and checking that stickers stuck"
  (sly-stickers--with-fixture ('((defun foo () (bar (baz)))
                                 (defun quux () (coiso (cena))))
                               '("(bar" "(baz" "(coiso"))
    (call-interactively 'sly-compile-defun)
    (sly-sync-to-top-level 1)
    (unless (sly-stickers--face-p 'sly-stickers-armed-face)
      (ert-fail "Expected QUUX stickers to be armed"))
    (ert-simulate-command '(sly-stickers-prev-sticker 1))
    (unless (sly-stickers--face-p 'sly-stickers-placed-face)
      (ert-fail "Compiled just the QUUX defun, didn't expect FOO stickers to arm."))
    (call-interactively 'sly-compile-defun)
    (sly-sync-to-top-level 1)
    (unless (sly-stickers--face-p 'sly-stickers-armed-face)
      (ert-fail "Expected innermost FOO sticker to be armed by now."))
    (ert-simulate-command '(sly-stickers-prev-sticker 1))
    (unless (sly-stickers--face-p 'sly-stickers-armed-face)
      (ert-fail "Expected outermost FOO sticker to also be armed by now."))))

(define-sly-ert-test stickers-when-invalid-dont-stick ()
  "Test trying to make invalid stickers stick"
  (sly-stickers--with-fixture ('((defun foo () (bar (baz))))
                               '("(bar" "(baz" "foo"))
    (goto-char (point-min))
    (ert-simulate-command '(sly-stickers-next-sticker 1))
    (unless (sly-stickers--face-p 'sly-stickers-placed-face)
      (ert-fail "Expected FOO sticker to be unarmed"))
    (call-interactively 'sly-compile-defun)
    (sly-sync-to-top-level 1)
    (unless (sly-stickers--face-p 'sly-stickers-placed-face)
      (ert-fail "Expected invalid FOO sticker to remain unarmed"))
    (ert-simulate-command '(sly-stickers-next-sticker 1))
    (unless (sly-stickers--face-p 'sly-stickers-placed-face)
      (ert-fail "Expected valid BAR sticker to remain unarmed"))
    (ert-simulate-command '(sly-stickers-next-sticker 1))
    (unless (sly-stickers--face-p 'sly-stickers-placed-face)
      (ert-fail "Expected valid BAZ sticker to remain unarmed"))))

(define-sly-ert-test stickers-in-a-file
  "Test compiling a file with some valid and invalid stickers."
  (sly-stickers--with-fixture ('((defun foo () (bar (baz)))
                                 (defun bar (x) (values (list x) 'bar))
                                 (defun baz () 42)
                                 (defun xpto () (let ((coiso)) coiso)))
                               '("(bar" "(baz" "(coiso"))

    (goto-char (point-min))
    (call-interactively 'sly-compile-and-load-file)
    (sly-sync-to-top-level 1)
    (ert-simulate-command '(sly-stickers-next-sticker 1))
    (unless (sly-stickers--face-p 'sly-stickers-armed-face)
      (ert-fail "Expected BAR sticker to be armed"))
    (ert-simulate-command '(sly-stickers-next-sticker 1))
    (unless (sly-stickers--face-p 'sly-stickers-armed-face)
      (ert-fail "Expected BAZ sticker to be armed"))
    (ert-simulate-command '(sly-stickers-next-sticker 1))
    (unless (sly-stickers--face-p 'sly-stickers-placed-face)
      (ert-fail "Didn't expect COISO sticker to be armed"))))

(define-sly-ert-test stickers-record-stuff ()
  "Test actually checking stickers' values."
  (sly-stickers--with-fixture ('((defun foo () (bar (baz)))
                                 (defun bar (x) (values (list x) 'bar))
                                 (defun baz () 42))
                               '("(bar" "(baz"))

    (goto-char (point-min))
    (call-interactively 'sly-compile-and-load-file)
    (sly-sync-to-top-level 1)
    (ert-simulate-command '(sly-stickers-next-sticker 1))
    (unless (sly-stickers--face-p 'sly-stickers-armed-face)
      (ert-fail "Expected BAR sticker to be armed by now"))
    (sly-eval-async '(slynk-stickers-fixture::foo))
    (sly-sync-to-top-level 1)
    (call-interactively 'sly-stickers-fetch)
    (sly-sync-to-top-level 1)
    (unless (sly-stickers--face-p 'sly-stickers-recordings-face)
      (ert-fail "Expected BAR sticker to have some information"))

    ;; This part still needs work
    ;;
    ;; (ert-simulate-command '(sly-stickers-next-sticker 1))
    ;; (ert-simulate-command '(sly-stickers-next-sticker 1))
    ;; (call-interactively 'sly-compile-defun)
    ;; (sly-sync-to-top-level 1)
    ;; (unless (sly-stickers--face-p 'sly-stickers-armed-face)
    ;;   (ert-fail "Expected QUUX sticker to be armed"))
    ;; (sly-eval-async '(cl:ignore-errors (slynk-stickers-fixture::quux)))
    ;; (call-interactively 'sly-stickers-fetch)
    ;; (sly-sync-to-top-level 1)
    ;; (unless (sly-stickers--face-p 'sly-stickers-exited-non-locally-face)
    ;;   (ert-fail "Expected QUXX sticker COISO to have exited non-locally"))
    ))

(provide 'sly-stickers-tests)
