#lang racket
(require gigls/unsafe)

;Authors:
;Medha Gopalaswamy
;Zachary Segall

;==============================================================================
;-----------------------------------------------------------------------------
;                              HELPER PROCEDURES
;-----------------------------------------------------------------------------
;==============================================================================
;;; Procedure:
;;;   delta...
;;; Parameters:
;;;   n, a positive integer
;;;   start, a number
;;;   end, a number
;;; Purpose:
;;;   finds the rate of change between two numbers in a certain
;;;     amount of steps
;;; Produces:
;;;   delta, a number
;;; Preconditions:
;;;   [no additional]
;;;   Preconditions are not verified
;;; Postconditions:
;;;   delta is (/ (- end start) n).
(define delta...
  (lambda (n start end)
    (/ (- end start) n)))

;diagonal-shift from
; Rae Kuhlman and Zachary Segall
; Assignment 3: Drawing Generally and Concisely
; Saturday, September 13, 2014

;;; Procedure:
;;;   diagonal-shift
;;; Parameters:
;;;   vertical, a real number
;;;   horizontal, a real number
;;;   drawing, a drawing
;;; Purpose:
;;;   Shifts a drawing vertically and horizontally at the same time.
;;; Produces:
;;;   shifted-drawing, a drawing
;;; Preconditions:
;;;   The function combines vshift and hshift into one function.
;;;   The same restrictions that apply to vshift and hshift apply
;;;     to diagonal-shift.
;;;   Preconditions are not verified
;;; Postconditions:
;;;   shifted-drawing is shifted vertical units vertically
;;;      and horizontal units horizontally from drawing.
;;;   shifted-drawing is otherwise identical to drawing.
(define diagonal-shift
  (lambda (horizontal vertical drawing)
    (hshift-drawing horizontal
                    (vshift-drawing vertical drawing))))

