#lang racket
(require gigls/unsafe)

;;; Procedure:
;;;  image-series
;;; Parameter:
;;;  n, a non-negative integer
;;;  width, a positive integer
;;;  height, a positive integer
;;; Purpose:
;;;  Generates the nth image in a series of 1000 images
;;; Produces:
;;;  universe, an image
;;; Preconditions:
;;;  n < 1000
;;; Postconditions:
;;;  Each different value of n would generates a distinct image
;;;    with the given width and given height.

(define image-series
  (lambda (n
           width
           height)
    (let ([canvas
           (draw-background n width height)])
      (turtle-stars! canvas width height)
      (draw-planets! canvas n)
      (image-show canvas))))

;;; Procedure:
;;;  draw-ellipse!
;;; Parameter:
;;;  image, an image
;;;  col, a positive integer
;;;  row, a positive integer
;;;  radius-h, a positive integer
;;;  radius-v, a positive integer
;;;  color, a positive integer
;;; Purpose:
;;;  draw an ellipse whose center locates at (col, row)
;;;  with given width, height and color in a given image
;;; Produces:
;;;  ellipse, an ellipse on the given image (side effect)
;;; Preconditions:
;;;  col, row,  (- col radius-h), (- col radius-h), (- row radius-v) and (+ row radius-v)
;;;  should all be points inside the image
;;; Postconditions:
;;;  (left ellipse) = (- col radius-h)
;;;  (top ellipse) = (- row radius-v)
;;;  (width ellipse) = (* 2 radius-h)
;;;  (top ellipse) = (* 2 radius-v)

; The code draw-ellipse! is modeled from code draw-circle! from lab "Geometric Art"
; URL: http://www.cs.grinnell.edu/~weinman/courses/CSC151/2014F/labs/geometric-art-lab.html
; Authors: Janet Davis Samuel A. Rebelsky Jerod Weinman

(define draw-ellipse!
  (lambda (image
           col
           row
           radius-h
           radius-v
           color)(image-select-ellipse! image
                           REPLACE
                           (- col radius-h)
                           (- row radius-v)
                           (* 2 radius-h)
                           (* 2 radius-v))
    (context-set-brush! "2. Hardness 025" 10)
    (image-stroke-selection! image)
    (context-set-fgcolor! color)
    (image-fill-selection! image)
    (image-select-nothing! image)))



;;; Procedure:
;;;  round-to-90
;;; Parameter:
;;;  ang, a positive integer
;;; Purpose:
;;;  to turn any integer to a new integer that is less than 90
;;; Produces:
;;;  integer-new, a positive integer
;;; Preconditions:
;;;  [No Additional]
;;; Postconditions:
;;;  when ang > 90,
;;;   ang-new = 90 - 180 * (quotient ang 90)
;;;  when ang < 90.
;;;   ang-new = ang

(define round-to-90
  (lambda (ang)
    (if (> ang 90)
        (- 90 (modulo ang 90))
        ang)))


;;; Procedure:
;;;  draw-planet!
;;; Parameter:
;;;  image, an image
;;;  oldhr, a positive integer
;;;  oldvr, a positive integer
;;;  col, a positive integer
;;;  row, a positive integer
;;;  color, a string
;;;  angle, a positive integer
;;;  distance, a positive integer
;;;  hr, a positive integer
;;;  vr, a positive integer
;;; Purpose:
;;;  draw an ellipse with given width, height and color, located at the certain distance
;;;  and certain angle from the center of ellipse
;;; Produces:
;;;   planet, an ellipse on the given image. [ Called for the side effect]
;;; Preconditions:
;;;  oldhr, col, distance, hr and (col + oldhr + distace + hr)
;;;  are smaller than width of the image.
;;;  oldvr, row, vr, distance and (row + oldvr + vr +distance)
;;;  are smaller than height of the image.
;;; Postconditions:
;;;  (width planet) = hr
;;;  (height planet) = vr
;;;  when 0 < angle <= 90
;;;   (left planet) =
;;;     (- (+ (+ col (* oldvr (cos angle)))
;;;        (* distance (cos (round-to-90 angle)))) (2 * vr))
;;;   (top planet) =
;;;    (- (+ (+ row (* oldhr (sin angle)))
;;;        (* distance (sin (round-to-90 angle)))) (2 * hr))
;;;  when 90 < angle <= 180
;;;   (left planet) =
;;;     (- (- (+ col (* oldvr (cos angle)))
;;;        (* distance (cos (round-to-90 angle)))) (2 * vr))
;;;   (top planet) =
;;;     (- (+ (+ row (* oldhr (sin angle)))
;;;        (* distance (sin (round-to-90 angle)))) (2 * hr))
;;;  when 180 < angle <= 270
;;;   (left planet) =
;;;     (- (- (+ col (* oldvr (cos angle)))
;;;        (* distance (cos (round-to-90 angle)))) (2 * vr))
;;;   (top planet) =
;;;     (- (- (+ row (* oldhr (sin angle)))
;;;        (* distance (sin (round-to-90 angle)))) (2 * hr))
;;;  when 270 < angle <= 360
;;;   (left planet) =
;;;     (+ (+ (+ col (* oldvr (cos angle)))
;;;        (* distance (cos (round-to-90 angle)))) (2 * vr))
;;;   (top planet) =
;;;     (- (+ (+ row (* oldhr (sin angle)))
;;;        (* distance (sin (round-to-90 angle)))) (2 * hr))


