;;; -*- Mode:LISP; Package:USER; Base:10; Readtable:CL -*-

;;; Standard window for hacking graphics.

(defflavor hack-window ()
	   (tv:stream-mixin
	    tv:borders-mixin
	    tv:graphics-mixin
	    tv:minimum-window))

(defparameter *hack-window-top*      0)
(defparameter *hack-window-left*     0)
(defparameter *hack-window-height* 500)
(defparameter *hack-window-width*  500)
(defparameter *hack-window-border-size* 2)

(defvar hack1 (make-instance 'hack-window
			     :borders     *hack-window-border-size*
			     :left        *hack-window-left*
			     :top         *hack-window-top*
			     :width       *hack-window-width*
			     :height      *hack-window-height*
			     :deexposed-typeout-action :expose
			     :save-bits t
			     :blinker-p nil))

(defmethod (hack-window :clear) ()
  (send self :draw-rectangle (send self :width) (send self :height) 0 0 tv:alu-andca))
			     
(defparameter *draw-window-top*  0)
(defparameter *draw-window-left* 0)
(defparameter *draw-window-height* 100)
(defparameter *draw-window-width*  100)
(defparameter *draw-window-border-size* 5)

(defvar draw-window (make-instance 'hack-window
			     :borders     *draw-window-border-size*
			     :left        *draw-window-left*
			     :top         *draw-window-top*
			     :width       *draw-window-width*
			     :height      *draw-window-height*
			     :deexposed-typeout-action :expose
			     :save-bits t
			     :blinker-p nil))

;;; Points

(defstruct (point
	     (:conc-name "POINT-")
	     (:callable-constructors nil)
	     (:constructor make-point (coordinate-system x y r theta))
	     (:print-function print-point))
  coordinate-system
  x
  y
  r
  theta)

