(require "draw-sig.ss")
(require "pingp-sig.ss")
(require "error-lib.ss")

(define pingpU
  (unit/sig pingpS
    (import errorS drawS plt:userspace^)

    ;; Exported Functions
    ;; ------------------

    ;; tracer : posn S (posn S num -> posn) num -> #t
    (define (tracer pos speed f e)
      ; --- error checking: redo
      (check-arg  'tracer (posn? pos) 'posn '1st pos)
      ; the second arg is polymorphic
      (check-proc 'tracer f 3 '3rd "3 arguments")
      (check-arg  'tracer (and (number? e) (> e 0)) "positive number" '4th e)
      ; --- 
      (TT (make-bb pos speed)
	  bb-p
	  (lambda (b t) (let ((s (bb-s b))) (make-bb (f (bb-p b) s t) s)))
	  e))

    (define-struct bb (p s))

    ;; trace-ball : X (X -> posn) (X num -> X) number -> #t
    (define (trace-ball ball ball-posn f e)
      ; --- error checking
      ; the first arg is polymorphic
      (check-proc 'trace-ball ball-posn 1 '2nd "1 argument")
      (check-proc 'trace-ball f         2 '3rd "2 arguments")
      (check-arg  'tracer (and (number? e) (> e 0)) "positive number" '4th e)
      ; ---
      (TT ball ball-posn f e))

    ;; play : (posn X -> B) (num num -> S) (B -> posn) (B num -> B) -> #t
    (define (play make-ball make-speed ball-posn move)
      (check-proc 'play make-ball  2 '1st "2 arguments")
      (check-proc 'play make-speed 2 '2nd "2 arguments")
      (check-proc 'play ball-posn  1 '3rd "1 argument")
      (check-proc 'play move       2 '4th "2 arguments")
      (set-pad)
      (unset-trace)
      (play2 make-ball make-speed ball-posn move))

    ;; landed-on-paddle? : posn -> bool
    (define (landed-on-paddle? ballp)
      (error 'landed-on-paddle?
	"eval (play make-ball make-vec ball-posn move) first"))

    ;; change-speed : int[> 0] -> void
    (define (change-speed s)
      (check-arg 'change-speed (and (integer? s) (> s 0)) "positive integer" '1st s)
      (set! SLEEP (/ 10 s)))

    ;; change-wind : int[> 0] -> void
    (define (change-wind s)
      (check-arg 'change-wind (and (integer? s) (> s 0)) "positive integer" '1st s)
      (set! SWITCH s))

    ;; change-width : int[> 200] -> void
    (define (change-width s)
      (check-arg 'change-width (and (integer? s) (> s 200)) "integer [> 200]" '1st s)
      (set! EAST s))

    ;; change-height : int[> 200] -> void
    (define (change-height s)
      (check-arg 'change-height (and (integer? s) (> s 200)) "integer [> 200]" '1st s)
      (set! SOUTH s))

    (define NORTH 0)
    (define SOUTH 400)
    (define WEST  0)
    (define EAST  400)

    ;; protect : (listof ball)
    ;;           ((listof ball) -> (listof ball))
    ;;           ((listof ball) -> (listof ball))
    ;;           ((listof ball) -> (listof ball))
    ;;           ((listof ball) -> (listof posn))
    ;;           -> void
    (define (protect objs move-objs remove-objs-hit-paddle remove-outside-objs objs-posn)
      ; --- error checking
      (check-arg 'protect (list? objs) 'list        '1st objs)
      (check-proc 'protect move-objs              1 '2nd "1 argument")
      (check-proc 'protect remove-objs-hit-paddle 1 '3rd "1 argument")
      (check-proc 'protect remove-outside-objs    1 '4th "1 argument")
      (check-proc 'protect objs-posn              1 '5th "1 argument")
      ; --- 
      (ready-to-go?)
      (let* ((objs#  (length objs))
	     (PAD_Y0 (- (quotient SOUTH 2) (quotient PAD_LENGTH 2)))
	     (west   (box (make-posn 0 0)));; fake: to reuse move-paddle and make-landed
	     (east   (box (make-posn (- EAST (+ PAD_WIDTH PAD_DIST_WALL)) PAD_Y0))))
	(set! landed-on-paddle? (make-landed-on-paddle east west))
	(draw-paddle (unbox east))
	;; --- the loop
	(let PLAY ((ball0 objs) (p0 (objs-posn objs)) (hits 0))
	  (if (null? ball0)
	      (printf "You caught ~s balls, but ~s hit the wall." hits (- objs# hits))
	      (begin
		(for-each draw-ball p0)
		(sleep SLEEP)
		(for-each clear-ball p0)
		(let* ((ball1 (move-objs ball0))
		       (ball2 (remove-objs-hit-paddle ball1))
		       (ball3 (remove-outside-objs ball2))
		       (p1    (objs-posn ball3)))
		  (move-paddle east west (ready-mouse-click @VP))
		  (PLAY ball3 p1 (+ hits (- (length ball1) (length ball2))))))))
	;; --- clean up
	(stop)))

    ;; Hidden Definitions
    ;; ==================

    ;; Global Properties (initialized by set-up!)
    ;; ------------------------------------------

    (define BG-COLOR GREEN)
    (define BALL-COLOR RED)
    (define PAD-COLOR BLUE)
    (define (set-pad) (set! PAD-COLOR BLUE)) 
    (define (unset-pad) (set! PAD-COLOR WHITE)) 
    (define TRACE-COLOR WHITE)
    (define (set-trace) (set! TRACE-COLOR GREEN)) 
    (define (unset-trace) (set! TRACE-COLOR WHITE)) 

    (define SLEEP .15)
    (define SWITCH 10000)
    (define CENTER (quotient EAST 2))
    (define PADDLE-X EAST)
    (define PADDLE-Y (quotient SOUTH 2))

    ;; Hidden Functions: Tracing and Playing
    ;; -------------------------------------

    ;; TT : X (X -> posn) (X -> X) num[n] -> #t
    ;; effect: trace X's movement for n steps on canvas
    (define (TT ball ball-posn f e)
      (start2 EAST SOUTH)
      (let dl ((ball0 ball) (s 1))
	(if (> s e) #t
	  (let ((ball1 (f ball0 1)))
	    (and
	      (draw-solid-disk (ball-posn ball1) 3 RED)
	      (draw-solid-line (ball-posn ball0) (ball-posn ball1))
	      (dl ball1 (+ s 1)))))))

;    (define (check s make-ball ball-posn move)
;      (check-proc s make-ball  2 '1st "2 arguments")
;      (check-proc s ball-posn  1 '2nd "1 argument")
;      (check-proc s move       2 '3rd "2 arguments"))

    (define (play2 make-ball make-speed ball-posn move)
      (ready-to-go?)
      (let* ((rn-10-10 (lambda ()
			 (let ((n (random 20)))
			   (if (< n 10) (- n 10) (- n  9)))))
	     (posn0 (make-posn (quotient EAST 2) (quotient SOUTH 2)))
	     (PAD_Y0 (- (quotient SOUTH 2) (quotient PAD_LENGTH 2)))
	     (west (box (make-posn PAD_DIST_WALL                        PAD_Y0)))
	     (east (box (make-posn (- EAST (+ PAD_WIDTH PAD_DIST_WALL)) PAD_Y0)))
	     (start-time (current-seconds)))
	(set! landed-on-paddle? (make-landed-on-paddle east west))
	(draw-paddle (unbox west))
	(draw-paddle (unbox east))
	;; --- the loop
	(let play ((ball0 (make-ball posn0 (make-speed (rn-10-10) (rn-10-10)))) (p0 posn0) (i 1))
	  (unless (or (< (posn-x p0) 0) (< EAST (posn-x p0)))
	    (draw-ball p0) (sleep SLEEP) (clear-ball p0)
	    (let* ((ball1 (move ball0 1)) (p1 (ball-posn ball1)))
	      (draw-solid-line p0 p1 TRACE-COLOR)
	      (move-paddle east west (ready-mouse-click @VP))
	      (if (zero? (modulo i SWITCH))
		  (play (make-ball p1 (make-speed (rn-10-10) (rn-10-10))) p1 1)
		  (play ball1 p1 (add1 i))))))
	;; --- clean up
	(printf "You kept the ball in play for ~s seconds." (- (current-seconds) start-time))
	(stop)))

    ;; move-paddle : (box posn) (box posn) (union #f mouse-click) -> void
    ;; effect: modify the appropriate box, if mouse was clicked
    (define (move-paddle east west mc)
      (let ((new-posn (and mc (center-paddle (mouse-click-posn mc)))))
	(cond
	  ((not new-posn) (void))
	  ((in-west-zone? new-posn) (move-one-paddle west new-posn))
	  ((in-east-zone? new-posn) (move-one-paddle east new-posn)))))

    ;; move-one-paddle : (box posn) -> void
    ;; effect: modify the boxe, re-draw at appropriate place
    (define (move-one-paddle a-paddle a-posn)
      (clear-paddle (unbox a-paddle))
      (set-box! a-paddle (new-paddle (unbox a-paddle) (posn-y a-posn)))
      (draw-paddle (unbox a-paddle)))

    ;; ready-to-go? : -> void
    ;; effect: set up window, make announcement, wait for user to start the game
    (define (ready-to-go?)
      (start2 EAST SOUTH)
      (draw-solid-rect   (make-posn (- (quotient EAST 2) 100) 10) 200 20 BG-COLOR)
      ((draw-string @VP) (make-posn (- (quotient EAST 2)  65) 20)
       "Click anywhere when ready!" RED)
      (let loop () (unless (ready-mouse-click @VP) (loop)))
      (draw-solid-rect   (make-posn (- (quotient EAST 2) 100) 10) 200 20 BG-COLOR))
 
    (define (start2 x y)
      (start x y)
      (draw-solid-rect (make-posn 0 0) EAST SOUTH BG-COLOR))

    ;; The Graphical Ball Representation
    ;; ---------------------------------
    (define BALL-RADIUS 5)
    (define (draw-ball p) (draw-solid-disk p BALL-RADIUS RED))
    (define (clear-ball p) (draw-solid-disk p BALL-RADIUS WHITE))

    ;(define (draw-ball p)
    ;  (set! draw-ball ((draw-pixmap-posn "Gifs/redball.gif") @VP))
    ;  (draw-ball p))
    ;(define (clear-ball p)
    ;  (set! clear-ball ((clear-pixmap-posn "Gifs/redball.gif") @VP))
    ;  (clear-ball p))

    ;; The Graphical Paddle Representation
    ;; ------------------------------------
    (define PAD_WIDTH  3)
    (define PAD_LENGTH 50)
    (define PAD_DIST_WALL  0)

    (define (draw-paddle paddle) 
      (draw-solid-rect paddle PAD_WIDTH PAD_LENGTH PAD-COLOR))
    (define (clear-paddle paddle)
      (draw-solid-rect paddle PAD_WIDTH PAD_LENGTH BG-COLOR))

    ;; center-paddle : posn -> posn
    (define (center-paddle p)
      (make-posn (posn-x p) (- (posn-y p) (quotient PAD_LENGTH 2))))
	
    (define (new-paddle paddle new-y)
      (make-posn (posn-x paddle) new-y))

    (define (make-landed-on-paddle east west)
      (lambda (pball)
	(let ((y-pad (posn-y
		       (unbox
			 (cond
			   ((= EAST (posn-x pball)) east)
			   ((= WEST (posn-x pball)) west)
			   (else (error 'landed-on-paddle
				   "this ball has not reached a wall: ~s" pball)))))))
	  (<= y-pad (posn-y pball) (+ y-pad PAD_LENGTH)))))
      
    (define (in-west-zone? p) (<= 0 (posn-x p) CENTER))
    (define (in-east-zone? p) (<= CENTER (posn-x p) EAST))

    ))

(compound-unit/sig (import (PLT : plt:userspace^))
  (link
    [ERR   : errorS (errorU)]   
    [DRAW  : drawS  ((require-unit/sig "draw-lib.ss") PLT)]
    [PINGP : pingpS (pingpU ERR DRAW PLT)])
  (export (open DRAW) (open PINGP)))