tiles.ss
; Modified 1/19/2005 to be compatible with new image.ss contracts.
; Modified 2/16/2005 to include stuff from world.ss as well as image.ss
; Modified 2/17/2005 to provide on-update-event (which requires overriding a few
; functions from world.ss)
; Modified 5/20/2005 to rename on-update-event as on-redraw-event, and
; handle pinholes more consistently in image-beside and image-above.
; Modified 1/26/2006 to remove the functions I was replacing in image.ss
; (since image.ss now does things the way I wanted) and
; to remove my tweaked copy of world.ss (since world.ss now does things the
; way I wanted).
; Modified 7/12/2006 to allow image-beside and image-above to take variable numbers of arguments.
; Modified 7/26/2006 to add image-beside-align-top, image-beside-align-bottom, image-above-align-left, and image-above-align-right.

(module tiles mzscheme
  (require
;   (lib "etc.ss")
;   (lib "class.ss")
   (lib "list.ss") ; foldl
;   (lib "mred.ss" "mred")
;   (lib "error.ss" "htdp")
;   (prefix beg: (lib "htdp-beginner.ss" "lang"))
;   (lib "prim.ss" "lang")
;   (all-except (lib "image.ss" "htdp") rectangle circle ellipse triangle)
;   (rename (lib "image.ss" "htdp") rectangle^ rectangle)
;   (rename (lib "image.ss" "htdp") circle^ circle)
;   (rename (lib "image.ss" "htdp") ellipse^ ellipse)
;   (rename (lib "image.ss" "htdp") triangle^ triangle)
   (lib "image.ss" "htdp")   ; overlay/xy, etc.
;   (lib "error.ss" "htdp")
   )
  (provide image-above
           image-above-align-right
           image-above-align-left
           image-beside
           image-beside-align-top
           image-beside-align-bottom
           reflect-vert
           reflect-horiz
           reflect-main-diag
           reflect-other-diag
           rotate-cw
           rotate-ccw
           rotate-180
;           rectangle
;           circle
;           ellipse
;           triangle
           show-pinhole
           )

  

  
  ; Added functions to image.ss
  
  ; image-translate : image int int => image
  (define (image-translate image dx dy)
    (overlay/xy (rectangle (+ (image-width image) dx)
                           (+ (image-height image) dy)
                           'solid
                           'white)
                dx
                dy 
                image))
  
  
  ; pinhole-to-bottom : image -> number
  (define (pinhole-to-bottom img)
    (- (image-height img) (pinhole-y img)))
  ; pinhole-to-top : image -> number
  (define (pinhole-to-top img)
    (pinhole-y img))
  ; pinhole-to-left : image -> number
  (define (pinhole-to-left img)
    (pinhole-x  img))
  ; pinhole-to-right : image -> number
  (define (pinhole-to-right img)
    (- (image-width img) (pinhole-x img)))
  
; concat-vert : image image => image
  (define (concat-vert under over)
    (let [[dy (+ (pinhole-to-bottom over) (pinhole-to-top under))]]
      (move-pinhole
       (overlay/xy over 0 (round (+ (pinhole-to-bottom over) (pinhole-to-top under))) under)
       0 (round (/ dy 2)))))

; image-above : image image ... -> image
  (define (image-above . images)
    (cond [(null? images) (error 'image-above "Expected two or more images; given 0")]
          [(null? (cdr images)) (error 'image-above "Expected two or more images; given 1")]
          [else (foldl concat-vert (car images) (cdr images))]))

; image-above-align-right : image image ... -> image
  (define (image-above-align-right . images)
    (apply image-above
           (map (lambda (img)
                  (move-pinhole img (pinhole-to-right img) 0))
                images)))
  
; image-above-align-left : image image ... -> image
  (define (image-above-align-left . images)
    (apply image-above
           (map (lambda (img)
                  (move-pinhole img (- (pinhole-to-left img)) 0))
                images)))
  
  
  ; concat-horiz : image image => image
  (define (concat-horiz right left)
    (let [[dx (+ (pinhole-to-right left) (pinhole-to-left right))]]
      (move-pinhole
       (overlay/xy left dx 0 right)
       (round (/ dx 2)) 0)))

; image-beside : image image ... => image
  (define  (image-beside . images)
    (cond [(null? images) (error 'image-beside "Expected two or more images; given 0")]
          [(null? (cdr images)) (error 'image-beside "Expected two or more images; given 1")]
          [else (foldl concat-horiz (car images) (cdr images))]))
; image-beside-align-top : image image ... -> image
  (define (image-beside-align-top . images)
    (apply image-beside
           (map (lambda (img)
                  (move-pinhole img 0 (- (pinhole-to-top img))))
                images)))
  
; image-beside-align-bottom : image image ... -> image
  (define (image-beside-align-bottom . images)
    (apply image-beside
           (map (lambda (img)
                  (move-pinhole img 0 (pinhole-to-bottom img)))
                images)))

  ; show-pinhole : image -> image
  ; assumes the image is not solid black
  (define (show-pinhole img)
    (overlay img (circle 2 "solid" "black")))
;  "Examples of show-pinhole:"
;  (show-pinhole (rectangle 50 30 "solid" "blue"))
;  "should be a 50x30 blue rectangle with a black dot in the center"
  ; first-n : list n => list of length n (or less)
  (define (first-n L n)
    (cond [(null? L) ()]
          [(<= n 0) ()]
          [else (cons (car L)
                      (first-n (cdr L) (- n 1)))]))
  ;"Examples of first-n:"
  ;(first-n empty 2) "should be" empty
  ;(first-n (list 'a) 2) "should be" (list 'a)
  ;(first-n (list 'a 'b 'c) 2) "should be" (list 'a 'b)
  
  ; rest-n : list n => list of length n smaller (or 0)
  (define (rest-n L n)
    (cond [(null? L) ()]
          [(<= n 0) L]
          [else (rest-n (cdr L) (- n 1))]))
  ;"Examples of rest-n:"
  ;(rest-n empty 2) "should be" empty
  ;(rest-n (list 'a) 2) "should be" empty
  ;(rest-n (list 'a 'b 'c) 2) "should be" (list 'c)
  
  ; slice : list n => list-of-lists
  ; Assumes n divides the length of the list
  (define (slice L width)
    (cond [(null? L) ()]
          [else (cons (first-n L width)
                      (slice (rest-n L width) width))]))
  
  ;"Examples of slice:"
  ;(slice (list 1 2 3 4 5 6) 2) "should be" (list (list 1 2) (list 3 4) (list 5 6))
  
  ; unslice : list-of-lists => list
  (define (unslice lists)
    (apply append lists))
  ;"Example of unslice:"
  ;(unslice (list (list 1 2) (list 3 4) (list 5))) "should be" (list 1 2 3 4 5)
  
  ; reflect-horiz : image => image
  (define (reflect-horiz picture)
    (color-list->image
     (unslice
      (map reverse
           (slice (image->color-list picture)
                  (image-width picture))))
     (image-width picture)
     (image-height picture)
     (pinhole-to-right picture)
     (pinhole-to-top picture)))
  
  ; reflect-vert : image => image
  (define (reflect-vert picture)
    (color-list->image
     (unslice
      (reverse 
       (slice 
        (image->color-list picture)
        (image-width picture))))
     (image-width picture)
     (image-height picture)
     (pinhole-to-left picture)
     (pinhole-to-bottom picture)))
  
  ; ncons-each : list => list-of-one-element-lists
  (define (ncons-each L)
    (map list L))
  ;"Examples of ncons-each:"
  ;(ncons-each empty) "should be" empty
  ;(ncons-each (list 'a)) "should be" (list (list 'a))
  ;(ncons-each (list 'a 'b)) "should be" (list (list 'a) (list 'b))
  
  ; transpose : list-of-lists => list-of-lists
  ; Assumes all lists are the same length
  ; Assumes there's at least one row and at least one column
  (define (transpose rows)
    (apply map (cons list rows)))
  ;"Exampls of transpose:"
  ;(transpose (list (list 'a))) "should be" (list (list 'a))
  ;(transpose (list (list 'a 'b))) "should be" (list (list 'a) (list 'b))
  ;(transpose (list (list 'a) (list 'b))) "should be" (list (list 'a 'b))
  ;(transpose (list (list 'a 'b) (list 'c 'd))) "should be" (list (list 'a 'c) (list 'b 'd))
  
  ; reflect-main-diag : image => image
  (define (reflect-main-diag picture)
    (color-list->image
     (unslice
      (transpose
       (slice
        (image->color-list picture)
        (image-width picture))))
     (image-height picture)
     (image-width picture)
     (pinhole-y picture)
     (pinhole-x picture)))
  
  ; reflect-other-diag : image => image
  (define (reflect-other-diag picture)
    ;  (reflect-vert
    ;   (reflect-main-diag
    ;    (reflect-vert picture))))
    (color-list->image
     (unslice
      (reverse
       (transpose
        (reverse
         (slice
          (image->color-list picture)
          (image-width picture))))))
     (image-height picture)
     (image-width picture)
     (pinhole-to-bottom picture)
     (pinhole-to-right picture)))
  
  ; The following should probably be rewritten to rotate around the pinhole....
  ; rotate-cw : image => image
  (define (rotate-cw picture)
    ;  (reflect-main-diag (reflect-vert picture)))
    (color-list->image
     (unslice
      (transpose
       (reverse
        (slice
         (image->color-list picture)
         (image-width picture)))))
     (image-height picture)
     (image-width picture)
     (pinhole-to-bottom picture)
     (pinhole-to-left picture)))
  
  ; rotate-ccw : image => image
  (define (rotate-ccw picture)
    ;  (reflect-vert (reflect-main-diag picture)))
    (color-list->image
     (unslice
      (reverse
       (transpose
        (slice
         (image->color-list picture)
         (image-width picture)))))
     (image-height picture)
     (image-width picture)
     (pinhole-to-top picture)
     (pinhole-to-right picture)))
  
  ; rotate-180 : image => image
  (define (rotate-180 picture)
    ; (rotate-cw (rotate-cw picture))
    ; (reflect-vert (reflect-horiz picture))
    (color-list->image
     (unslice
      (reverse
       (map reverse
            (slice (image->color-list picture)
                   (image-width picture)))))
     (image-width picture)
     (image-height picture)
     (pinhole-to-right picture)
     (pinhole-to-bottom picture)))
  
  
  )