(defun make-point-cartesian (x y)
  (make-point 'cartesian x y nil nil))

(defun make-point-polar (r theta)
  (make-point 'polar nil nil r theta))

(defun primitive-spread-point (point receiver)
  (funcall receiver (point-coordinate-system point)
		    (point-x                 point)
		    (point-y                 point)
		    (point-r                 point)
		    (point-theta             point)))

(defun spread-point-cartesian (point receiver)
  (primitive-spread-point point
    #'(lambda (c x y r theta)
	(if (or (eq c 'cartesian) (eq c 'both))
	    (funcall receiver x y)
	  (let ((x (* r (cos theta)))
		(y (* r (sin theta))))
	    (setf (point-coordinate-system point) 'both
		  (point-x                 point) x
		  (point-y                 point) y)
	    (funcall receiver x y))))))

(defun spread-point-polar (point receiver)
  (primitive-spread-point point
    #'(lambda (c x y r theta)
	(if (or (eq c 'polar) (eq c 'both))
	    (funcall receiver r theta)
	  (let* ((r (sqrt (+ (* x x) (* y y))))
		 (theta (if (zerop r) 0 (atan y x))))
	    (setf (point-coordinate-system point) 'both
		  (point-r                 point) r
		  (point-theta             point) theta)
	    (funcall receiver r theta))))))

(defun print-point (point stream ignore)
  (si:printing-random-object (point stream :type)
    (primitive-spread-point point
      #'(lambda (c x y r theta)
	  (cond ((eq c 'both)      (format stream "~S<~D,~D;~D,~D>" c x y r theta))
		((eq c 'polar)     (format stream "~S<~D,~D>" c r theta))
		((eq c 'cartesian) (format stream "~S<~D,~D>" c x y)))))))

(defun real-height-and-width (window reciever)
  (funcall reciever (send window ':height) (send window ':width)))

(defun hack-height-and-width (window receiver)
  (real-height-and-width window
    #'(lambda (h w)
	(funcall receiver (truncate h 2) (truncate w 2)))))

(defun draw-points (window point-list alu scaling)
  (hack-height-and-width window
    #'(lambda (center-x center-y)
	(dolist (point point-list)
	  (spread-point-cartesian point
	    #'(lambda (px py)
		(send window ':draw-point (round (+ center-x (* scaling px)))
					  (round (- center-y (* scaling py))) alu)))))))

(defun draw-point-to-point (window point-list alu)
  (hack-height-and-width window
    #'(lambda (center-x center-y)
	(do ((start (first point-list)               end)
	     (end   (second point-list)      (first more))
	     (more  (rest (rest point-list))  (rest more)))
	    ((null end) nil)
	  (spread-point-cartesian start
	    #'(lambda (sx sy)
		(spread-point-cartesian end
		  #'(lambda (ex ey)
		      (send window ':draw-line (round (+ center-x sx)) (round (- center-y sy))
					       (round (+ center-x ex)) (round (- center-y ey))
					       alu nil)))))))))

(defun stars (window how-many)
  (hack-height-and-width window
    #'(lambda (x y)
	(let ((x1 (* x 2))
	      (y1 (* y 2)))
	(let ((point-list '()))
	  (dotimes (count how-many)
	    (push (make-point-cartesian (- x (random x1)) (- y (random y1)))
		  point-list))
	  (send window ':clear)
	  (draw-points window point-list tv:alu-ior 1))))))

(defun degrees (rads)
  (* rads 2 (/ pi 360.0)))

(defvar klingon-shape-list
  (list (list 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0)
        (list 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0)
	(list 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0)
	(list 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0)
	(list 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0)
	(list 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0)
	(list 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0)
	(list 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0)
	(list 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0)
	(list 0 0 0 0 1 0 0 1 1 0 0 1 0 0 0 0)
	(list 0 0 0 0 1 0 0 1 1 0 0 1 0 0 0 0)
	(list 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0)
	(list 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0)
	(list 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0)
	(list 0 0 1 0 0 0 0 1 1 0 0 0 0 1 0 0)
	(list 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0)
	(list 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0)
	(list 0 1 0 0 0 0 1 0 0 1 0 0 0 0 1 0)
	(list 0 1 0 0 1 1 1 1 1 1 1 1 0 0 1 0)
	(list 0 1 0 1 0 0 0 0 0 0 0 0 1 0 1 0)
	(list 1 0 1 0 0 0 0 0 0 0 0 0 0 1 0 1)
	(list 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1)
	(list 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1)
	(list 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1)
	))

(defvar enterprise-shape-list
  (list (list 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0)
        (list 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0)
	(list 0 0 0 0 1 1 0 0 0 0 1 1 0 0 0 0)
	(list 0 0 0 1 1 0 0 0 0 0 0 1 1 0 0 0)
	(list 0 0 0 1 1 0 0 0 0 0 0 1 1 0 0 0)
	(list 0 0 1 1 0 0 0 1 1 0 0 0 1 1 0 0)
	(list 0 0 1 1 0 0 1 0 0 1 0 0 1 1 0 0)
	(list 0 0 1 1 0 0 1 0 0 1 0 0 1 1 0 0)
	(list 0 0 1 1 0 0 0 1 1 0 0 0 1 1 0 0)
	(list 0 0 0 1 1 0 0 0 0 0 0 1 1 0 0 0)
	(list 0 0 0 1 1 0 0 0 0 0 0 1 1 0 0 0)
	(list 0 0 0 0 1 1 0 0 0 0 1 1 0 0 0 0)
	(list 1 1 0 0 0 1 1 1 1 1 1 0 0 0 1 1)
	(list 1 1 0 0 0 0 0 1 1 0 0 0 0 0 1 1)
	(list 1 1 0 0 0 0 0 1 1 0 0 0 0 0 1 1)
	(list 1 1 0 0 0 0 0 1 1 0 0 0 0 0 1 1)
	(list 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1)
	(list 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1)
	(list 1 1 0 0 0 0 0 1 1 0 0 0 0 0 1 1)
	(list 1 1 0 0 0 0 0 1 1 0 0 0 0 0 1 1)
	(list 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1)
	(list 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1)
	(list 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1)
	(list 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1)
	))

(defun bit-shape->point-list (bit-list)
  (let ((point-list nil))
    (do ((row  (- (length bit-list)) (1+ row))
	 (rows bit-list (rest rows)))
	((null? rows) '())
      (do ((column (- (* (length (first rows)) 2)) (1+ column))
	   (bits   (first rows) (rest bits)))
	  ((null? bits) '())
	(when (= (first bits) 1)
	  (push (make-point-cartesian (1+ (* row 2)) (1+ (* column 2))) point-list)
	  (push (make-point-cartesian (1+ (* row 2)) (* column 2)) point-list)
	  (push (make-point-cartesian (* row 2) (1+ (* column 2))) point-list)
	  (push (make-point-cartesian (* row 2) (* column 2)) point-list))))
    point-list))

(defvar klingon-shape-points (bit-shape->point-list klingon-shape-list))
(defvar enterprise-shape-points (bit-shape->point-list enterprise-shape-list))

(defvar square-shape (list (make-point-cartesian 50 50)
			   (make-point-cartesian 50 -50)
			   (make-point-cartesian -50 -50)
			   (make-point-cartesian -50 50)
			   (make-point-cartesian 50 50)))

(defvar triangle-shape (list (make-point-polar 50 0)
			     (make-point-polar 50 (* (/ pi 3) 2))
			     (make-point-polar 50 (* (/ pi 3) 4))
			     (make-point-polar 50 0)))


(defun rotate-shape (shape d-theta)
  (labels (
    (rotate-point (p)
      (spread-point-polar p
	#'(lambda (r theta)
	    (make-point-polar r (+ d-theta theta))))))
    (map 'list #'rotate-point shape)))

(defun test-spin-shape (shape)
  (draw-points hack1 shape tv:alu-ior .5)
    (let ((shape shape)
	  (next-shape '()))
      (dotimes (count 64.)
	(setq next-shape (rotate-shape shape (/ pi 19.)))
	(without-interrupts
	  (draw-points hack1 shape tv:alu-andca .5)
	  (draw-points hack1 next-shape tv:alu-ior .5))
	(setq shape next-shape))))

(defun test-concurrency ()
  (send hack1 ':clear)
  (process-run-function '(:name "foo" :priority 1 :quantum 1) #'test-spin-shape square-shape)
  (process-run-function '(:name "bar" :priority 1 :quantum 1) #'test-spin-shape triangle-shape))

(defflavor meter-window (last-value
			 last-bottom-x
			 last-bottom-y
			 last-top-x
			 last-top-y
			 internal-computer
			 )
	   (tv:stream-mixin
	    tv:centered-label-mixin
	    tv:label-mixin
	    tv:borders-mixin
	    tv:graphics-mixin
	    tv:minimum-window)
  :settable-instance-variables)

(defparameter *meter-window-top*      0)
(defparameter *meter-window-left*     0)
(defparameter *meter-window-height* 50)
(defparameter *meter-window-width*  50)
(defparameter *meter-window-border-size* 1)

(defvar meter1 (make-instance 'meter-window
			     :borders     nil
			     :left        *meter-window-left*
			     :top         *meter-window-top*
			     :width       *meter-window-width*
			     :height      *meter-window-height*
			     :label       "Test"
			     :last-value  0
			     :blinker-p nil))

(defmethod (meter-window :clear) ()
  (send self :draw-rectangle (send self :height) (send self :width) 0 0 tv:alu-andca))

(defvar *overlap-ratio* .5)
(defvar *needle-ratio* 1.6)
(defconstant pi/2 (/ pi 2))

(defmethod (meter-window :draw-meter-and-create-computer) ()
  (let ((width (1- (send self :inside-width)))
	(height (1- (send self :inside-height))))
    (let ((center-x (truncate width 2))
	  (center-y (truncate height 2)))
      (let ((radius (min center-x center-y)))
	(let ((needle-source-y (+ radius center-y)))
	  (send self :draw-circle center-x center-y radius)
	  (labels ((magic-formula (ratio)
		     (- pi/2 (asin (/ ratio 2))))
		   (compute-radial-line-end (ratio theta)
		     (values (truncate (- center-x         (* ratio radius (cos theta))))
			     (truncate (- needle-source-y  (* ratio radius (sin theta)))))))
	    (let ((bottom-theta (magic-formula *overlap-ratio*)))
	      (send self :draw-circular-arc center-x needle-source-y
		    (truncate (* radius *overlap-ratio*))
		    (- pi/2 bottom-theta) (+ pi/2 bottom-theta)))
	    (let ((needle-maximum-theta (magic-formula *needle-ratio*)))
	      #'(lambda (percent)
		  (let ((valid-percent (max (min percent 1.) -1.)))
		    (let ((needle-theta (- pi/2 (* (- valid-percent) needle-maximum-theta))))
		      (values-values
			(compute-radial-line-end *overlap-ratio* needle-theta)
			(compute-radial-line-end *needle-ratio* needle-theta))))))))))))

(defmethod (meter-window :needle) (percent)
  (if tv:exposed-p
      (when (not (= percent last-value))
	(multiple-value-bind (bottom-x bottom-y top-x top-y)
	    (funcall internal-computer percent)
	  (without-interrupts
	    (send self :draw-line last-bottom-x last-bottom-y last-top-x last-top-y tv:alu-xor)
	    (send self :draw-line bottom-x bottom-y top-x top-y tv:alu-xor))
	  (setq last-value    percent
		last-bottom-x bottom-x
		last-bottom-y bottom-y
		last-top-x    top-x
		last-top-y    top-y)))
	  nil))

(defmethod (meter-window :redraw) ()
  (setq internal-computer (send self :draw-meter-and-create-computer))
  (multiple-value-setq (last-bottom-x last-bottom-y last-top-x last-top-y)
    (funcall internal-computer last-value))
  (send self :draw-line last-bottom-x last-bottom-y last-top-x last-top-y tv:alu-xor))

(defmethod (meter-window :after :expose) ()
  (send self :redraw))

(defun tick (meter-window)
  (let ((time (sin (* pi (/ (ash (time:fixnum-microsecond-time) -8.) 4000.0)))))
    (send meter-window :needle time)))


(defun test (tv:w)
  tv:(with-mouse-usurped
      (lock-sheet (main-screen)
	(lock-sheet (who-line-screen)
	  (without-interrupts
	    (let ((ms mouse-sheet) (sw selected-window))
	      (and (sheet-me-or-my-kid-p ms main-screen)
		   (setq mouse-sheet nil))
	      (send who-line-screen :deexpose)
	      (send main-screen     :deexpose)
	      (setq mouse-sheet ms)
	      (send who-line-screen :change-of-size-or-margins :right (- main-screen-height w))
	      (send main-screen     :change-of-size-or-margins :right (- main-screen-height w))
	      (mouse-set-sheet ms)
	      (send main-screen :expose)
	      (send who-line-screen :expose)
	      (and sw (send sw :select))))))))

;(defun meter (window x y radius)
;  (send window :draw-circle x y radius)
;  (let ((needle-source-y (+ y radius)))
;    (labels ((compute-visible-needle-end (ratio theta)
;	       (values (truncate (- x (* ratio radius (cos theta))))
;		       (truncate (- needle-source-y (* ratio radius (sin theta))))))
;	     (magic-formula (ratio)
;	       (- (/ pi 2) (asin (/ ratio 2)))))
;      (let ((bottom-theta  (magic-formula *overlap-ratio*)))
;	(send window :draw-circular-arc x needle-source-y
;	      (truncate (* radius *overlap-ratio*))
;	      (- (/ pi 2) bottom-theta) (+ (/ pi 2) bottom-theta)))
;      (let ((needle-pin-theta (magic-formula *needle-ratio*)))
;	(let (needle-bottom-x
;	      needle-bottom-y
;	      needle-top-x
;	      needle-top-y)
;	  (labels ((draw-needle ()
;		     (send window :draw-line
;			   needle-bottom-x
;			   needle-bottom-y
;			   needle-top-x
;			   needle-top-y tv:alu-xor))
;		   (compute-new-needle-value (percentage)
;		     (let ((needle-theta (- (/ pi 2)(* percentage needle-pin-theta))))
;		       (multiple-value-setq
;			 (needle-bottom-x needle-bottom-y)
;			 (compute-visible-needle-end *overlap-ratio* needle-theta))
;		       (multiple-value-setq
;			 (needle-top-x needle-top-y)
;			 (compute-visible-needle-end *needle-ratio* needle-theta)))))
;	    (compute-new-needle-value 0)
;	    (draw-needle)
;	    (lambda (new-percentage)
;	      (without-interrupts
;		(draw-needle)
;		(compute-new-needle-value new-percentage)
;		(draw-needle)))))))))

(defvar foo (make-instance 'tv::basic-frame))
(defvar bar (make-instance 'hack-window :superior foo))

(defvar foo (make-instance 'tv::basic-constraint-frame
			   ':panes
			   '((label-pane tv:window :blinker-p nil :label ""
					 :height 20
					 :width 100)
			     (data-pane tv:window :blinker-p nil :label ""))
			   ':constraints
			   '((main . ((only-strip)
				      ((only-strip :horizontal (:even)
					(label-pane data-pane)
					((label-pane :ask :pane-size)
					 (data-pane :even)))))))))
						  
(defflavor word-window (word)
	   (tv:borders-mixin
	    tv:minimum-window))

(defmethod (word-window :after :refresh) ()
  (send self :string-out-x-y-centered-explicit word))

(defvar foo (make-instance 'word-window