;;; Procedure:
;;;  cyclic-list
;;; Parameter:
;;;  lst, a non-empty list
;;;  len, a non-negative number
;;; Purpose:
;;;  to generate a new list that contains len element
;;; Produces:
;;;  newlst, a list
;;; Preconditions:
;;;  [No Additional]
;;; Postconditions:
;;;  when len < (list-length lst)
;;;   newlst = (list-take lst len)
;;;  when len > (list-length lst)
;;;   (list-ref newlst (+ 1 (list-length lst))) = (list-ref lst 1)
;;;  when len = 0
;;;   newlst is a null list

(define cyclic-list
  (lambda (lst
           len)
    (map (l-s list-ref lst)
         (map (r-s mod (length lst))
              (iota len)))))
; This code is modeled based on the code for Problem 5, Exam 2, from "note to Exam2" Professor Weinman sent to us.
; Author: Sam Rebelsky


;;; Procedure:
;;;  color-selection
;;; Parameter:
;;;  n, a non-negative integer
;;; Purpose:
;;;  to produce a list of colors from a same color scheme
;;; Produces:
;;;  color-list, a list
;;; Preconditions:
;;;  n =< 1000
;;; Postconditions:
;;;  (list-length color-list) = 9
;;;  All elements of  color-list are names of colors belonging to a same color scheme.

(define color-selection
  (lambda (n)
    (let ([selection
           (lambda (pattern)
             (cyclic-list (context-list-colors pattern) 9))]
         ;selection: generates a list of 9 different colors
          )
      (selection (cond [(and (< n 100)
                             (>= n 0)) "green"]
                       [(and (< n 200)
                             (>= n 100)) "red"]
                       [(and (< n 300)
                             (>= n 200)) "blue"]
                       [(and (< n 400)
                             (>= n 300)) "yellow"]
                       [(and (< n 500)
                             (>= n 400)) "pink"]
                       [(and (< n 600)
                             (>= n 500)) "brown"]
                       [(and (< n 700)
                             (>= n 600)) "orange"]
                       [(and (< n 800)
                             (>= n 700)) "violet"]
                       [(and (< n 900)
                             (>= n 800)) "gold"]
                       [(and (< n 1001)
                             (>= n 900)) "light"])))))



;;; Procedure:
;;;  draw-planets!
;;; Parameter:
;;;  image, an image
;;;  n, a non-negative number
;;; Purpose:
;;;  To draw a set of ellipses 
;;; Produces:
;;;   [Nothing; Called for the side effect]
;;; Preconditions:
;;;  n =< 1000
;;; Postconditions:
;;;  there are nine colored ellipses around an orange ellipse
;;;  the colors of the nine ellipses are based on n
;;;  the distances between each ellipses are the same

(define draw-planets!
  (lambda (image
           n)
    (let* ([width (image-width image)]
           [height (image-height image)]
           [diagnol
            (sqrt (+ (square (* width 0.8)) (square (* height 0.8))))]
           [col
            (truncate (/ width 2))]
           [row
            (truncate (/ height 2))])
      (draw-ellipse! image col row (/ width 20) (/ height 20) "orange") ; sun
      (for-each
       (lambda (color
                distance
                angle
                )
         (draw-planet! image (/ width 20) (/ height 20) col row color angle distance (/ width 80) (/ height 80)))
       ; this procedure draws each planets with given inputs
       (color-selection n)
       ; a list of colors
       (map (o (l-s * (truncate (* (/ 9 200) (min width height))))  increment) (iota 9))
       ; a list of distances between planets and the center of sun
       (map (o (r-s modulo 360) truncate (l-s * (* 3.6 n)))
            (list (/ 1 0.24) ; Mercury
                  (/ 1 0.62) ; Venus
                  1 ; Earth
                  (/ 1 2) ; Mars
                  (/ 1 12) ; Jupiter
                  (/ 1 30) ; Saturn
                  (/ 1 84) ; Uranus
                  (/ 1 165) ; Neptune
                  (/ 1 248)) ; Pluto
            )))))
       ;this is a list of ratios between each planet's orbital period and earth's orbital period

;;; Procedure:
;;;   draw-background
;;; Parameter:
;;;   n, a non-negative integer
;;;   width, a positive integer
;;;   heigh, a positive integer
;;; Purpose:
;;;   To produce an image of the given height and width with blended color.
;;; Produce:
;;;   Back-ground, an image.
;;; Preconditions:
;;;   n =< 1000
;;; Postconditions:
;;;   For an arbitrary pixel (col, row) in Back-ground, the color is
;;;   (irgb (* (- width col) (/ (- 128 (mod 32 n)) width)) 16
;;;   (* row (/ (+ 64 (mod 32 n)) height)))).

