#lang scheme/gui
(require scheme/class
         scheme/local
         scheme/bool
         mred
         htdp/error
         htdp/image
         (only-in lang/htdp-beginner image?)
         mrlib/cache-image-snip
         lang/prim
         (for-syntax scheme/base))
(require mrlib/gif)
(require mzlib/runtime-path)
(require mrlib/bitmap-label
         string-constants)
(provide (all-from-out htdp/image))
(provide
  nw:rectangle  place-image   empty-scene   scene+line     )
(provide       big-bang	 )
(provide-higher-order-primitive
 on-tick-event (tock)  )
(provide-higher-order-primitive
 on-redraw (world-to-image)  )
(provide-higher-order-primitive
 on-receive-event (rec)   )
(provide
 key-event?  key=?  )
(provide-higher-order-primitive
 on-key-event (control)  )
(provide-higher-order-primitive
 on-mouse-event (clack)   )
(provide-higher-order-primitive
 stop-when (last-world)   )
(provide-higher-order-primitive
 run-simulation (_ _ _ create-scene)  )
(provide
 run-movie  )
(define (nw:rectangle width height mode color)
  (check-pos 'rectangle width "first")
  (check-pos 'rectangle height "second")
  (check-mode 'rectangle mode "third")
  (check-color 'rectangle color "fourth")
  (put-pinhole (rectangle width height mode color) 0 0))
(define (place-image image x y scene)
  (check-image 'place-image image "first")
  (check-arg 'place-image (number? x) 'integer "second" x)
  (check-arg 'place-image (number? y) 'integer "third" y)
  (check-scene 'place-image scene "fourth")
  (let ([x (number->integer x)]
        [y (number->integer y)])
    (place-image0 image x y scene)))
(define (empty-scene width height)
  (check-pos 'empty-scene width "first")
  (check-pos 'empty-scene height "second")    
  (put-pinhole 
   (overlay (rectangle width height 'solid 'white)
            (rectangle width height 'outline 'black))
   0 0))
(define (scene+line img x0 y0 x1 y1 c)
    (check-arg 'scene+line (scene? img) "scene" "first" "plain image")
  (check-arg 'scene+line (number? x0) "number" "second" x0)
  (check-arg 'scene+line (number? y0) "number" "third" y0)
  (check-arg 'scene+line (number? x1) "number" "fourth" x1)
  (check-arg 'scene+line (number? y1) "number" "fifth" y1)
  (let ([x0 (number->integer x0)]
        [x1 (number->integer x1)]
        [y0 (number->integer y0)]
        [y1 (number->integer y1)])
    (add-line-to-scene0 img x0 y0 x1 y1 c)))
(define big-bang
  (lambda x 
    (define args (length x))
    (if (or (= args 5) (= args 4))
        (apply big-bang0 x) 
        (error 'big-bang msg))))
(define msg
  (string-append
   "big-bang consumes 4 or 5 arguments:\n"
   "-- (big-bang <width> <height> <rate> <world0>)\n"
   "-- (big-bang <width> <height> <rate> <world0> <animated-gif>)\n"
   "see Help Desk."))
(define *run-sema* (make-semaphore 1))
(define *running?* #f)
(define big-bang0
  (case-lambda 
    [(w h delta world) (big-bang w h delta world #f)]
    [(w h delta world animated-gif) 
     (check-pos 'big-bang w "first")
     (check-pos 'big-bang h "second")
                    (check-arg 'big-bang
                (and (number? delta) (<= 0 delta 1000))
                "number [of seconds] between 0 and 1000"
                "third"
                delta)
     (check-arg 'big-bang 
                (boolean? animated-gif)
                "boolean expected"
                "fifth"
                animated-gif)
     (let ([w (coerce w)]
           [h (coerce h)])
       (semaphore-wait *run-sema*)
       (when *running?*  (error 'big-bang "the world is still running"))
       (set! *running?* #t)
       (semaphore-post *run-sema*)
              (set-stop-when-callback (lambda (w) #f))
       (install-world delta world)        (set-and-show-frame w h animated-gif)        (unless animated-gif (set! add-event void))        (set! *the-delta* delta)
       #t)]))
(define (coerce x) (inexact->exact (floor x)))
(define *the-delta* 0.0)
(define (on-tick-event f)
  (check-proc 'on-tick-event f 1 "first" "one argument")
  (check-world 'on-tick-event)
  (set-timer-callback f)
  (send the-time start
        (let* ([w (ceiling (* 1000 the-delta))])
          (if (exact? w) w (inexact->exact w))))
  #t)
(define (on-redraw f)
  (check-proc 'on-redraw f 1 "first" "one argument")
  (check-world 'on-redraw)
  (set-redraw-callback f)
  (redraw-callback)
  #t)
(define (key-event? k)
  (or (char? k) (symbol? k)))
(define (key=? k m)
  (check-arg 'key=? (key-event? k) 'KeyEvent "first" k)
  (check-arg 'key=? (key-event? m) 'KeyEvent "first" m)
  (eqv? k m))
(define (on-key-event f)
  (check-proc 'on-key-event f 2 "first" "two arguments")
  (check-world 'on-key-event)
  (set-key-callback f (current-eventspace))
  #t)
(define (on-receive-event f)
  (check-proc 'on-receive-event f 2 "first" "two arguments")
  (check-world 'on-receive-event)
  (set-receive-callback f)
  #t)
(define (on-mouse-event f)
  (check-proc 'on-mouse-event f 4 "first" "four arguments")
  (check-world 'on-mouse-event)
  (set-mouse-callback f (current-eventspace))
  #t)
(define (stop-when f)
  (check-proc 'stop-when f 1 "first" "one argument")
  (check-world 'stop-when)
  (when (f the-world)
    (callback-stop!))
  (set-stop-when-callback f)
  #t)
(define (run-movie movie)
  (check-arg 'run-movie (list? movie) "list (of images)" "first" movie)
  (for-each (lambda (cand) 
              (check-image 'run-movie cand "first" "list of images"))
            movie)
  (let* ([fst (car movie)]
         [wdt (image-width fst)]
         [hgt (image-height fst)])
    (big-bang wdt hgt (/ 1 27) movie)
    (let run-movie ([movie movie])
      (cond
        [(null? movie) #t]
        [(pair? movie)
         (update-frame (car movie))
         (sleep/yield .05)
         (run-movie (cdr movie))]))))
(define run-simulation 
  (lambda x 
    (define args (length x))
    (if (or (= args 5) (= args 4))
        (apply run-simulation0 x) 
        (error 'run-simulation msg-run-simulation))))
(define msg-run-simulation
  (string-append
   "consumes 4 or 5 arguments:\n"
   "-- (run-simulation <width> <height> <rate> <world-to-world-function>)\n"
   "-- (run-simulation <width> <height> <rate> <world-to-world-function> <create-animated-gif?>)\n"
   "see Help Desk."))
(define run-simulation0
  (case-lambda
    [(width height rate f record?)
     (check-pos 'run-simulation width "first")
     (check-pos 'run-simulation height "second")
     (check-arg 'run-simulation (number? rate) 'number "third" rate)
     (check-proc 'run-simulation f 1 "fourth" "one argument")
     (check-arg 'run-simulation (boolean? record?) 'number "fifth [and optional]" record?)
     (big-bang width height rate 1 record?)
     (on-redraw f)
     (on-tick-event add1)]
    [(width height rate f)
     (run-simulation width height rate f #f)]))
(define (check-pos tag c rank)
  (check-arg tag (and (number? c) (> (coerce c) 0))
             "positive integer" rank c))
(define (check-image tag i rank . other-message)
  (if (and (pair? other-message) (string? (car other-message)))
      (check-arg tag (image? i) (car other-message) rank i)
      (check-arg tag (image? i) "image" rank i)))
(define (check-scene tag i rank)
  (if (image? i)
      (unless (scene? i)
        (error tag "scene expected, given image whose pinhole is at (~s,~s) instead of (0,0)"
               (pinhole-x i) (pinhole-y i)))
      (check-arg tag #f "image" rank i)))
(define (scene? i) (and (= 0 (pinhole-x i)) (= 0 (pinhole-y i))))
(define (check-color tag width rank)
  (check-arg tag (or (symbol? width) (string? width)) 
             "color symbol or string" rank width))
(define (check-mode tag s rank)
  (check-arg tag (or (eq? s 'solid)
                     (eq? s 'outline)
                     (string=? "solid" s)
                     (string=? "outline" s)) "mode (solid or outline)" rank s))
(define (place-image0 image x y scene)
  (define sw (image-width scene))
  (define sh (image-height scene))
  (define ns (overlay/xy scene x y image))
  (define nw (image-width ns))
  (define nh (image-height ns))
  (if (and (= sw nw) (= sh nh)) ns (shrink ns 0 0 sw sh)))
(define (place-image0 image x y scene)
  (define sw (image-width scene))
  (define sh (image-height scene))
  (define ns (overlay/xy scene x y image))
  (define nw (image-width ns))
  (define nh (image-height ns))
  (if (and (= sw nw) (= sh nh)) ns (shrink ns 0 0 (- sw 1) (- sh 1)))) 
(define (add-line-to-scene0 img x0 y0 x1 y1 c)
  (define w (image-width img))  
  (define h (image-height img))
  (cond
    [(and (<= 0 x0) (< x0 w) (<= 0 x1) (< x1 w) (<= 0 y0) (< y0 w) (<= 0 y1) (< y1 w))
     (add-line img x0 y0 x1 y1 c)]
    [(= x0 x1)      (if (<= 0 x0 w) (add-line img x0 (app y0 h) x0 (app y1 h) c) img)]
    [(= y0 y1)      (if (<= 0 y0 h) (add-line img (app x0 w) y0 (app x1 w) y0 c) img)]
    [else 
     (local ((define lin (points->line x0 y0 x1 y1))
             (define dir (direction x0 y0 x1 y1))
             (define-values (upp low lft rgt) (intersections lin w h))
             (define (add x y) (add-line img x0 y0 x y c)))
       (cond
         [(and (< 0 x0 w) (< 0 y0 h))           (case dir
            [(upper-left)  (if (number? upp) (add upp 0) (add 0 lft))]
            [(lower-left)  (if (number? low) (add low h) (add 0 lft))]
            [(upper-right) (if (number? upp) (add upp 0) (add h rgt))]
            [(lower-right) (if (number? low) (add low h) (add w rgt))]
            [else (error 'dir "contract violation: ~e" dir)])]
         [(and (< 0 x1 w) (< 0 y1 h))           (add-line-to-scene0 img x1 y1 x0 y0 c)]
         [else 
          (cond
            [(and (number? upp) (number? low)) (add-line img upp 0 low h c)]
            [(and (number? upp) (number? lft)) (add-line img upp 0 0 lft c)]
            [(and (number? upp) (number? rgt)) (add-line img upp 0 w rgt c)]
            [(and (number? low) (number? lft)) (add-line img low h 0 lft c)]
            [(and (number? low) (number? rgt)) (add-line img low h w rgt c)]
            [(and (number? lft) (number? rgt)) (add-line img 0 lft w rgt c)]
            [else img])]))]))
(define (app y h)
  (cond
    [(and (<= 0 y) (< y h)) y]
    [(< y 0) 0]
    [else (- h 1)]))
(define (direction x0 y0 x1 y1)
  (string->symbol
   (string-append 
    (if (<= y0 y1) "lower" "upper") "-" (if (<= x0 x1) "right" "left"))))
(define-struct lyne (slope y0))
(define (points->line x0 y0 x1 y1)
  (local ((define slope  (/ (- y1 y0) (- x1 x0))))
    (make-lyne slope (- y0 (* slope x0)))))
(define (of ln x) (+ (* (lyne-slope ln) x) (lyne-y0 ln)))
(define (intersections l w h)
  (values
   (opt (X l 0) w) (opt (X l h) w) (opt (lyne-y0 l) h) (opt (of l w) h)))
(define (opt z lft) (if (<= 0 z lft) z false))
(define (X ln h) (/ (- h (lyne-y0 ln)) (lyne-slope ln)))
(define unique-world (cons 1 1))
(define (check-world tag)
  (when (eq? unique-world the-world) 
    (error tag "evaluate (big-bang Number Number Number World) first")))
(define (empty-message? msg)
  (null? msg))
(define the-world unique-world)
(define (update-world f)
  (define new-world (f the-world))
    (if (package? new-world)
      (let ([msg (package-message new-world)]
            [new-world (package-world new-world)])
        (set! the-world new-world)
        (when *out* 
          (if (sexp? msg)
              (unless (empty-message? msg)
                (tcp-send *out* msg))
              (error 'send "Sexp expected; given ~e\n" msg)))
        #f)
      (let ([result (eq? new-world the-world)])
        (set! the-world new-world)
        result)))
(define the-world0 unique-world)
(define (install-world delta w)
  (reset-event-history)
  (set! the-delta delta)
  (update-world (lambda (_) w))
  (set! the-world0 w)
  (vw-setup))
(define the-delta 1000)
(define visible-world #f)
(define (vw-setup)
  (set! visible-world (new pasteboard%))
  (send visible-world set-cursor (make-object cursor% 'arrow)))
(define (vw-init?) (is-a? visible-world pasteboard%))
(define (update-frame pict)
  (send visible-world begin-edit-sequence)
  (send visible-world lock #f)
  (let ([s (send visible-world find-first-snip)])
    (when s
      (send visible-world delete s)))
  (let ([c (send visible-world get-canvas)])
    (let-values ([(px py)
                  (if (is-a? pict cache-image-snip%)
                      (send pict get-pinhole)
                      (values 0 0))]
                 [(cw ch)
                  (send c get-client-size)])
      (send visible-world insert (send pict copy) (- px) (- py))))
  (send visible-world lock #t)
  (send visible-world end-edit-sequence))
(define (set-and-show-frame w h animated-gif)
  (define the-play-back-custodian (make-custodian))
  (define frame (create-frame the-play-back-custodian))
  (set! WIDTH w)
  (set! HEIGHT h)
  (when animated-gif
    (add-stop-and-image-buttons frame the-play-back-custodian))
  (add-editor-canvas frame visible-world w h)
  (send frame show #t))
(define WIDTH 0) 
(define HEIGHT 0)
(define (create-frame the-play-back-custodian)
  (new (class frame%
         (super-new)
         (define/augment (on-close)  
           (callback-stop! 'create-frame)
           (custodian-shutdown-all the-play-back-custodian)))
       (label "DrScheme")
       (stretchable-width #f)
       (stretchable-height #f)
       (style '(no-resize-border metal))))
(define IMAGES "Images")
(define-runtime-path s:pth '(lib "icons/break.png"))
(define-runtime-path i:pth '(lib "icons/file.gif"))
(define (add-stop-and-image-buttons  frame the-play-back-custodian)
  (define p (new horizontal-pane% [parent frame][alignment '(center center)]))
  (define S ((bitmap-label-maker (string-constant break-button-label) s:pth) '_))
  (define I ((bitmap-label-maker IMAGES i:pth) '_))
  (define stop-button
    (new button% [parent p] [label S] [style '(border)]
         [callback (lambda (this-button e) 
                     (callback-stop! 'stop-button)
                     (send this-button enable #f)
                     (send image-button enable #t))]))
  (define image-button 
    (new button% [parent p] [enabled #f] [label I] [style '(border)]
         [callback (lambda (b e)
                     (parameterize ([current-custodian the-play-back-custodian])
                       (define th (thread play-back))
                       (send b enable #f)))]))
  (void))
(define (add-editor-canvas frame visible-world w h)
  (define c 
    (new (class editor-canvas%
           (super-new)
           (define/override (on-char e) (key-callback (send e get-key-code)))
           (define/override (on-event e) (mouse-callback e)))
         (parent frame)
         (editor visible-world)
         (style '(no-hscroll no-vscroll))
         (horizontal-inset INSET)
         (vertical-inset INSET)))
  (send c min-client-width (+ w INSET INSET))
  (send c min-client-height (+ h INSET INSET))
  (send c focus))
(define INSET 5)
(define TICK 'tick)
(define MOUSE 'mouse)
(define KEY 'key)
(define REC 'rec)
(define event-history '())
(define (reset-event-history)
  (set! event-history '()))
(define (add-event type . stuff)
  (set! event-history (cons (cons type stuff) event-history)))
(define (zfill a-num a-len)
  (let ([n (number->string a-num)])
    (string-append (build-string (max (- a-len (string-length n)) 0)
                                 (lambda (i) #\0))
                   n)))
(define (play-back)
    (define (world-transition world fst)
    (case (car fst)
      [(tick)  (timer-callback0 world)]
      [(key)   (key-callback0 world (cadr fst))]
      [(mouse) (mouse-callback0 world (cadr fst) (caddr fst) (cadddr fst))]
      [(rec)   (receive-callback0 world (cadr fst))]
      [else (error 'play-back "bad type of event: ~s" fst)]))
    (define total (+ (length event-history) 1))
  (define image-count 0)
  (define bitmap-list '())
  (define (save-image img)
    (define-values (w h) (send img get-size)) 
    (define (make-bitmap)
      (define bm (make-object bitmap% w h))
      (define dc (make-object bitmap-dc% bm))
      (send dc clear)
      (send img draw dc 0 0 0 0 w h 0 0 #f)
      bm)
    (define bm (make-bitmap)) 
    (set! bitmap-list (cons bm bitmap-list))
    (set! image-count (+ image-count 1))
    (send bm save-file (format "i~a.png" (zfill image-count (string-length (number->string total)))) 'png))
    (define target:dir
    (let* ([cd (current-directory)]
           [dd (get-directory "Select directory for images" #f cd)])
      (if dd dd cd)))
  (parameterize ([current-directory target:dir])
    (let replay ([ev (reverse event-history)][world the-world0])
      (define img (redraw-callback0 world))
      (update-frame (text (format "~a/~a created" image-count total) 18 'red))
      (save-image img)
      (cond
        [(null? ev)
         (update-frame (text (format "creating ~a" ANIMATED-GIF-FILE) 18 'red))
         (create-animated-gif (reverse bitmap-list))
         (update-frame img)]
        [else
         (let ([world1 (world-transition world (car ev))])
           (replay (cdr ev) world1))]))))
(define (create-animated-gif bitmap-list)
  (define intv (if (> +inf.0 *the-delta* 0) (inexact->exact (floor (* 100 *the-delta*))) 5))
  (when (file-exists? ANIMATED-GIF-FILE)
    (delete-file ANIMATED-GIF-FILE))
  (write-animated-gif bitmap-list intv ANIMATED-GIF-FILE #:one-at-a-time? #t #:loop? #f))
(define ANIMATED-GIF-FILE "i-animated.gif")
(define-syntax (define-callback stx)
  (syntax-case stx ()
    [(_ n msg (f esp ...) para body ...)
     (let* ([n:str (symbol->string (syntax-e (syntax n)))]
            [callback (lambda (before after)
                        (string->symbol 
                         (string-append before n:str "-callback" after)))]
            [name (datum->syntax stx (callback "" ""))]
            [name0 (datum->syntax stx (callback "" "0"))]
            [set-name (datum->syntax stx (callback "set-" ""))])
       #`(define-values (#,name #,name0 #,set-name)
           (values 
            void void 
            (lambda (f esp ...)
              
              (when (callback-set? #,name) 
                (error (format "the ~a has already been specified") msg))
              (set! #,name0 f)
              (set! #,name (lambda para body ...))))))]))
(define (callback-stop! . n)
    (send the-time stop)
  (set! timer-callback void)
  (set! mouse-callback void)
  (set! key-callback void)
  (set! receive-callback void)
  (set! stop-when-callback (lambda () #f))
  (set! redraw-callback void)
  (set! *running?* #f))
(define (callback-set? cb) (not (eq? cb void)))
(define the-time (new timer% [notify-callback (lambda () (timer-callback))]))
(define-callback timer "tick-event hander" (f) ()
  (with-handlers ([exn:break? break-handler][exn? exn-handler])
    (update-world f)     (add-event TICK)
    (redraw-callback)))
(define-callback redraw "redraw function" (f) ()
  (with-handlers ([exn:break? break-handler][exn? exn-handler])
    (define result (f the-world))
    (define fname (object-name f))
    (define tname (if fname fname 'your-redraw-function))
    (if (image? result)
        (check-result tname scene? "scene" result
                      (format "image with pinhole at (~s,~s)"
                              (pinhole-x result) (pinhole-y result)))
        (check-result tname (lambda (x) (image? x)) "scene" result))
    (update-frame result)
        (when (stop-when-callback)
      (callback-stop! 'stop-when-callback))))
(define-callback stop-when "is end of world check" (f) ()
  (define result (f the-world))
  (define fname (object-name f))
  (define tname (if fname fname 'your-redraw-function))
  (check-result fname boolean? "boolean" result)
  result)
(define-callback key "key-event handler" (f evt-space) (e)
  (parameterize ([current-eventspace evt-space])
    (queue-callback 
     (lambda ()
       (with-handlers ([exn:break? break-handler][exn? exn-handler])
         (unless (update-world (lambda (w) (f w e)))
           (add-event KEY e)
           (redraw-callback)))))))
(define-callback mouse "mouse event handler" (f evt-space) (e)
  (parameterize ([current-eventspace evt-space])
    (queue-callback
     (lambda ()
       (define x (- (send e get-x) INSET))
       (define y (- (send e get-y) INSET))
       (define m (mouse-event->symbol e))
       (when (and (<= 0 x WIDTH) (<= 0 y HEIGHT))
         (with-handlers ([exn:break? break-handler][exn? exn-handler])
           (unless (update-world (lambda (w) (f w x y m)))
             (add-event MOUSE x y m)
             (redraw-callback))))))))
(define (mouse-event->symbol e)
  (cond [(send e button-down?) 'button-down]
        [(send e button-up?)   'button-up]
        [(send e dragging?)    'drag]
        [(send e moving?)      'move]
        [(send e entering?)    'enter]
        [(send e leaving?)     'leave]
        [else          (error 'on-mouse-event
                (format 
                 "Unknown event type: ~a"
                 (send e get-event-type)))]))
(define (exn-handler e)
  (callback-stop! 'exn-handler)
  (raise e))
(define (break-handler . _) 
  (printf "animation stopped")
  (callback-stop! 'break-handler)
  the-world)
(define (number->integer x)
  (inexact->exact (floor x)))
(define TRIES 3) (define PAUSE 2) 
(provide
  register   LOCALHOST  ) 
(define (register ip . sexp)
  (check-arg 'register (string? ip) "ip address" 'first ip)
  (check-arg 'register (or (null? sexp) (sexp? (car sexp))) "sexp"
             "second, optional" sexp)
  (apply register0 ip sexp))
(define (register0 ip . sexp)
    (define host 
    (if (string=? LOCALHOST ip)
        "server on local host"
        (format "server at ~a" ip)))
  (define (register n)
    (define (err b)
      (define FMT  "unable to register with ~a after ~s tries")
      (if b (error 'register FMT host TRIES) (error 'register FMT host TRIES)))
        (define (handler x)
      (unless (> n 0) (err #t))
      (sleep PAUSE)
      (register (- n 1)))
    (printf "trying to register with ~a ...\n" host)
    (with-handlers ((exn:fail:network? handler))
      (define-values (in out) (tcp-connect ip SQPORT))
      (tcp-send out `(REGISTER ,sexp))
      (let ([ackn (tcp-receive in)])
        (if (eq? ackn 'okay)
            (values in out)
            (err #f)))))
  (define (RECEIVE)
    (sync
     (wrap-evt 
      in 
      (lambda (in)
        (define msg (tcp-receive in))
        (unless (sexp? msg)
          (error 'rec  "can't happen: sexp expected from server, rec'd: ~e" msg))
        (receive-callback msg)
        (RECEIVE)))))
    (define-values (in out) (register TRIES))
  (printf "... successful registered and ready to receive\n")
  (set-server-out! out (thread RECEIVE))
  true)
(define-struct package (world message) #:transparent)
(define (create-package w m)
  (check-arg 'make-package (sexp? m) 'sexp "second" m)
  (make-package w m))
(provide
 (rename-out
  (create-package make-package)   )
 package-world
 package-message
 package?)
(define *out* #f)
(define *rec* #f)
(define (set-server-out! f th) 
  (set! *out* f)
  (set! *rec* th))
(define-callback receive "receive-event handler" (f) (m)
  (queue-callback 
   (lambda ()
     (with-handlers ([exn:break? break-handler][exn? exn-handler])
       (update-world (lambda (w) (f w m)))
       (add-event REC m)
       (redraw-callback)))))
(require htdp/error)
(define LIMIT  2) 
(define (make-initial-state-and-messages player1 player2) 
  (void))
(define (process-message s p m)
  (void))
(define (administrator-start players)
    (define (loop state)
    (define (player-wait-for-msg p)
      (wrap-evt (player-in p) (read-and-process-message state p loop)))
    (apply sync (map player-wait-for-msg players)))
    (define x (apply make-initial-state-and-messages players))
  (broadcast (rest x))
  (loop (first x)))
(define (read-and-process-message state@t p loop)
  (lambda (in)
    (define received  (tcp-receive in))
    (define response  (process-message state@t p received))
    (define state@t+1 (first response))
    (define send-out  (rest response))
    (show (list (format "received from ~a: ~a" (player-name p) received)))
    (broadcast send-out)
    (loop state@t+1)))
(define (broadcast lm)
  (for-each (lambda (p+m) (player-send (first p+m) (second p+m))) lm)
  (show
   (map (lambda (p+m) 
          (format "sent msg to ~a  : ~a" (player-name (first p+m)) (second p+m)))
        lm)))
(define show void)
(define-struct player (in out name info) #:transparent)
(define (create-player i o info) 
  (make-player i o (gensym 'player) info))
(define (player-send p sexp)
  (tcp-send (player-out p) sexp))
(require scheme/match)
(define gui:ch (make-channel))
(define dr:custodian (current-custodian))
(define the-custodian (make-custodian))
(define (listener enable-stop)
  (lambda ()
    (define tcp-listener 
      (with-handlers ((exn:fail:network? 
                       (lambda (x) (custodian-shutdown-all the-custodian))))
        (tcp-listen SQPORT 4 #t)))
    (define (loop players add-player)
      (sync (wrap-evt (tcp-accept-evt tcp-listener) (add-player players))
            (wrap-evt gui:ch (interpret-command players))))
    (define (add-no-players players) 
      (lambda (in-out)
        (define out (second in-out))        
        (tcp-send out 'no)
        (close-input-port (first in-out))
        (close-output-port out)
        (loop players add-no-players)))
    (define (add-player players)
      (lambda (in-out)
        (define in (first in-out))
        (define out (second in-out))        
        (define next (tcp-receive in))         (match next
          [(cons 'REGISTER info)
           (let* ([p (create-player in out info)]
                  [players (cons p players)])
             (players->announcement p)
             (player-send p 'okay)
             (if (>= player# LIMIT) (go players) (loop players add-player)))]
          [else (printf "erroneous connection: ~s\n" next) 
                (loop players add-player)])))
    (define player# 0)
    (define (players->announcement p)
      (set! player# (+ player# 1))
      (show `(,(format "~a signed up as player ~a" (player-name p) player#))))
    (define (go players)
      (enable-stop)
      (thread (lambda () (administrator-start players)))
      (loop 'dummy add-no-players))
    (define (interpret-command players)
      (lambda (c)
        (case c
          [(START) (go players)]
          [(STOP-RESTART) 
           (parameterize ([current-custodian dr:custodian])
             (show '("... done" "----------------------------------"))
             (thread 
              (lambda () 
                                (custodian-shutdown-all the-custodian)
                (run-server))))]
          [(STOP) (custodian-shutdown-all the-custodian)]
          [else (error 'command "got ~e" c)])))
        (loop '() add-player)))
(define (gui)
  (define frame
    (new (class frame%
           (super-new)
           (define/augment (on-close) 
             (send frame show #f)
             (channel-put gui:ch 'STOP)))
         [label "Server"][width 500][height 200][style '(metal)]))
  (define panel (new horizontal-panel% 
                     [parent frame]
                     [stretchable-height #f]
                     [alignment '(center center)]))
  (define (switch)
    (send stop enable #t)
    (send s&re enable #t))
  (define (stop* stop-l hide? en?)
    (new button% [parent panel] [label stop-l] [enabled en?]
         [callback (lambda (but evt)
                     (define msg (if hide? 'STOP 'STOP-RESTART))
                     (channel-put gui:ch msg)
                     (when hide? (send frame show #f)))]))
  (define stop (stop* "stop" #t #t))
  (define s&re (stop* "stop game & relaunch server" #f #f))
  (define (show lostr)
    (queue-callback 
     (lambda () 
       (send text lock #f)
       (for-each (lambda (str) (send text insert (format "~a\n" str))) lostr)
       (send text lock #t))))
  (define text (new text%))
    (new editor-canvas% [parent frame]
       [editor text] [style '(no-border combo no-hscroll auto-vscroll)])
  (send text lock #t)
  (send frame show #t)
  (values show switch (lambda () (send frame show #f))))
(define *universe-sema* (make-semaphore 1))
(define *universe-running?* #f)
(define (universe initial* process*)
    (semaphore-wait *universe-sema*)
  (when *universe-running?* 
    (semaphore-post *universe-sema*)
    (error 'universe "another universe is up and running"))
  (set! *universe-running?* #t)
  (semaphore-post *universe-sema*)
    (check-proc 'universe initial* 2 "first" "two arguments")
  (check-proc 'universe process* 3 "second" "two arguments")
    (let*-values ([(sh sw cl) (gui)])
    (set! show sh)
    (set! switch sw)
    (set! make-initial-state-and-messages 
          (check-res cl initial* (name initial* "\"initial\" function")))
    (set! process-message
          (check-res cl process* (name process* "\"process\" function")))
    (run-server)
    #t))
(define (check-res close f n)
  (define s (format "expected from ~a; given: " n))
  (define (stop msg)
    (printf "universe: ~a\n" msg)
    (printf "shutting down\n")
    (close)
    (custodian-shutdown-all the-custodian))
  (lambda x 
    (define r (apply f x))
    (cond
      [(cons? r) (let ([m (cdr r)])
                   (if (mails? m)
                       r
                       (stop (format "list of Mails ~a~e" s m))))]
      [else (stop (format "(cons ServerState Mail) ~a~e" s r))])))
(define (name o alt)
  (define initial-name (object-name o))
  (if initial-name initial-name alt))
(define (mails? m)
  (andmap (lambda (x) 
            (and (cons? x) (cons? (cdr x)) (player? (car x)) (sexp? (cdr x))))
          m))
(define switch void)
(define (run-server)
  (show '("starting a server ..."))
  (set! the-custodian (make-custodian))
  (parameterize ([current-custodian the-custodian])
    (thread (listener switch))))
(provide sexp?)
(provide
   player?   universe   )
(define (sexp? x)
  (cond
    [(empty? x) true]
    [(string? x) true]
    [(symbol? x) true]
    [(number? x) true]
    [(char? x) true]
    [(pair? x) (and (list? x) (andmap sexp? x))]
    [else false]))
(define (tcp-send out msg)
  (fprintf out "~s\n" msg)
  (flush-output out))
(define (tcp-receive in)
  (define x (read-line in))
  (cond
    [(eof-object? x)
     (printf "tcp-receive: connection closed prematurely")
     (exit)]
    [(string=? x "") (error 'tcp-receive "can't happen")]
    [else (read (open-input-string x))]))
(define SQPORT 4567)
(define LOCALHOST "127.0.0.1")