;; ----------------------------------------------------------------------
;; Part C, question 1:
;; ----------------------------------------------------------------------

(define (make-freecell-card rank suit)
  (define (valid-rank? r) (and (> r 0) (< r 14)))
  
  (define (valid-suit? r)
    (or (eq? suit 's)
        (eq? suit 'h)
        (eq? suit 'd)
        (eq? suit 'c)))
  
  (define (valid-card?) 
    (and (valid-rank? rank) (valid-suit? suit)))
  
  (define (rank-to-print rank)
    (cond ((= rank 1)  " A")
          ((= rank 10) "10")
          ((= rank 11) " J")
          ((= rank 12) " Q")
          ((= rank 13) " K")
          ((or (< rank 1) (> rank 13)) (error "invalid rank!"))
          (else (string-append " " (number->string rank)))))

  (define (display-card)
    (display (rank-to-print rank))
    (display suit))
  
  ;; you can add other helper functions here if you like
  
  (if (not (valid-card?))
      (error "invalid card!")
      (lambda (op . args)
        (cond ((eq? op 'display) (display-card))
              ((eq? op 'rank) ...) ;; TODO
              ((eq? op 'suit) ...) ;; TODO
              ((eq? op 'color) ...) ;; TODO
              ((eq? op 'goes-below?) ...) ;; TODO
              ((eq? op 'next-in-suit?) ...) ;; TODO
              (else (error "unknown operation: " op))))))


;; ----------------------------------------------------------------------
;; Part C, question 2:
;; ----------------------------------------------------------------------

(define (make-deck)
  ;; Some helper functions provided for you:

  ;; Return a full deck of 52 cards: 1 to 13 (A to K) in four suits.
  (define (full-deck)
    ;; the clever way; brute-force would also work
    (apply append
           (map (lambda (rank) 
                  (map (lambda (suit) 
                         (make-freecell-card rank suit)) 
                       '(s h d c)))
                (list 1 2 3 4 5 6 7 8 9 10 11 12 13))))
  
  ;; Shuffle a list of objects randomly.
  (define (shuffle lst)
    (if (null? lst)
        lst
        ; take a random element from a list...
        (let ((first (list-ref lst (random (length lst)))))
          ; and cons it to the shuffled rest of the list.
          (cons first (shuffle (remq first lst))))))
  
  ;; Return the first n elements of a list, or as many as you can if there
  ;; are fewer than n elements.  n should be >= 0.
  (define (take n lst)
    (if (or (null? lst) (= n 0))
        (list)
        (cons (car lst) (take (- n 1) (cdr lst)))))
  
  ;; Return a list without the first n elements, or the empty list if there
  ;; are fewer than n elements.  n should be >= 0.
  (define (drop n lst)
    (if (or (null? lst) (= n 0))
        lst
        (drop (- n 1) (cdr lst))))
  
  (let ((cards (shuffle (full-deck)))
        (ncards-left 52))
    (lambda (op . args)
      (cond ((eq? op 'ncards) ...) ;; TODO
            ((eq? op 'reshuffle!) ...) ;; TODO
            ((eq? op 'get-cards!) ...) ;; TODO
            (else (error "unknown operation: " op))))))

;; ----------------------------------------------------------------------
;; Part C, question 3:
;; ----------------------------------------------------------------------

(define (make-freecell-game)
  ;; ------------------------------------------------------------
  ;; Helper functions which don't depend on the state variables.
  ;; ------------------------------------------------------------

  ;; Return a list of lists of cards, representing the columns on the 
  ;; FreeCell board.  The first four columns start off with 7 cards,
  ;; while the last four start off with 6 cards.
  (define (deal-columns)
    (let ((deck (make-deck)))
      (list (deck 'get-cards! 7)  ;; ok to do this since order of evaluation
            (deck 'get-cards! 7)  ;; is unimportant to final result
            (deck 'get-cards! 7)
            (deck 'get-cards! 7)
            (deck 'get-cards! 6)
            (deck 'get-cards! 6)
            (deck 'get-cards! 6)
            (deck 'get-cards! 6))))
  
  ;; Take a list, a non-negative integer index n, and a value and return
  ;; a new list with that value at the nth position in the list.
  (define (list-replace lst n val)
    (cond ((< n 0) (error "invalid index into list: " n))
          ((= n 0) (cons val (cdr lst)))
          (else
           (cons (car lst) (list-replace (cdr lst) (- n 1) val)))))
  
  ;; Take a list and a non-negative integer index n, and return a new list
  ;; which is the same as the old list except that the nth item in the old
  ;; list has been removed.
  (define (list-remove lst n)
    (cond ((< n 0) (error "invalid index into list: " n))
          ((= n 0) (cdr lst))
          (else
           (cons (car lst) (list-remove (cdr lst) (- n 1))))))
  
  ;; Return the last item in a list.
  (define (list-last lst)
    (cond ((null? lst) (error "no last element"))
          ((null? (cdr lst)) (car lst))
          (else (list-last (cdr lst)))))
  
  ;; Is the column number valid?  Only column numbers from 0 to 7 are valid.
  (define (valid-column-number? n)
    (or (>= n 0) (< n 8)))
  
  ;; Return an integer value corresponding to a given suit symbol.
  (define (suit-number suit)
    (cond ((eq? suit 's) 0)
          ((eq? suit 'h) 1)
          ((eq? suit 'd) 2)
          ((eq? suit 'c) 3)
          (else "unknown suit: " suit)))
  
  ;; ------------------------------------------------------------
  ;; State variables.
  ;; ------------------------------------------------------------

  (let ((columns     (deal-columns))
        (freecells   (list))
        (foundations (list (list) (list) (list) (list))))
    
    ;; ------------------------------------------------------------
    ;; Helper functions which depend on the state variables.
    ;; ------------------------------------------------------------

    ;; Re-initialize the state variables for a new game.
    (define (new-game!)
      (set! columns (deal-columns)) ;; list of lists of cards
      (set! freecells (list)) ;; empty list of cards
      (set! foundations (list (list) (list) (list) (list))) 
                    ;; four empty lists of cards, one per suit
      'done)
    
    ;; Display the game on the terminal.
    (define (display-game)
      ;; assume this has been written and works correctly
      )
    
    ;; Get the column (list of cards) corresponding to a particular column
    ;; number.
    (define (get-column column-number)
      (if (valid-column-number? column-number)
          (list-ref columns column-number)
          (error "invalid column number: " column-number)))
    
    ;; Change the column at a particular column number to a new list of
    ;; cards.
    (define (set-column! column-number col)
      (if (valid-column-number? column-number)
          (set! columns (list-replace columns column-number col))
          (error "invalid column number: " column-number)))
    
    ;; Return the last card in a column, or 'none if the column is empty.
    ;; We represent the last card of a column (i.e. the end of the column)
    ;; as the first item in the list.
    (define (last-in-column column-number)
      (let ((old-col (get-column column-number)))
        (if (null? old-col)
            'none
            (car (get-column column-number)))))
  
    ;; Add a card to the end of a column.  No error checking.
    (define (add-to-column! column-number card)
      (set-column! column-number (cons card (get-column column-number))))
    
    ;; Remove the last card in a column.  
    (define (remove-last-in-column! column-number)
      (let ((old-col (get-column column-number)))
        (if (null? old-col)
            (error "no cards in column: " column-number)
            (set-column! column-number (cdr old-col)))))
  

    ;; Add a card to the list of cards on free cells. 
    ;; No error checking.    
    (define (add-to-freecells! card)
      (set! freecells (cons card freecells)))
    
    ;; Get the card at the nth position in the freecell list.
    (define (get-freecell n)
      (if (or (< n 0) (>= n (length freecells)))
          (error "invalid index or no card at index: " n)
          (list-ref freecells n)))
    
    ;; Remove the card at the nth position in the freecell list.
    (define (remove-freecell! n)
      (if (or (< n 0) (>= n (length freecells)))
          (error "invalid index or no card at index: " n)
      (set! freecells (list-remove freecells n))))
    
    
    ;; Get the list of cards in the foundation corresponding to a particular suit.
    (define (get-foundation-suit suit)
      (list-ref foundations (suit-number suit)))
    
    ;; Get the last card in a particular suit on the foundation.
    (define (last-in-foundation suit)
      (let ((sf (get-foundation-suit suit)))
        (if (null? sf)
            'none
            (list-last sf))))

    ;; Add a card to the foundation suit for a particular suit.
    (define (add-to-foundation-suit! suit card)
      (let ((sf (get-foundation-suit suit))
            (sn (suit-number suit)))
        (if (null? sf)
            (set! foundations (list-replace foundations sn (list card)))
            (set! foundations (list-replace foundations sn (append sf (list card)))))))

    ;; ------------------------------------------------------------
    ;; Message-passing object.
    ;; ------------------------------------------------------------

    (lambda (op . args)
      (cond ((eq? op 'display)  ;; display the game
             (display-game))
             
            ((eq? op 'new-game) (new-game!))  ;; initialize a new game
            
            ;; Move a card from one column on the board to another column.
            ;; The columns are identified by numeric indices.
            ;; Signal an error if the move is invalid.
            ((eq? op 'move-col->col)
             (let ((col-number1 (car args))
                   (col-number2 (cadr args)))
               ;; TODO; can be done in less than 10 lines of code
               ...))

            ;; Move a card from a column on the board (indicated by the numeric
            ;; index of the column) to the freecell list.
            ;; Signal an error if there are no more freecells (i.e. the list
            ;; is full).
            ((eq? op 'move-col->free)
             (let ((col-number (car args)))
               ;; TODO; can be done in less than 10 lines of code
               ...))
            
            ;; Move a card from a column on the board (indicated by the numeric
            ;; index of the column) to the foundation.
            ;; Signal an error if the move is invalid.
            ((eq? op 'move-col->foundation)
             (let ((col-number (car args)))
               ;; TODO; can be done in less than 20 lines of code
               ...))
                   
            ;; Move a card from a freecell to a column.  The freecell and the
            ;; column are indicated by numeric indices.
            ;; Signal an error if the move is invalid.
            ((eq? op 'move-free->col)
             (let ((free-number (car args))
                   (col-number (cadr args)))
               ;; TODO; can be done in less than 10 lines of code
               ...))
            
            ;; Move a card from a freecell to a column.  The freecell is
            ;; indicated by a numeric index.
            ;; Signal an error if the move is invalid.
            ((eq? op 'move-free->foundation)
             (let ((free-number (car args)))
               ;; TODO; can be done in less than 20 lines of code
               ...))
            
            (else (error "unknown operation: " op))))))