(define draw-background
  (lambda (n
           width
           height)
    (image-compute
     (lambda (col row)
       (irgb (* (- width col) (/ (- 128 (mod 32 ( + 1 n))) width))
             16
             (* row (/ (+ 64 (mod 32 ( + 1 n))) height))))
     width height)))




;;; Procedure:
;;;   close?
;;; Parameter:
;;;   num1, a number
;;;   num2, a number
;;; Purpose:
;;;   To check if the difference between two number is less then 10
;;; Produce:
;;;   a Boolean value
;;; Preconditions:
;;;   No additional.
;;; Postconditions:
;;;   If (abs (- num1 num2)) is less than 10, then #t is produced.
;;;   If (abs (- num1 num2)) is larger than or equal to 10, then #f is produced.


(define close?
  (lambda (num1
           num2)
    (< (abs (- num1 num2)) 10)))



;;; Procedure:
;;;  turtle-pentagon!
;;; Parameters:
;;;  s-l, a positive number
;;;  turtle, a turtle
;;;  h, a positive number
;;; Purpose:
;;;  Draw five vertices of a pentagon with s-l as side-length.
;;; Produces:
;;;  turtle, the same turtle
;;; Preconditions:
;;;  The turtle is within the bounds of its underlying image.
;;;  No boundary is closer than side-length.
;;; Postconditions:
;;;   For every turtle with the position at (col, row) where
;;;     (close? h (+ col row)) doesn't hold, the following will be true as
;;;      postconditions.
;;;   The turtle is again in the same position, facing the same
;;;     direction.
;;;   The turtle's pen is down.
;;;   The image now contains a picture of five vertices of a pentagon,
;;;     drawn with the turtle's current brush and color. The edges of
;;;     the pentagon are s-l.
;;;   One of the five vertices is at the turtle's original position.  
;;;   
;;; The documentation is edited from the original documentation authored by
;;; Professor J.Weinman, quesion 5, exam3.
;;; http://www.cs.grinnell.edu/~weinman/courses/CSC151/2014F/assignments/exam3.2014F.html

;;; The code is inspired by quesion 5 in exam3.

(define turtle-pentagon!
  (lambda (s-l
           turtle
           h)
    (let
        ([movement
          (lambda (s-l
                   turtle
                   h)
            (turtle-up! turtle)
            (turtle-forward! turtle s-l)
            (turtle-turn! turtle (- 180 (/ (* 3 180) 5)))
            (if  (close? h
                         (+ (car (turtle-point turtle))
                            (cdr (turtle-point turtle))))               
                 (turtle-up! turtle)
                 (turtle-down! turtle))
            (turtle-forward! turtle 1)
            (turtle-up! turtle))])
         ;movement: enable the turtle to draw two dots
      (repeat 5 movement s-l turtle h))))
;Procedure for "turtle-polygon!" is borrowed from Peiyun's answer for
;Question 5 in Exam 3.        


;;; Procedure:
;;;  turtle-stars!
;;; Parameter:
;;;  image, an image
;;;  width, a positive integer
;;;  height, a positive integer
;;; Purpose:
;;;  To produce an image of blended color with star-like dots 
;;; Produces:
;;;  turtle, the same turtle
;;; Preconditions:
;;;  The turtle is within the bounds of its underlying image.
;;;  No boundary is closer than side-length.
;;; Postconditions:
;;;   The turtle is again in the same position, facing the same
;;;     direction.
;;;   The turtle's pen is down.
;;;   The image now contains a picture of numbers of five vertices of a pentagon,
;;;     drawn with "2. Star" brush in white. 

;;; The documentation is edited from the original documentation authored by
;;; Professor J.Weinman, quesion 5, exam3.
;;; http://www.cs.grinnell.edu/~weinman/courses/CSC151/2014F/assignments/exam3.2014F.html


(define turtle-stars!
  (lambda (image
           width
           height)
    (let*  ([t
             (turtle-new image)]
            [side-length
             (/ (min width height) 8)]
            [list-of-column
             (map (o (l-s * 10) (r-s + 1))
                  (iota (truncate (/ height 10))))]
            [list-of-row
             (reverse (map (r-s * (/ width height)) list-of-column))]
            [angle
             (map (o (l-s * 10) (r-s + 3) (r-s modulo 5))
                  (iota (truncate (/ height 10))))])
      ;Purpose of "list-of-column" is to find the start columns for the stars.
      ;Purpose of "list-of-row" is to find the start rows for the stars.
      ;Purpose of "angle" is to find the turning angles.
      (turtle-set-brush! t "2. Star" 1)
      (turtle-set-color! t "white")
      (for-each
       (lambda (col
                row
                angle)
         (turtle-teleport! t col row)
         (turtle-turn! t angle)
         (turtle-pentagon! side-length t height))
       list-of-row
       list-of-column
       angle))))