;;; Procedure:
;;;   number-blend
;;; Parameters:
;;;   initial-number, a number
;;;   final-number, a number
;;;   total-distance, a positive number
;;;   distance-traveled, a positive number
;;; Purpose:
;;;   Creates a new number between initial-number and final-number
;;; Produces:
;;;   blended-number, a number
;;; Preconditions:
;;;   [No additional]
;;;   Preconditions are not verified
;;; Postconditions:
;;;   blended-number is dependent on the distance-travelled in relation
;;;     to the total-distance.
;;;   i.e., blended-number =
;;;     (+ initital-number
;;;        (* (/ distance-traveled total-distance)
;;;           (- final-number initial-number)
(define number-blend...
  (lambda (initial-number final-number total-distance distance-traveled)
    (+ initial-number
       (* (/ distance-traveled total-distance)
          (- final-number initial-number)))))

;Citation:
;Davis, J., Kluber, M., Rebelsky, S., and Weinman, J.(2014)
;Reading Checks:  Building Images by Iterating Over Positions. CSC-151-02/2014F.
;http://www.cs.grinnell.edu/~weinman/courses/CSC151/2014F/readings/iterate-positions-reading.html
;Accessed: 11/12/14

;;; Procedure:
;;;   euclidean-distance
;;; Parameters:
;;;   col1, a real number
;;;   row1, a real number
;;;   col2, a real number
;;;   row2, a real number
;;; Purpose:
;;;   Computes the euclidean distance between (col1,row1) and
;;;   (col2,row).
;;; Produces:
;;;   distance, a real number
(define euclidean-distance
  (lambda (col1 row1 col2 row2)
    (sqrt (+ (square (- col2 col1)) (square (- row2 row1))))))

;;; Procedure:
;;;   calculate-gradient
;;; Parameters:
;;;   x1, a positive number
;;;   y1, a positive number
;;;   x2, a positive number
;;;   y1, a positive number
;;; Purpose:
;;;   Calculates the gradient between two points
;;; Produces:
;;;   calculated-gradient, a number
;;; Preconditions:
;;;   [No other]
;;;   Preconditions are not verified
;;; Postconditions:
;;;   calculated-gradient is the gradient between two points,
;;;     (x1, y1) and (x2, y2).
;;;   Therefore, caluclated-gradient = ((y2 - y1) / (x2 - x1))
;;;   Note: If (x2 - x1) = 0, calculated-gradient will be 0.
(define calculate-gradient
  (lambda (x1 y1 x2 y2)
    (if (zero? (- x1 x2))
        0
        (/ (- y2 y1) (- x2 x1)))))

;Fade code modeled after code from
; Title: Exam 2: Control and Color
; Author: Zachary Segall

;;; Procedure:
;;;   color-blend
;;; Parameters:
;;;   initial-color, an integer encoded rgb-color
;;;   final-color, an integer encoded rgb-color
;;;   total-distance, a positive number
;;;   distance-traveled, a positive number
;;; Purpose:
;;;   Creates a new color between initial-color and final-color
;;; Produces:
;;;   blended-color, an integer encoded rgb-color
;;; Preconditions:
;;;   [No other]
;;;   Preconditions are not verified
;;; Postconditions:
;;;   blended-color is dependent on the distance-travelled in relation
;;;     to the total-distance.
;;;   The components of blended color are the difference of the
;;;     respective components of initial-color and final-color
;;;     multiplied by the ratio of distance-travelled and total-distance
;;;     which is then added to the initial-color component.
;;;   i.e., (irgb-red blended-color) =
;;;     (+ init-red
;;;        (* (/ distance-traveled total-distance)
;;;           (- fin-red init-red)
;;;   and similarily for the blue and green components.
(define color-blend
  (lambda (initial-color final-color total-distance distance-traveled)
    (let ([init-red (irgb-red initial-color)]
          [init-green (irgb-green initial-color)]
          [init-blue (irgb-blue initial-color)]
          [fin-red (irgb-red final-color)]
          [fin-green (irgb-green final-color)]
          [fin-blue (irgb-blue final-color)]
          [ratio (/ distance-traveled total-distance)])
      (irgb
       (+ init-red
          (* ratio 
             (- fin-red init-red)))
       (+ init-green
          (* ratio
             (- fin-green init-green)))
       (+ init-blue
          (* ratio
             (- fin-blue init-blue)))))))

;;; Procedure:
;;;   avg-color
;;; Parameters:
;;;   color1, an integer encoded rgb-color
;;;   color2, an integer encoded rgb-color
;;; Purpose:
;;;   Finds the average of two colors
;;; Produces:
;;;   average-color, an integer encoded rgb-color
;;; Preconditions:
;;;   [No other]
;;;   Preconditions are not verified
; Title: Exam 2: Control and Color
;;; Postconditions:
;;;   average-color's components are the average of
;;;    the respective components of color1 and color2.
;;;   i.e., (irgb-red average-color) = (((irgb-red color1) + (irgb-red color2)) / 2)
;;;   and so on for green and blue.
(define avg-color
  (lambda (color1 color2)
    (irgb (/ (+ (irgb-red color1) (irgb-red color2)) 2)
          (/ (+ (irgb-green color1) (irgb-green color2)) 2)
          (/ (+ (irgb-blue color1) (irgb-blue color2)) 2))))


;==============================================================================
;-----------------------------------------------------------------------------
;                      CHANGING/USING N TO CREATE NEW VALUES
;-----------------------------------------------------------------------------
;==============================================================================

;We decided not to generalize the choose-beam-color procedures with a higher
;  order procedure since there were only two of them, and only three elements 
;  in each list. If the lists were longer or we had more procedures, we would
;  have generalized them, but with only two instances of the procedure, writing
;  the higher order procedure may have taken more time and had a lower readability
;  that just using two procedures.

;;; Procedure:
;;;   choose-first-beam-color
;;; Parameters:
;;;   n, a non-negative integer
;;; Purpose:
;;;   Returns a color based on n
;;; Produces:
;;;   beam-color, an integer encoded irgb color
;;; Preconditions:
;;;   n must be so that 0 <= n <= 36
;;;   Preconditions are not verified
;;; Postconditions:
;;;   when (modulo n 3) is 0, beam-color will be (irgb 255 0 0) - red
;;;   when (modulo n 3) is 1, beam-color will be (irgb 0 255 0) - green
;;;   when (modulo n 3) is 2, beam-color will be (irgb 0 0 255) - blue
(define choose-first-beam-color
  (let ([list-of-colors (list (irgb 255 0 0) (irgb 0 255 0) (irgb 0 0 255))])
    (o (l-s list-ref list-of-colors) (r-s modulo 3))))

;;; Procedure:
;;;   choose-second-beam-color
;;; Parameters:
;;;   n, a non-negative integer
;;; Purpose:
;;;   Returns a color based on n
;;; Produces:
;;;   beam-color, an integer encoded irgb color
;;; Preconditions:
;;;   n must be so that 0 <= n <= 36
;;;   Preconditions are not verified
;;; Postconditions:
;;;   when (modulo n 3) is 0, beam-color will be (irgb 255 255 0) - yellow
;;;   when (modulo n 3) is 1, beam-color will be (irgb 0 255 255) - cyan
;;;   when (modulo n 3) is 2, beam-color will be (irgb 255 0 255) - purple
(define choose-second-beam-color
  (let ([list-of-colors (list (irgb 255 255 0) (irgb 0 255 255) (irgb 255 0 255))])
    (o (l-s list-ref list-of-colors) (r-s modulo 3))))

;;; Procedure:
;;;   choose-spot-x
;;; Parameters:
;;;   n, a non-negative integer
;;; Purpose:
;;;   Returns a integer based on n
;;; Produces:
;;;   spot-x, an integer
;;; Preconditions:
;;;   n must be so that 0 <= n <= 36
;;;   Preconditions are not verified
;;; Postconditions:
;;;   spot-x = (+ 1 (* 2 (floor (/ n 6)))
(define choose-spot-x
  (o (l-s + 1)
     (l-s * 2)
     floor
     (r-s / 6)))

;;; Procedure:
;;;   choose-spot-y
;;; Parameters:
;;;   n, a non-negative integer
;;; Purpose:
;;;   Returns a integer based on n
;;; Produces:
;;;   spot-y, an integer
;;; Preconditions:
;;;   n must be so that 0 <= n <= 36
;;;   Preconditions are not verified
;;; Postconditions:
;;;   spot-y = (+ 9 (* 2 (modulo n 2)))
(define choose-spot-y
  (o (l-s + 9)
     (l-s * 2)
     (r-s modulo 2)))

;;; Procedure:
;;;   spot-1
;;; Parameters:
;;;   init-n, a non-negative integer
;;; Purpose:
;;;   Returns a list of spot-1's attributes
;;; Produces:
;;;   spot-1-attributes, a list
;;; Preconditions:
;;;   n must be so that 0 <= n <= 999
;;;   Preconditions are not verified
;;; Postconditions:
;;;   (length spot-1-attributes) == 3
;;;   (list-ref spot-1-attributes 0) is an integer encoded irgb color,
;;;    (the beam color)
;;;   (list-ref spot-1-attributes 1) is an integer (the x-coordinate)
;;;   (list-ref spot-1-attributes 2) is an integer (the y-coordinate)
(define spot-1
  (lambda (init-n)
    (let ([n (modulo init-n 36)])
      (list (choose-first-beam-color n)
            (/ (choose-spot-x n) 12)
            (/ (choose-spot-y n) 12)))))

;;; Procedure:
;;;   spot-2
;;; Parameters:
;;;   init-n, a non-negative integer
;;; Purpose:
;;;   Returns a list of spot-1's attributes
;;; Produces:
;;;   spot-2-attributes, a list
;;; Preconditions:
;;;   n must be so that 0 <= n <= 999
;;;   Preconditions are not verified
;;; Postconditions:
;;;   (length spot-2-attributes) == 3
;;;   (list-ref spot-2-attributes 0) is an integer encoded irgb color,
;;;    (the beam color)
;;;   (list-ref spot-2-attributes 1) is an integer (the x-coordinate)
;;;   (list-ref spot-2-attributes 2) is an integer (the y-coordinate)
(define spot-2
  (lambda (init-n)
    (let ([n (floor (/ init-n 36))])
      (list (choose-second-beam-color n)
            (/ (choose-spot-x n) 12)
            (/ (choose-spot-y n) 12)))))


; testing:
;> (spot-1 541)
;'(65280 0 1)
;> (spot-2 541)
;'(16711680 2 1)
;> (spot-1 932)
;'(255 5 0)
;> (spot-2 932)
;'(65280 4 1)
;> (spot-1 1)
;'(65280 0 1)
;> (spot-2 1)
;'(16711680 0 0)

;===============================================================================
;-----------------------------------------------------------------------------
;                              ELLIPSE PROCEDURES
;-----------------------------------------------------------------------------
;===============================================================================
;;; Procedure:
;;;   drawing-two-ellipses
;;; Parameters:
;;;   center-1-x, a positive integer
;;;   center-2-x, a positive integer
;;;   center-y, a positive integer
;;;   width, a positive integer
;;;   height, a positive integer
;;;   color1, an integer encoded rgb color
;;;   color2, an integer encoded rgb color
;;; Purpose:
;;;   Creates a drawing of two ellipses
;;; Produces:
;;;   ellipses, a drawing
;;; Preconditions:
;;;   [No additional]
;;;    Note: Parameters are unverified
;;; Postconditions:
;;;   Creates a grouped drawing of two ellipses
;;;    Where they both have a width and height of (2 * radius)
;;;     The first ellipse has center (center-1-x, center-y),
;;;      and is of color color1
;;;     The second has center (center-2-x, center-y),
;;;      and is of color color2
(define drawing-two-ellipses
  (lambda (center-1-x center-2-x center-y width height color1 color2)
    (drawing-group
     (recolor-drawing
      color1
      (hshift-drawing
       center-1-x
       (vshift-drawing
        center-y
        (vscale-drawing
         height
         (hscale-drawing
          width
          drawing-unit-circle)))))
     (recolor-drawing
      color2
      (hshift-drawing
       center-2-x
       (vshift-drawing
        center-y
        (vscale-drawing
         height
         (hscale-drawing
          width
          drawing-unit-circle))))))))

;==============================================================================
;-----------------------------------------------------------------------------
;                                  THE BEAM
;-----------------------------------------------------------------------------
;============================================================================== 
;;; Procedure:
;;;   adding-beam!
;;; Parameters:
;;;   image, an image
;;;   hole-radius, a positive number
;;;   hole-x, a positive integer
;;;   hole-y, a positive integer
;;;   spot-radius, a positive number
;;;   spot-x, a positive integer
;;;   spot-y, a positive integer
;;;   beam-color, an integer encoded rgb-color
;;;   height, a positive number
;;; Purpose:
;;;   Adds a beam to image
;;; Produces:
;;;   [Nothing. Called for side-effect.]
;;; Preconditions:
;;;   hole-x, hole-y, spot-x, spot-y must all be less than
;;;    the height and width of the image.
;;;   Preconditions are not verified
;;; Postconditions:
;;;   Creates a beam on image from the edges of hole to the edges of spot
;;;    Where hole is an ellipse centered at (hole-x, hole-y) and
;;;     has a radius of hole-radius
;;;    And spot is an ellipse centered at (spot-x, spot-y) and
;;;     has a radius of spot-radius
;;;   The beam is of color
;;;     (beam-fade col row color beam-color beam-faded height hole-y)
(define adding-beam!
  (lambda (image hole-width hole-x hole-y spot-radius spot-x spot-y beam-color)
    (let* (;calculating the width and height of the image 
           [height (image-height image)]
           [width (image-width image)]
           
           ; Finds the red, blue, green components of beam-color
           ; and creates two new irgb colors using these
           [beam-red-comp (irgb-red beam-color)]
           [beam-green-comp (irgb-green beam-color)]
           [beam-blue-comp (irgb-blue beam-color)]
           [beam-lighter (irgb (* beam-red-comp 0.5)
                               (* beam-green-comp 0.5)
                               (* beam-blue-comp 0.5))]
           [beam-faded (irgb (* beam-red-comp 0.2)
                             (* beam-green-comp 0.2)
                             (* beam-blue-comp 0.2))]
           
           ;cacluates values that are constant for the spot-light
           [major-axis (if (< width height) 'y 'x)]
           [spacing (sqrt
                     (- (square spot-radius)
                        (square (* spot-radius (min
                                                (/ width height)
                                                (/ height width))))))]
           
           ;calculates the (x,y) coordinates for the foci of the spot-light
           [focus-1-x (if (equal? major-axis 'x)
                          (- spot-x spacing)
                          spot-x)]
           [focus-1-y (if (equal? major-axis 'y)
                          (- spot-y spacing)
                          spot-y)]
           [focus-2-x (if (equal? major-axis 'x)
                          (+ spot-x spacing)
                          spot-x)]
           [focus-2-y (if (equal? major-axis 'y)
                          (+ spot-y spacing)
                          spot-y)]
           
           ;calculates the distance to the edge of the ellipse from the center
           ;moving only in the x-direction
           [distance-to-edge-on-x-axis (if (equal? major-axis 'x)
                                           spot-radius
                                           (* (/ width height) 
                                              (/ (+ width height) 1800)
                                              50))]
           
           ;calculates the left and right coordinates of the top ellipse
           ;and the spotlight
           [hole-left (- hole-x (* 0.5 hole-width))]
           [hole-right (+ hole-x (* 0.5 hole-width))]
           [spot-left (- spot-x distance-to-edge-on-x-axis)]
           [spot-right (+ spot-x distance-to-edge-on-x-axis)]
           
           ;calculates the gradient of the two lines that enclose the beam
           [gradient-left
            (calculate-gradient hole-left hole-y spot-left spot-y)]
           [gradient-right
            (calculate-gradient hole-right hole-y spot-right spot-y)]
           
           ;calculates the direction of the gradient
           [gradient-direction? (if (positive? gradient-left) > <)]
           )
      (image-redo! image
                   (lambda (col row color)
                     (let ([distance-to-foci
                            (/
                             (+
                              (euclidean-distance col row focus-1-x focus-1-y)
                              (euclidean-distance col row focus-2-x focus-2-y))
                             2)])
                       ; if the row and col are between
                       ;  the top hole and the bottom spotlight
                       ;  and within the two lines that make up the beam
                       ;  the pixels at those points will be colored by the
                       ;  value returned by beam-fade
                       ; any other pixels will stay the original color
                       (if
                        (and
                         (< hole-y row spot-y)
                         (> distance-to-foci spot-radius)
                         ; if the gradient is positive, the left line will be
                         ;  greater than the right line, and therefore
                         ;  gradient-direction? is >
                         ; otherwise, the left line will be less than the
                         ;  right line, and therefore
                         ;  gradient-direction? is <
                         (gradient-direction?
                          (* gradient-left (- col hole-left))
                          (- row hole-y)
                          (* gradient-right (- col hole-right))))
                        (beam-fade col
                                   row
                                   color
                                   beam-lighter
                                   beam-faded
                                   height 
                                   hole-y)
                        color)))))))
;;; Procedure:
;;;   beam-fade
;;; Parameters:
;;;   col, a positive integer
;;;   row, a positive integer
;;;   color, an integer encoded rgb-color
;;;   beam-color, an integer encoded rgb-color
;;;   beam-faded, an integer encoded rgb-color
;;;   height, a positive integer
;;;   hole-y, a positive number
;;; Purpose:
;;;   Determines the color of the beam
;;;   based on col, row, and height
;;; Produces:
;;;   final-beam-color
;;; Preconditions:
;;;   [no additional]
;;;   Preconditions are not verified
;;; Postconditions:
;;;   When the row is between hole-y and 5/9ths of the height,
;;;     final-beam-color is the result of (beam-overlap color beam-faded)
;;;   When the row is between 2/3rds of the height and 5/9ths of the height,
;;;     final-beam-color is the result of (beam-overlap color beam-shade)
;;;   Otherwise,
;;;     final-beam-color is the result of (beam-overlap color beam-color)
(define beam-fade
  (lambda (col row color beam-color beam-faded height hole-y)
    (let* (
           ;calculates values for where the black fades to gray 
           [backdrop-border (round (* (/ 5 9) height))]
           [fade-distance (round (/ height 9))]
           [horizon (round (* (/ 2 3) height))])
      (cond
        ; if the row is between the center of hole and
        ;  backdrop-border (the beginning of the fade)
        ;  the color will be the value returned by beam-overlap of
        ;  the original color and beam-faded
        [(< hole-y row backdrop-border)
         (beam-overlap color
                       beam-faded)]
        ; if the row is within the fade area, i.e. between
        ;  backdrop-border and horizon,
        ;  the color will be the value returned by beam-overlap of
        ;  the original color and beam-shade
        ;  where beam-shade is the color-blend of beam-color and beam-faded
        [(<= backdrop-border row horizon)
         (let ([beam-shade (color-blend beam-color
                                        beam-faded
                                        fade-distance
                                        (- horizon row))])
           (beam-overlap color
                         beam-shade))]
        ; otherwise, (when the row is between the horizon and the spotlight)
        ;  the color will be the value returned by beam-overlap of
        ;  the original color and beam-color
        [else
         (beam-overlap color
                       beam-color)]))))

;;; Procedure:
;;;   beam-overlap
;;; Parameters:
;;;   color, an integer encoded rgb-color
;;;   current-beam-color, an integer encoded rgb-color
;;; Purpose:
;;;   Checks for possible overlaps
;;; Produces:
;;;   final-beam-color, an integer encoded rgb-color
;;; Preconditions:
;;;   [no additional]
;;;   Preconditions are not verified
;;; Postconditions:
;;;   If color is any shade of grey (including black),
;;;    final-beam-color is current-beam-color
;;;   Else, final-beam-color is the average of color and current-beam-color
(define beam-overlap
  (lambda (color current-beam-color)
    ; if the original color is any shade of grey
    ;  (where the components are all equal)
    ;  the procedure will return current-beam-color
    ;  otherwise, it will return the average of color and current-beam-color
    (if (= (irgb-red color)
           (irgb-green color) 
           (irgb-blue color))
        current-beam-color
        (avg-color color current-beam-color))))

;==============================================================================
;-----------------------------------------------------------------------------
;                                THE PATTERN
;-----------------------------------------------------------------------------
;============================================================================== 

;;; Procedure:
;;;   v-zigzag...
;;; Parameters:
;;;   upper-leg, an integer
;;;   lower-leg, an integer
;;; Purpose:
;;;   builds a vertically oriented zigzag shapepd drawing
;;; Produces:
;;;   zigzag, a drawing
;;; Preconditions:
;;;    [no-additional]
;;;    Note: preconditions are not verified
;;; Postconditions:
;;;    The upper part of the zigzag is (upper-leg - 1) pixels wide
;;;     and 1 pixel tall
;;;    The middle part of the zigzag is 1 pixel wide and
;;;     (+ upper-leg lower-leg 1) pixels tall 
;;;    The lower part of the zigzag is (lower-leg - 1) pixels  wide
;;;     and 1 pixel tall
;;;    The middle of the middle part of the zigzag is centered at (0, 0).
;;;    The upper part of the zigzag connects to the middle part at the top pixel
;;;     of the middle part and the rightmost pixel of the upper part
;;;    The lower part of the zigzag connects to the middle part at the 
;;;     bottom pixel of the middle part and the leftmost pixel of the upper part 
;;;    If upper-leg or lower-leg is less than 2, it will
;;;     automatically be set to 3.

(define v-zigzag...
  (lambda (upper-leg lower-leg)    
    (cond
      [(not (integer? upper-leg))
       (error "v-zigzag...: expected an integer for first parameter, recieved: " upper-leg)]
      [(not (integer? lower-leg))
       (error "v-zigzag...: expected an integer for second parameter, recieved: " lower-leg)] 
      [else
       ;sets leg length
       ;if legs are lower than 2, this sets them to 3
       (let ([upper (if (< upper-leg 2)
                        3
                        upper-leg)]
             [lower (if (< lower-leg 2)
                        3
                        lower-leg)])
         (drawing-compose
          (list (diagonal-shift (- (/ lower 2) 1.5)
                                lower
                                (hscale-drawing (- lower 1)
                                                drawing-unit-square))
                (vshift-drawing (/ (- lower upper) 2)
                                (vscale-drawing (+ upper lower 1) 
                                                drawing-unit-square))
                (diagonal-shift (/ (- upper 1) -2)
                                (* -1 upper) 
                                (hscale-drawing (- upper 1)
                                                drawing-unit-square)))))])))


;;; Procedure:
;;;   v-zigzag-chain...
;;; Parameters:
;;;   x-pos, a number
;;;   y-pos, a number
;;;   upper-leg-length, an integer
;;;   lower-leg-length, an integer
;;;   scale-factor, an integer
;;;   repetitions, an integer
;;; Purpose:
;;;   Produces a chain of the v-zigzag shape
;;; Produces:
;;;   drawing, a drawing
;;; Preconditions: 
;;;   [no additional]
;;; Postconditions:
;;;   v-zigzag-chain... uses v-zigzag... to draw its zig-zags
;;;   The first zigzag will have an upper-leg value of upper-leg-length and
;;;     a lower-leg value of lower-leg-length
;;;   Each subsequent zigzag in the chain will have a lower-leg value 
;;;     scale-factor greater than the lower-leg value for the previous 
;;;     zigzag in the chain and an upper-leg value the same as the
;;;     lower-leg value for the previous zigzag in the chain.
;;;   The first zigzag in the chain will be centered at (x-pos, y-pos)
;;;   Each subsequent zigzag will be shifted the upper-leg value of 
;;;     the previous zigzagin the x and y direction from the previous zigzag.
;;;   There will be repetitions zigzags in the chain

(define v-zigzag-chain...
  (lambda (x-pos
           y-pos
           upper-leg-length 
           lower-leg-length
           scale-factor 
           repetitions)
    (cond 
      [(not (number? x-pos)) 
       (error "v-zigzag-chain...: expected a number for first parameter, recieved: " x-pos)]
      [(not (number? y-pos)) 
       (error "v-zigzag-chain...: expected a number for second parameter, recieved: " y-pos)]
      [(not (integer? upper-leg-length)) 
       (error "v-zigzag-chain...: expected an integer for third parameter, recieved: " upper-leg-length )] 
      [(not (integer? lower-leg-length)) 
       (error "v-zigzag-chain...: expected an integer for fourth parameter, recieved: " lower-leg-length )] 
      [(not (integer? scale-factor)) 
       (error "v-zigzag-chain...: expected an integer for fifth parameter, recieved: " scale-factor )] 
      [(not (integer? repetitions)) 
       (error "v-zigzag-chain...: expected an integer for sixth parameter, recieved: " repetitions )] 
      [else
       (if (< 0 repetitions)          
           (drawing-compose
            (list (diagonal-shift x-pos 
                                  y-pos 
                                  (v-zigzag... upper-leg-length
                                               lower-leg-length))
                  (v-zigzag-chain... (+ x-pos lower-leg-length)
                                     (+ y-pos lower-leg-length)
                                     lower-leg-length
                                     (+ lower-leg-length scale-factor)
                                     scale-factor
                                     (- repetitions 1))))
           (diagonal-shift x-pos 
                           y-pos 
                           (v-zigzag... upper-leg-length
                                        lower-leg-length)))])))

;testing:
;> (image-show (drawing-render! (v2-zig-zag-recur... 10 10 5 2 11)
;                               canvas))
;14
;> (image-show (drawing-render! (v2-zig-zag-recur... 30 30 27 -2 11)
;                               canvas))
;14

;;; Procedure:
;;;   v-zigzag-chain-chain...
;;; Parameters:
;;;   n, an integer
;;;   start-x, a number
;;;   start-y, a number
;;;   end-x, a number
;;;   end-y, a number
;;;   start-lower-leg, an integer
;;;   end-lower-leg, an integer
;;;   start-upper-leg, an integer
;;;   end-lower-leg, an integer
;;;   start-scale-factor, an integer
;;;   end-scale-factor, an integer
;;;   start-zigzags, an integer
;;;   end-zigzags, an integer
;;; Purpose:
;;;   recurs v-zigzag-chain to produce a chain of zigzag chains
;;; Produces:
;;;   drawing, a drawing
;;; Preconditions:
;;;   [no additional]
;;; Postconditions:
;;;   if n is less than 0, the procedure will do nothing.
;;;   if start-zigzags and end-zigzags are less than 0
;;;    the procedure will do nothing
;;;   See v-zigzag-chain... for a description of each individual zigzag chain
;;;   There will be n zigzag chains in drawing.
;;;   The i-th zigzag chain drawn will begin at 
;;;   ({start-x + [(i / n) * (end-x - end-x)]}, 
;;;    {start-y + [(i / n) * (start-y - end-y)]})
;;;   The scale factor for the i-th zigzag will be
;;;      start-scale-factor + (i / n) * (start-scale-factor - end-scale-factor)
;;;   The number of zig-zags in the i-th chain will be 
;;;     start-zigzags + (i / n) * (start-zigzags - end-zigzags)
;;;   The length of the upper leg for the first zigzag in the i-th zigzag chain
;;;    will be start-upper-leg + (i / n) * (start-upper-leg - end-upper-leg)
;;;   The length of the lower leg for the first zigzag in the i-th zigzag chain
;;;    will be
;;;    start-lower-leg + (i / n) * (start-lower-leg - end-start-lower-leg)
(define v-zigzag-chain-chain...
  (lambda (n 
           start-x            start-y 
           end-x              end-y
           start-upper-leg    end-upper-leg
           start-lower-leg    end-lower-leg
           start-scale-factor end-scale-factor
           start-zigzags      end-zigzags)
    (let ([delta-x (delta... n start-x end-x)] 
          [delta-y (delta... n start-y end-y)]
          [delta-upper-leg (delta... n start-upper-leg end-upper-leg)]
          [delta-lower-leg (delta... n start-lower-leg end-lower-leg)]
          [delta-scale-factor (delta... n start-scale-factor end-scale-factor)]
          [delta-zigzags (delta... n start-zigzags end-zigzags)])
      (let kernel ([repetitions 0]
                   [x start-x]
                   [y start-y]
                   [upper-leg start-upper-leg]
                   [lower-leg start-lower-leg]
                   [scale-factor start-scale-factor]
                   [zigzags start-zigzags])
        (if (< repetitions n)
            (drawing-compose
             (list (v-zigzag-chain... x 
                                      y 
                                      (round lower-leg)
                                      (round upper-leg)
                                      (round scale-factor) 
                                      (round zigzags))
                   (kernel (+ repetitions 1)
                           (+ x delta-x)
                           (+ y delta-y)
                           (+ upper-leg delta-upper-leg)
                           (+ lower-leg delta-lower-leg)
                           (+ scale-factor delta-scale-factor)
                           (+ zigzags delta-zigzags) )))
            (v-zigzag-chain... x 
                               y 
                               (round lower-leg)
                               (round upper-leg)
                               (round scale-factor) 
                               (round zigzags)))))))

;testing
;;> (image-show (drawing-render!  (v-zigzag-chain-chain... 20
;                                                         10 10
;                                                         810 310
;                                                         5 30
;                                                         5 30
;                                                         4 -4
;                                                         20 20) canvas))

;; Procedure:
;;;   pattern!
;;; Parameters:
;;;   image, an image
;;; Purpose:
;;;   draws a pattern using v-zigzag-chain-chain...
;;;     and renders it onto an image
;;; Produces:
;;;   [nothing, called for side effect]
;;; Preconditions:
;;;   [no additional]
;;; Postconditions;
;;;   A pattern using three repeitiosn of v-zigzag-chain-chain...
;;;     will be rendered on to image.
;;;   There are some issues with scaling the size of the pattern made
;;;    with v-zigzag-chain-chain... because the v-zigzag... procedure
;;;    upon which v-zigzag-chain-chain... is based has minimum size requirements
;;;   Additionally, the sheer number of repetitions in v-zigzag-chain-chain...
;;;    make it liable to unintended side effects when its parameters are tweaked
;;;   In general, though, the patterns look relatively similar as long as
;;;    the size in the image remains about 100 by 100 pixels and stray elements
;;;    are minimal.
(define pattern!
  (lambda (image)
    (let* ([width (image-width image)]
           [height (image-height image)]
           [upper-pattern-limit (round (* (/ 22 30) height))]
           [largest-zigzag-size (max 5
                                     (round (* (/ 1 60) (+ width height))))])
      (let ([pattern
             (recolor-drawing
              (irgb 1 2 3)
              ;the strange irgb value is so that image-redo!
              ;can easily identify the pixels that are part
              ;of the pattern
              (drawing-compose
               (list 
                (v-zigzag-chain-chain... 10
                                         largest-zigzag-size (- height 10)
                                         largest-zigzag-size upper-pattern-limit 
                                         5 largest-zigzag-size
                                         5 largest-zigzag-size
                                         2 2
                                         1 10)
                (v-zigzag-chain-chain... 15
                                         largest-zigzag-size upper-pattern-limit 
                                         (* 4/9 width) upper-pattern-limit 
                                         largest-zigzag-size 5
                                         largest-zigzag-size 5
                                         -2 2
                                         15 15)
                (v-zigzag-chain-chain... 15
                                         (* 4/9 width) upper-pattern-limit 
                                         (* 8/9 width) upper-pattern-limit 
                                         5 largest-zigzag-size
                                         5 largest-zigzag-size
                                         2 -2
                                         15 15))))])
        (drawing-render! pattern
                         image)))))

;==============================================================================
;-----------------------------------------------------------------------------
;                               THE BACKGROUND
;-----------------------------------------------------------------------------
;==============================================================================

;;; Procedure:
;;;   background!
;;; Parameters:
;;;   img, an image
;;;   spot-1-color, an integer encoded irgb-color
;;;   spot-1-x, a non-negative integer
;;;   spot-1-y, a non-negative integer
;;;   spot-2-color, an integer encoded irgb-color
;;;   spot-2-x, a non-negative integer
;;;   spot-2-y, a non-negative integer
;;; Purpose:
;;;   draws a black fading to gray background with two colored 
;;;     spots in the gray area
;;; Produces:
;;;   [nothing, called for side effect]
;;; Preconditions:
;;;   [no additional]
;;;   Note: Preconditions are unverified because this procedure
;;;     is only called within the procedure that generates the final image
;;;     with inputs that should always be correct
;;; Postconditions:
;;;   The top 5/9s of the image will be colored black
;;;   The middle ninth of the image will be fading from black to gray
;;;   The bottom 1/3 of the image will be colored gray with two
;;;      ellipses colored spot-1-color and spot-2-color in the gray area
;;;   The first ellipse will be centered at (spot-1-x, spot-1-y)
;;;   The second ellipse will be centered at (spot-2-x, spot-2-y)
;;;   The ellipses will be of identical width and height.
;;;   The width and height of the ellipses should be the same ratio
;;;     as the width and height of img, reduced by a factor of 18.
;;;   Any pixels with irgb encoded value for (irgb 1 2 3) and within
;;;     the two spots will be colored a rainbow blend mixed with the 
;;;     color of the spot. This meshes with the pattern drawn in the procedure
;;;     pattern!

(define background!
  (lambda (img spot-1-color spot-1-x spot-1-y spot-2-color spot-2-x spot-2-y)
    (let* (;calculating the width and height of the image 
           [height (image-height img)]
           [width (image-width img)]
           
           ;calculates values for where the black fades to gray 
           [backdrop-border (round (* 5/9 height))]
           [fade-distance (round (/ height 9))]
           [horizon (round (* 2/3 height))]
           
           ;cacluates values that are constant for both ellipses
           [radius (/ (+ height width) 36)]
           [major-axis (if (< width height) 'y 'x)]          
           [spacing (sqrt
                     (- (square radius)
                        (square (* radius (min
                                           (/ width height)
                                           (/ height width))))))]
           
           
           ;calculates the (x,y) coordinates for the foci of the first ellipse
           [spot-1-focus-1-x (if (equal? major-axis 'x)
                                 (- spot-1-x spacing)
                                 spot-1-x)]
           [spot-1-focus-1-y (if (equal? major-axis 'y)
                                 (- spot-1-y spacing)
                                 spot-1-y)]
           [spot-1-focus-2-x (if (equal? major-axis 'x)
                                 (+ spot-1-x spacing)
                                 spot-1-x)]
           [spot-1-focus-2-y (if (equal? major-axis 'y)
                                 (+ spot-1-y spacing)
                                 spot-1-y)]
           
           ;calculates the (x,y) coordinates for the foci of the second ellipse
           [spot-2-focus-1-x (if (equal? major-axis 'x)
                                 (- spot-2-x spacing)
                                 spot-2-x)]
           [spot-2-focus-1-y (if (equal? major-axis 'y)
                                 (- spot-2-y spacing)
                                 spot-2-y)]
           [spot-2-focus-2-x (if (equal? major-axis 'x)
                                 (+ spot-2-x spacing)
                                 spot-2-x)]
           [spot-2-focus-2-y (if (equal? major-axis 'y)
                                 (+ spot-2-y spacing)
                                 spot-2-y)])
      (image-redo!
       img
       (lambda (col row color)
         (let ;finds the distance between the current pixel and the foci of the 
             ;ellipses
             ([distance-to-spot-1
               (/ (+ (euclidean-distance col
                                         row
                                         spot-1-focus-1-x 
                                         spot-1-focus-1-y)
                     (euclidean-distance col
                                         row
                                         spot-1-focus-2-x
                                         spot-1-focus-2-y)) 2)]
              [distance-to-spot-2
               (/ (+ (euclidean-distance col
                                         row
                                         spot-2-focus-1-x
                                         spot-2-focus-1-y)
                     (euclidean-distance col
                                         row
                                         spot-2-focus-2-x
                                         spot-2-focus-2-y)) 2)]
              [pattern-color (hsv->irgb
                              (list (round (number-blend... 0 360 width col))
                                    1
                                    1))])
           (cond
             ;colors the top 5/9s black
             [(> backdrop-border row)
              (irgb 0 0 0)]
             
             ;has the middle ninth fade from black to gray
             [(>= horizon row backdrop-border)
              (let ([gray-shade
                     (color-blend
                      (irgb 128 128 128)
                      (irgb 0 0 0)
                      fade-distance
                      (- horizon row))])
                gray-shade)]
             
             ;colors the bottom third appropriately
             [else
              (cond
                ;If the point is within both ellipses,
                ; it is colored based on the colors of both ellipses
                [(and (< distance-to-spot-1 radius)
                      (< distance-to-spot-2 radius))
                 (if (= color (irgb 1 2 3))
                     ;if the color is part of the pattern,
                     ;it is colored based on a rainbow fade
                     ;blended with the ellipse color
                     (avg-color (avg-color spot-1-color
                                           spot-2-color)
                                pattern-color)
                     (avg-color spot-1-color
                                spot-2-color))]
                
                ;If the point is within the first ellipse,
                ; it is colored based on the color of the first ellipse.
                [(< distance-to-spot-1 radius)
                 (if (= color (irgb 1 2 3))
                     ;if the color is part of the pattern,
                     ;it is colored based on a rainbow fade
                     ;blended with the ellipse color
                     (avg-color spot-1-color
                                pattern-color)
                     spot-1-color)]
                
                ;If the point is within the second ellipse,
                ; it is colored based on the color of the second ellipse.
                [(< distance-to-spot-2 radius)
                 (if (= color (irgb 1 2 3))
                     ;if the color is part of the pattern,
                     ;  it is colored based on a rainbow fade
                     ;  blended with the ellipse color
                     (avg-color spot-2-color
                                pattern-color)
                     spot-2-color)]
                
                
                ;otherwise the bottom third of the image is just gray
                [else
                 (irgb 128 128 128)])])))))))
;==============================================================================
;-----------------------------------------------------------------------------
;                             THE FINAL PROCEDURE
;-----------------------------------------------------------------------------
;==============================================================================

(define image-series 
  (lambda (n width height)
    ;verifying preconditions
    ;checking that n is between 0 and 999 inclusive, and is an integer
    ;and that width and height are both positive integers
    (cond
      [(not (and (integer? n)
                 (<= 0 n 999)))
       (error "image-series expected an integer between 0 and 999 inclusive for the first parameter, recieved: " n)]
      [(not (and (positive? width)
                 (integer? width)))
       (error "image-series expected an positive integer for the second parameter, recieved: " width)]
      [(not (and (positive? height)
                 (integer? height)))
       (error "image-series expected an positive integer for the third parameter, recieved: " height)]
      [else
       (let* ([img (image-new width height)]
              
              ;Information about the first spotlight
              [spot-1-info (spot-1 n)]
              [spot-1-color (car spot-1-info)]
              [spot-1-x (* width (cadr spot-1-info))]
              [spot-1-y (* height (caddr spot-1-info))]
              
              ;Information about the second spotlight
              [spot-2-info (spot-2 n)]
              [spot-2-color (car spot-2-info)]
              [spot-2-x (* width (cadr spot-2-info))]
              [spot-2-y (* height (caddr spot-2-info))]
              [radius (/ (+ height width) 36)]
              
              ;Information about the holes
              [hole-height (/ height 9)]
              [hole-width (/ width 9)]
              [hole-1-x (* (/ 2 9) width)]
              [hole-y (* (/ 2 9) height)] ; both holes have the same y-value
              [hole-2-x (* (/ 7 9) width)])
         
         ;renders the pattern onto the image
         (pattern! img)
         
         ;constructs the background
         (background! img
                      spot-1-color
                      spot-1-x
                      spot-1-y
                      spot-2-color
                      spot-2-x
                      spot-2-y)
         
         ;adds beam number 1
         (adding-beam! img
                       hole-width
                       hole-1-x
                       hole-y
                       radius
                       spot-1-x
                       spot-1-y
                       spot-1-color) 
         
         ;adds beam number 2
         (adding-beam! img
                       hole-width
                       hole-2-x
                       hole-y
                       radius
                       spot-2-x
                       spot-2-y
                       spot-2-color) 
         
         ;adds two top holes
         (drawing-render!
          (drawing-two-ellipses
           hole-1-x
           hole-2-x
           hole-y
           hole-width
           hole-height
           spot-1-color
           spot-2-color)
          img) 
         
         )])))


