(module same mzscheme
  (require mzlib/etc
           mzlib/class
           mzlib/unit
           mred
           mzlib/list
           "../show-scribbling.ss")
  
  (provide game@)
  
  (define game@
    (unit
      (import)
      (export)
      
      (define board-width 20)
      (define board-height 10)
      (define cell-size 30)
      (define colors (map (lambda (x) (make-object color% x)) (list "blue" "red" "magenta" "yellow" "cyan")))
      (define pens (map (lambda (x) (make-object pen% x 1 'solid)) colors))
      (define brushes (map (lambda (x) (make-object brush% x 'solid)) colors))
      (define white-pen (make-object pen% "white" 1 'solid))
      (define white-brush (make-object brush% "white" 'solid))
      
      ;; build-board : (-> (vectorof (vectorof (vector (union num #f) boolean))))
      ;   this represents the board. Each entry is the color index of
      ;   the piece and a node to mark for the depth-first traversal.
      ;   #f for the color index indicates an eliminated piece.
      (define (build-board)
        (build-vector
         board-width
         (lambda (i)
           (build-vector
            board-height
            (lambda (j)
              (vector
               (begin
                 (if (= j (- board-height 1))
                     (- (length colors) 1)
                     (modulo i (- (length colors) 1)))
                 (random (length colors)))
               #f))))))
      
      (define board (build-board))
      
      (define game-over? #f)
      
      (define score 0)
      (define (calc-score n)
        (cond
          [(= n 2) 2]
          [else (- (* (- n 1) (- n 1)) (- n 3))]))
      
      (define same-canvas%
        (class canvas%
          (inherit get-dc get-client-size)
          (define/private (get-width) (let-values ([(w h) (get-client-size)]) w))
          (define/private (get-height) (let-values ([(w h) (get-client-size)]) h))
          (define/private (get-x-step) (/ (get-width) board-width))
          (define/private (get-y-step) (/ (get-height) board-height))
          [define/public draw-cell
            (lambda (dc highlight? i j)
              (let ([index (vector-ref (vector-ref (vector-ref board i) j) 0)]
                    [x (* i (get-x-step))]
                    [y (* j (get-y-step))])
		(send dc set-brush white-brush)
		(send dc set-pen white-pen)
		(send dc draw-rectangle x y (get-x-step) (get-y-step))
		(when index
		  (send dc set-brush (list-ref brushes index))
		  (send dc set-pen (list-ref pens index))
		  (cond
		   [highlight?
		    (send dc draw-ellipse 
			  (floor (+ x (/ (get-x-step) 4)))
			  (floor (+ y (/ (get-y-step) 4)))
			  (floor (/ (get-x-step) 2))
			  (floor (/ (get-y-step) 2)))]
		   [else
		    (send dc draw-ellipse x y (get-x-step) (get-y-step))]))))]
          
          [define/public  draw-line
            (lambda (dc i)
              (let ([show-turned? (> (length turned) 1)])
                (let loop ([j board-height])
                  (cond
                    [(zero? j) (void)]
                    [else
                     (draw-cell dc (and show-turned? (member (list i (- j 1)) turned)) i (- j 1))
                     (loop (- j 1))]))))]
          
          [define/public find-same-colors
            (lambda (i j)
              (let* ([index (vector-ref (vector-ref (vector-ref board i) j) 0)]
                     [ans
                      (let loop ([i i]
                                 [j j]
                                 [ps null])
                        (cond
                          [(not (and (<= 0 i) (< i board-width)
                                     (<= 0 j) (< j board-height)))
                           ps]
                          [(vector-ref (vector-ref (vector-ref board i) j) 1) ps]
                          [(not (vector-ref (vector-ref (vector-ref board i) j) 0)) ps]
                          [(= index (vector-ref (vector-ref (vector-ref board i) j) 0))
                           (let ([v (vector-ref (vector-ref board i) j)])
                             (vector-set! v 1 #t)
                             (loop (+ i 1)
                                   j
                                   (loop (- i 1)
                                         j
                                         (loop i
                                               (- j 1)
                                               (loop i
                                                     (+ j 1)
                                                     (cons (list v i j) ps))))))]
                          [else ps]))])
                (for-each (lambda (p) (vector-set! (first p) 1 #f)) ans)
                ans))]
          
          [define/public get-game-over-size
            (lambda (dc)
              (let ([border 5])
                (let-values ([(text-width text-height d l) (send dc get-text-extent game-over)])
                  (let ([x (- (/ (get-width) 2) (/ text-width 2))]
                        [y (- (/ (get-height) 2) (/ text-height 2))])
                    (values x y text-width text-height border)))))]
          
          [define/public paint-game-over
            (lambda (dc)
              (send dc set-font font)
              (let-values ([(x y text-width text-height border) (get-game-over-size dc)])
                (send dc set-pen white-pen)
                (send dc set-brush white-brush)
                (send dc draw-rectangle
                      (- x border) (- y border)
                      (+ text-width border border)
                      (+ text-height border border))
                (send dc draw-text game-over x y)))]
          
          
          (field
           [game-over "Game Over"]
           [font (make-object font% 24 'decorative 'normal 'normal #f)]
           [turned null])
          
          [define/private recalc/draw-turned
            (lambda (i j)
              (set! turned (map (lambda (xx) (list (second xx) (third xx))) (find-same-colors i j)))
              (cond
                [(> (length turned) 1)
                 (send this-message set-label (number->string (calc-score (length turned))))
                 (for-each (lambda (p) (draw-cell (get-dc) #t (first p) (second p))) turned)]
                [else
                 (send this-message set-label "")]))]
          
          [define/override on-event
            (lambda (evt)
              (let+ ([val x (send evt get-x)]
                     [val y (send evt get-y)]
                     [val i (inexact->exact (floor (* (/ x (get-width)) board-width)))]
                     [val j (inexact->exact (floor (* (/ y (get-height)) board-height)))])
                    (cond
                      [(or (send evt moving?)
                           (send evt entering?)
                           (send evt leaving?))
                       (cond
                         [(and (<= 0 i) (< i board-width)
                               (<= 0 j) (< j board-height))
                          (unless (member (list i j) turned)
                            (when (> (length turned) 1)
                              (for-each (lambda (p) (draw-cell (get-dc) #f (first p) (second p))) turned))
                            (recalc/draw-turned i j))]
                         [else
                          (when (> (length turned) 1)
                            (for-each (lambda (p) (draw-cell (get-dc) #f (first p) (second p))) turned))
                          (set! turned null)
                          (send this-message set-label "")])]
                      [(send evt button-up?)
                       (when (and (<= 0 i) (< i board-width)
                                  (<= 0 j) (< j board-height))
                         (when (> (length turned) 1)
                           (for-each (lambda (p) (draw-cell (get-dc) #f (first p) (second p))) turned))
                         (set! turned null)
                         (send this-message set-label "")
                         (let ([same-colors (find-same-colors i j)])
                           
                           ;; reset back the marks for the next depth-first traversal
                           
                           (when (>= (length same-colors) 2)
                             
                             ;; update score
                             (set! score (+ score (calc-score (length same-colors))))
                             (send message set-label (number->string score)) 
                             
                             ;; slide down empty pieces
                             (let ([is null])
                               (for-each
                                (lambda (p)
                                  (let ([i (second p)]
                                        [j (third p)])
                                    (unless (member i is)
                                      (set! is (cons i is)))
                                    (let loop ([x j])
                                      (cond
                                        [(<= 1 x)
                                         (let ([next (vector-ref (vector-ref board i) (- x 1))]
                                               [this (vector-ref (vector-ref board i) x)])
                                           (vector-set! this 0 (vector-ref next 0))
                                           (loop (- x 1)))]
                                        [else
                                         (vector-set! (vector-ref (vector-ref board i) x) 0 #f)]))))
                                (sort same-colors
                                      (lambda (x y) (<= (third x) (third y)))))

                               ;; slide empty over empty rows
                               (set! is (sort is >))
                               (let ([empty-is (filter (lambda (i)
                                                         (not (vector-ref (vector-ref (vector-ref board i) (- board-height 1)) 0)))
                                                       is)])
                                 (let ([is (if (null? empty-is)
                                               is
                                               (filter (lambda (x) (< x (car empty-is)))
                                                       is))])
                                   (for-each (lambda (empty-i)
                                               (let loop ([i empty-i])
                                                 (cond
                                                   [(<= i (- board-width 2))
                                                    (vector-set! board i (vector-ref board (+ i 1)))
                                                    (loop (+ i 1))]
                                                   [(= i (- board-width 1))
                                                    (vector-set! board i (build-vector board-height
                                                                                       (lambda (i) (vector #f #f))))])))
                                             empty-is)
                                   
                                   ;; draw changed lines
                                   (for-each (lambda (i) (draw-line (get-dc) i)) is)
                                   (unless (null? empty-is)
                                     (let loop ([i (car (last-pair empty-is))])
                                       (cond
                                         [(= i board-width) (void)]
                                         [else (draw-line (get-dc) i)
                                               (loop (+ i 1))])))
                                   
                                   ;; update `small' balls
                                   (recalc/draw-turned i j)
                                   )))
                             
                             (set! game-over?
                                   (not
                                    (let loop ([i board-width]
                                               [continue? #f])
                                      (cond
                                        [(zero? i) continue?]
                                        [else
                                         (or continue?
                                             (loop
                                              (sub1 i)
                                              (let loop ([j board-height]
                                                         [continue? continue?])
                                                (cond
                                                  [(zero? j) continue?]
                                                  [else
                                                   (or continue?
                                                       (loop
                                                        (sub1 j)
                                                        (> (length (find-same-colors (sub1 i) (sub1 j))) 1)))]))))])))) 
                             (when game-over?
                               (paint-game-over (get-dc))))))]
                      
                      [else (void)])))]
          
          [define/override on-paint
            (lambda ()
              (let ([dc (get-dc)])
                (send dc set-pen white-pen)
                (send dc set-brush white-brush)
                (let loop ([i board-width])
                  (cond
                    [(zero? i) (void)]
                    [else (draw-line dc (- i 1))
                          (loop (- i 1))]))
                (when game-over?
                  (paint-game-over dc))))]
          
          (super-new)

	  (send (get-dc) set-smoothing 'aligned)))
      
      (define semaphore (make-semaphore 0))
      (define same-frame%
        (class frame%
          [define/augment on-close
            (lambda ()
	      (semaphore-post semaphore)
              (inner (void) on-close))]
          (super-new [style '(metal)])))
      
      (define find-largest-connected-region
        (let ([biggest-so-far 0]
              [tests 0])
          (lambda ()
            (let ([answer 0])
              (let loop ([i 20])
                (cond
                  [(zero? i) (void)]
                  [else
                   (let loop ([j 10])
                     (cond
                       [(zero? j) (void)]
                       [else (set! answer
                                   (max
                                    answer
                                    (length
                                     (send canvas find-same-colors
                                           (- i 1) (- j 1)))))
                             (loop (- j 1))]))
                   (loop (- i 1))]))
              (set! biggest-so-far (max biggest-so-far (calc-score answer)))
              (set! tests (+ tests 1))
              (printf "tests: ~a sofar: ~a largest connected region: ~a score ~a\n"
                      tests
                      biggest-so-far
                      answer
                      (calc-score answer))))))
      
      (define (new-game-callback redraw?)
        (set! game-over? #f)
        (set! board (build-board))
        (unless (= score 0)
          (set! score 0)
          (send message set-label "0"))
        (send this-message set-label "")
        (when redraw?
          (send canvas on-paint)))
      
      (define frame (make-object same-frame% "Same"))
      (define panel (make-object vertical-panel% frame))
      (define canvas (make-object same-canvas% panel))
      (define hp (make-object horizontal-panel% panel))
      (make-object message% "Total Score: " hp)
      (define message (make-object message% "0" hp))
      (make-object message% "This Score: " hp)
      (define this-message (make-object message% "0" hp))
      (define button (make-object button% "New Game" hp (lambda x (new-game-callback #t))))
      '(make-object button% "Run Scores" hp (lambda x
                                              (let loop ()
                                                (new-game-callback #f)
                                                (find-largest-connected-region)
                                                (loop))))
      
      (define help-button (make-object button% "Help"
                            hp
                            (let ([show-help
                                   (show-scribbling
                                    '(lib "games/scribblings/games.scrbl")
                                    "same")])
                              (lambda (_1 _2)
                                (show-help)))))
      
      (send message stretchable-width #t)
      (send this-message stretchable-width #t)
      (send hp stretchable-height #f)
      (send canvas min-width (* board-width cell-size))
      (send canvas min-height (* board-height cell-size))
      
      (send frame show #t)
      (yield semaphore))))
    
