#lang racket
(require "board-size.rkt") ; for n

(provide x o none     ; cell values
         new-board    ; n => board
         ;  where n is the board size
         ;  the board is empty
         board-cell   ; board col row => cell-value
         ;   cols/rows are numbered from 0
         push         ; board side index value => board
         ;   where side is one of
         ;        'top 'bottom 'left 'right
         ;   index is a number in [0, n-1]
         ;   values is x or o
         rotate-cw    ; board turns => board
         ;   rotates 90 degrees x turns
         ;   rotation affects board-cell and
         ;    push, but not find-board-in-history
         
         new-history             ; => history
         ;  the history is empty
         find-board-in-history   ; board history => board or #f
         extend-history          ; board history => history
         extend-history!         ; board history => history
         ;   maybe mutates the input history
         )

(define x #\x)
(define o #\o)
(define none #\space)

(define-struct board (str n rotation) #:mutable)

(define (new-board n)
  (make-board (make-string (add1 (* n n)) #\space) n 0))

(define (dup b)
  (make-board (string-copy (board-str b)) (board-n b) (board-rotation b)))

(define (unrotate-indices board row col)
  (let ([n (current-board-size)])
    (case (board-rotation board)
      [(0) (values row col)]
      [(1) (values (- (sub1 n) col) row)]
      [(2) (values (- (sub1 n) row) (- (sub1 n) col))]
      [(3) (values col (- (sub1 n) row))])))

(define (board-cell board col row)
  (let-values ([(row col) (unrotate-indices board row col)]
               [(n) (current-board-size)])
    (string-ref (board-str board) (+ col (* row n)))))

(define (set-cell! board col row v)
  (let-values ([(row col) (unrotate-indices board row col)]
               [(n) (current-board-size)])
    (string-set! (board-str board) (+ col (* row n)) v)))

(define (xpush board c r inc-c inc-r piece)
  (let ([board (dup board)]
        [n (current-board-size)])
    (let loop ([c c][r r][old piece])
      (when (and (< -1 c n) (< -1 r n))
        (let ([v (board-cell board c r)])
          (set-cell! board c r old)
          (unless (eq? v none)
            (loop (inc-c c) (inc-r r) v)))))
    (string-set! (board-str board) (* n n) piece) ; last move indicator
    board))

(define identity (lambda (x) x))

(define push
  (lambda (board dir i piece)
    (let ([n (current-board-size)])
      (case dir
        [(left) (xpush board 0 i add1 identity piece)]
        [(right) (xpush board (sub1 n) i sub1 identity piece)]
        [(top) (xpush board i 0 identity add1 piece)]
        [(bottom) (xpush board i (sub1 n) identity sub1 piece)]
        [else (error 'push "bad directrion ~a" dir)]))))

(define (rotate-cw board amt)
  (let* ([b (dup board)]
         [r (modulo (+ (board-rotation board) amt) 4)]
         [r2 (if (negative? r)
                 (+ r 4)
                 r)])
    (set-board-rotation! b r2)
    b))


;; In board.c, history is implemented with hash tables and fast
;; compying. Here we just use an assoc list.

(define (new-history) 
  null)

(define (find-board-in-history board h) 
  (let ([v (assoc (string->symbol (board-str board)) h)])
    (and v (cdr v))))


(define (extend-history board h)
  (cons (cons (string->symbol (board-str board)) board) h))

(define extend-history! extend-history)
