#lang racket/base

; Small library to draw and output matricial images.
(require
  "color.rkt"
  racket/class
  (prefix-in draw: racket/draw))

(provide
  make-pen
  make-brush
  make-image
  bounding-box
  make-sprite-bank
  image-width
  image-height
  image-draw-rectangle!
  image-draw-square!
  image-draw-line!
  image-draw-ellipse!
  image-draw-circle!
  image-draw-image!
  image-draw-sprite!
  image-save)

; For drawing lines, a pen may be used
; Possible values for style: 'transparent 'solid 'dot 'long-dash 'short-dash 'dot-dash
(define (make-pen col [width 1] [style 'solid])
  (draw:make-pen
    #:color (draw-value col)
    #:width width
    #:style style))

; For filling shapes, a brush may be used
; Possible values for style: 'transparent 'solid 'bdiagonal-hatch 'crossdiag-hatch 'fdiagonal-hatch 'cross-hatch 'horizontal-hatch 'vertical-hatch
(define (make-brush col [style 'solid])
  (draw:make-brush
    #:color (draw-value col)
    #:style style))

; Image constructor
; Internally, an image is represented as a drawing context (bitmap-dc%) from racket/draw
(define (make-image width height)
  (define bt (draw:make-bitmap width height))
  (new draw:bitmap-dc% [bitmap bt]))

; Bounding box
; Used for defining sprites
(struct bounding-box
  (x
   y
   width
   height))

; Sprite bank
; A sprite bank is a collection of sprites loaded from the same file
; Each sprite is named and is defined from a bounding box
(struct sprite-bank
  (bitmap
   sprites))
; boxes is a list of (sprite-name x y width height)
(define (make-sprite-bank filepath boxes)
  (sprite-bank
    (draw:read-bitmap filepath)
    (make-immutable-hash
      (map
        (lambda (x)
          (cons
            (car x)
            (apply bounding-box (cdr x))))
        boxes))))

; Operations on images
; Width
(define (image-width img)
  (send (send img get-bitmap) get-width))
; Height
(define (image-height img)
  (send (send img get-bitmap) get-height))

; Draw a rectangle
(define (image-draw-rectangle! img x y width height [brush #f] [pen #f])
  (if pen
      (send img set-pen pen)
      (send img set-pen "white" 1 'transparent))
  (if brush
      (send img set-brush brush)
      (send img set-brush "white" 'transparent))
  (send img draw-rectangle
    x y
    width height))

; Draw a square
(define (image-draw-square! img x y width [brush #f] [pen #f])
  (image-draw-rectangle! img x y width width brush pen))

; Draw a line
(define (image-draw-line! img x1 y1 x2 y2 [pen #f])
  (if pen
      (send img set-pen pen)
      (send img set-pen "white" 1 'transparent))
  (send img set-brush "white" 'transparent)
  (send img draw-line x1 y1 x2 y2))

; Draw an ellipse
(define (image-draw-ellipse! img x y width height [brush #f] [pen #f])
  (if pen
      (send img set-pen pen)
      (send img set-pen "white" 1 'transparent))
  (if brush
      (send img set-brush brush)
      (send img set-brush "white" 'transparent))
  (send img draw-ellipse
    x y
    width height))

; Draw a circle, from center and radius
(define (image-draw-circle! img x y radius [brush #f] [pen #f])
  (image-draw-ellipse! img
    (- x radius) (- y radius)
    (* 2 radius) (* 2 radius)
    brush pen))

; Draw an image on an image
(define (image-draw-image! img src x y)
  (send img draw-bitmap (send src get-bitmap) x y))

; Draw a sprite on an image, from the bank and name of the sprite
(define (image-draw-sprite! img x y spt-bank spt-name)
  (define box (hash-ref (sprite-bank-sprites spt-bank) spt-name))
  (send img draw-bitmap-section
    (sprite-bank-bitmap spt-bank)
    x y
    (bounding-box-x box) (bounding-box-y box)
    (bounding-box-width box) (bounding-box-height box)))

; Save to a file or output stream
(define (image-save img output [format 'png])
  (send
    (send img get-bitmap)
    save-file
    output
    format))
