;;;-*-Mode: LISP; Package: CCL -*-
;;;
;;; Copyright 1994-2009 Clozure Associates
;;;
;;; Licensed under the Apache License, Version 2.0 (the "License");
;;; you may not use this file except in compliance with the License.
;;; You may obtain a copy of the License at
;;;
;;;     http://www.apache.org/licenses/LICENSE-2.0
;;;
;;; Unless required by applicable law or agreed to in writing, software
;;; distributed under the License is distributed on an "AS IS" BASIS,
;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;;; See the License for the specific language governing permissions and
;;; limitations under the License.

(in-package "CCL")

(defvar *inhibit-abort* nil)

;;; If any bits in the *periodic-task-mask* are set in the
;;; ptaskstate.flags word of a periodic task, it will not be run
(defvar *periodic-task-mask* 0)

(defmethod print-object ((p periodic-task) stream)
  (print-unreadable-object (p stream :type t :identity t)
    (format stream "~s ~d"
	    (ptask.name p)
	    (ptaskstate.interval (ptask.state p)))))

(defvar *periodic-task-lock* (make-lock))

(defun find-named-periodic-task (name)
  (dolist (task *%periodic-tasks%*)
    (when (eq name (ptask.name task))
      (return task))))

(defun %install-periodic-task (name function interval &optional 
                                    (flags 0)
                                    (privatedata (%null-ptr)))
  (with-lock-grabbed (*periodic-task-lock*)
   (let* ((already (find-named-periodic-task name))
          (state (if already (ptask.state already)
                   (%istruct 'ptaskstate 0 0 0 0)))
          (task (or already (%istruct 'periodic-task state name nil))))
     (setf (ptask.function task) function)
     (setf (ptaskstate.interval state) interval
           (ptaskstate.flags state ) flags
           (ptaskstate.privatedata state) privatedata
           (ptaskstate.nexttick state) (+ (get-tick-count) interval))
     (unless already (push task *%periodic-tasks%*))
     (let* ((interval-in-seconds (/ interval (float *ticks-per-second*))))
       (if (< interval-in-seconds *periodic-task-interval*)
         (set-periodic-task-interval interval-in-seconds)))
     task)))

(defmacro with-periodic-task-mask ((mask) &body body)
  (let ((thunk (gensym)))
    `(let ((,thunk #'(lambda () ,@body)))
       (funcall-with-periodic-task-mask ,mask ,thunk))))

(defvar *periodic-task-masks* nil)

; All this hair is so that multiple processes can vote on the *periodic-task-mask*
(defun funcall-with-periodic-task-mask (mask  thunk)
  (let* ((cell (list mask)))
    (declare (dynamic-extent cell))
    (flet ((logior-list (list)
             (declare (type list list))
             (let ((res 0))
               (declare (fixnum res))
               (loop
                 (when (null list) (return res))
                 (setq res (%ilogior res (pop list)))))))
      (declare (inline logior-list))
      (unwind-protect
        (progn
          (without-interrupts
           (setf (cdr cell) *periodic-task-masks*
                 *periodic-task-masks* cell)
           (setq *periodic-task-mask* (logior-list *periodic-task-masks*))
)
          (funcall thunk))
        (without-interrupts
         (let* ((first *periodic-task-masks*)
                (this first)
                (last nil))
           (declare (type cons first this last))
           (loop
             (when (eq this cell)
               (if last
                 (setf (cdr last) (cdr this))
                 (pop first))
               (return (setq *periodic-task-masks* first)))
             (setq last this
                   this (cdr this))))
         (setq *periodic-task-mask* (logior-list *periodic-task-masks*)))))))

(defparameter *invoke-debugger-hook-on-interrupt* nil)

(define-condition interrupt-signal-condition (condition) ()
  (:report "interrupt signal"))

(defmethod force-break-in-listener ((p process))
  (process-interrupt p
		     #'(lambda ()
                         (multiple-value-bind (vars inits old-vals) (%check-error-globals)
                           (progv vars old-vals
                             (mapcar (lambda (v f) (set v (funcall f))) vars inits)
                             (let ((condition (make-condition 'interrupt-signal-condition))
                                   (*top-error-frame* (%current-exception-frame)))
                               (ignoring-without-interrupts
                                 (when *invoke-debugger-hook-on-interrupt*
                                   (let* ((hook *debugger-hook*)
                                          (*debugger-hook* nil))
                                     (when hook
                                       (funcall hook condition hook))))
                                 (%break-in-frame *top-error-frame* condition)
                                 (clear-input *terminal-io*))))))))

(defglobal *quit-interrupt-hook* nil)

(defun force-async-quit (signum)
  (when *quit-interrupt-hook*
    (multiple-value-bind (req opt restp) (function-args *quit-interrupt-hook*)
      (if (and (= req 0) (= opt 0) (not restp))
        (funcall *quit-interrupt-hook*)
        (funcall *quit-interrupt-hook* signum))))
  ;; Exit by resignalling, as per http://www.cons.org/cracauer/sigint.html
  (quit #'(lambda ()
            (ff-call (%kernel-import target::kernel-import-lisp-sigexit) :signed signum)
            ;; Shouldn't get here
            (#__exit 143))))

(defstatic *running-periodic-tasks* nil)

(defun cmain ()
  (thread-handle-interrupts))


(defvar *select-interactive-process-hook* nil)

(defun select-interactive-abort-process ()
  (flet ((maybe-proc (proc) (and proc (process-active-p proc) proc)))
    (or (maybe-proc (and *select-interactive-process-hook*
                         (funcall *select-interactive-process-hook*)))
        (maybe-proc *interactive-abort-process*)
        (let* ((sr (input-stream-shared-resource *terminal-input*)))
          (when sr
            (or (maybe-proc (shared-resource-current-owner sr))
                (maybe-proc (shared-resource-primary-owner sr))))))))

(defun handle-gc-hooks ()
  (let ((bits *gc-event-status-bits*))
    (declare (fixnum bits))
    (cond ((logbitp $gc-postgc-pending-bit bits)
           (setq *gc-event-status-bits*
                 (logand (lognot (ash 1 $gc-postgc-pending-bit))
                         bits))
           (let ((f *post-gc-hook*))
             (when (functionp f) (funcall f)))))))

(defconstant $user-interrupt-break 1)
(defconstant $user-interrupt-quit 2)

(defun housekeeping ()
  (progn
    (handle-gc-hooks)
    (unless *inhibit-abort*
      (let* ((id (pending-user-interrupt))
             (kind (logand #xFF id)))
        (cond ((eql kind $user-interrupt-quit)
               ;; Try to use a process that has a shot at reporting any problems
               ;; in case of bugs in user hook.
               (let* ((proc (or (select-interactive-abort-process)
                                *initial-process*))
                      (signum (ash id -8)))
                 (process-interrupt proc #'force-async-quit signum)))
              ((eql kind $user-interrupt-break)
               (let* ((proc (select-interactive-abort-process)))
                 (if proc
                   (force-break-in-listener proc)))))))
    (flet ((maybe-run-periodic-task (task)
             (let ((now (get-tick-count))
                   (state (ptask.state task)))
               (when (and (>= (- now (ptaskstate.nexttick state))
                              0)
                          (eql 0 (logand (the fixnum (ptaskstate.flags state))
                                         (the fixnum *periodic-task-mask*))))
                 (setf (ptaskstate.nexttick state)
                       (+ now (ptaskstate.interval state)))
                 (funcall (ptask.function task))))))
      (let ((event-dispatch-task *event-dispatch-task*))
        (maybe-run-periodic-task event-dispatch-task)
        (with-lock-grabbed (*periodic-task-lock*)
          (bitclrf $gc-allow-stack-overflows-bit *gc-event-status-bits*)
          (unless *running-periodic-tasks*
            (let-globally ((*running-periodic-tasks* t))
              (dolist (task *%periodic-tasks%*)
                (unless (eq task event-dispatch-task)
                  (maybe-run-periodic-task task))))))))))


(defun %remove-periodic-task (name)
  (with-lock-grabbed (*periodic-task-lock*)
    (let ((task (find-named-periodic-task name)))
      (when task
        (if (setq *%periodic-tasks%* (delete task *%periodic-tasks%*))
          (let* ((min-ticks target::target-most-positive-fixnum))
            (dolist (other *%periodic-tasks%*
                     (set-periodic-task-interval (/ min-ticks (float *ticks-per-second*))))
              (let* ((other-ticks
                      (ptaskstate.interval (ptask.state other))))
                (if (< other-ticks min-ticks)
                  (setq min-ticks other-ticks)))))
          (set-periodic-task-interval 1)))
      task)))


(defun auto-flush-interactive-streams ()
  (with-lock-grabbed (*auto-flush-streams-lock*)
    (dolist (s *auto-flush-streams*)
      (when (open-stream-p s)
        (if (or (typep s 'basic-stream)
                (typep s 'buffered-io-stream-mixin))
          (if (ioblock-outbuf-lock (stream-ioblock s t))
            (force-output s)))
        (force-output s)))))

(defun add-auto-flush-stream (s)
  (with-lock-grabbed (*auto-flush-streams-lock*)
    (when (typep s 'output-stream)
      (pushnew s *auto-flush-streams*))))
      
(defun remove-auto-flush-stream (s)
  (with-lock-grabbed (*auto-flush-streams-lock*)
    (setq *auto-flush-streams* (delete s *auto-flush-streams*))))

; Is it really necessary to keep this guy in a special variable ?
(defloadvar *event-dispatch-task* 
  (%install-periodic-task 
   'auto-flush-interactive-streams
   'auto-flush-interactive-streams
   (truncate *ticks-per-second* 3)	;run 3 times/sec
   (+ $ptask_draw-flag $ptask_event-dispatch-flag)))


(defun event-ticks ()
  (let ((task *event-dispatch-task*))
    (when task (ptaskstate.interval (ptask.state task)))))

(defun set-event-ticks (n)
  (setq n (require-type n '(integer 0 32767)))   ;  Why this weird limit ?
  (let ((task *event-dispatch-task*))
    (when task (setf (ptaskstate.interval (ptask.state task)) n))))

;; Making the *initial-process* quit will cause an exit(),
;; though it might be nicer if all processes were shut down
;; in an orderly manner first.  This is the not-so-nice way
;; of quitting ...
(defun %quit ()
  (quit))



; end of L1-events.lisp

